autosnippet 3.0.0 → 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 +759 -243
  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
@@ -5,10 +5,11 @@
5
5
 
6
6
  import fs from 'node:fs';
7
7
  import path from 'node:path';
8
+ import { LanguageService } from '../../shared/LanguageService.js';
8
9
 
9
10
  const FRONTMATTER_RE = /^---\n([\s\S]*?)\n---/;
10
- const SNIPPET_HEADING_RE = /^##\s+(?:Snippet|Code|代码)/mi;
11
- const USAGE_HEADING_RE = /^##\s+(?:Usage\s*Guide|用法|使用指南)/mi;
11
+ const _SNIPPET_HEADING_RE = /^##\s+(?:Snippet|Code|代码)/im;
12
+ const USAGE_HEADING_RE = /^##\s+(?:Usage\s*Guide|用法|使用指南)/im;
12
13
  const FENCED_CODE_RE = /```(\w*)\n([\s\S]*?)```/;
13
14
 
14
15
  export class RecipeParser {
@@ -17,7 +18,9 @@ export class RecipeParser {
17
18
  * 需包含: frontmatter + 代码块 + Usage Guide
18
19
  */
19
20
  isCompleteRecipe(text) {
20
- if (!text) return false;
21
+ if (!text) {
22
+ return false;
23
+ }
21
24
  return FRONTMATTER_RE.test(text) && FENCED_CODE_RE.test(text) && USAGE_HEADING_RE.test(text);
22
25
  }
23
26
 
@@ -25,7 +28,9 @@ export class RecipeParser {
25
28
  * 检查是否为「仅介绍」Recipe(有 frontmatter 但无代码块)
26
29
  */
27
30
  isIntroOnly(text) {
28
- if (!text) return false;
31
+ if (!text) {
32
+ return false;
33
+ }
29
34
  return FRONTMATTER_RE.test(text) && !FENCED_CODE_RE.test(text);
30
35
  }
31
36
 
@@ -35,7 +40,9 @@ export class RecipeParser {
35
40
  * @returns {object|null}
36
41
  */
37
42
  parse(text) {
38
- if (!text) return null;
43
+ if (!text) {
44
+ return null;
45
+ }
39
46
 
40
47
  const frontmatter = this.parseFrontmatter(text);
41
48
  const body = text.replace(FRONTMATTER_RE, '').trim();
@@ -54,9 +61,10 @@ export class RecipeParser {
54
61
  if (usageMatch) {
55
62
  const usageStart = usageMatch.index + usageMatch[0].length;
56
63
  const nextHeading = body.slice(usageStart).search(/^##\s+/m);
57
- usageGuide = nextHeading > 0
58
- ? body.slice(usageStart, usageStart + nextHeading).trim()
59
- : body.slice(usageStart).trim();
64
+ usageGuide =
65
+ nextHeading > 0
66
+ ? body.slice(usageStart, usageStart + nextHeading).trim()
67
+ : body.slice(usageStart).trim();
60
68
  }
61
69
 
62
70
  // 提取标题
@@ -72,8 +80,10 @@ export class RecipeParser {
72
80
  description: frontmatter.description || frontmatter.summary || '',
73
81
  trigger: frontmatter.trigger || this.#generateTrigger(title),
74
82
  category: frontmatter.category || 'general',
75
- language: frontmatter.language || (codeBlocks[0]?.language !== 'text' ? codeBlocks[0]?.language : 'swift'),
76
- code: codeBlocks.map(b => b.code).join('\n\n'),
83
+ language:
84
+ frontmatter.language ||
85
+ (codeBlocks[0]?.language !== 'text' ? codeBlocks[0]?.language : 'swift'),
86
+ code: codeBlocks.map((b) => b.code).join('\n\n'),
77
87
  codeBlocks,
78
88
  usageGuide,
79
89
  headers,
@@ -86,9 +96,11 @@ export class RecipeParser {
86
96
  * 从文本中解析多段 Recipe(按 `---` 分隔)
87
97
  */
88
98
  parseAll(text) {
89
- if (!text) return [];
90
- const segments = text.split(/\n---\n/).filter(s => s.trim().length > 0);
91
- return segments.map(s => this.parse(s)).filter(Boolean);
99
+ if (!text) {
100
+ return [];
101
+ }
102
+ const segments = text.split(/\n---\n/).filter((s) => s.trim().length > 0);
103
+ return segments.map((s) => this.parse(s)).filter(Boolean);
92
104
  }
93
105
 
94
106
  /**
@@ -96,7 +108,9 @@ export class RecipeParser {
96
108
  */
97
109
  parseFrontmatter(text) {
98
110
  const match = text.match(FRONTMATTER_RE);
99
- if (!match) return {};
111
+ if (!match) {
112
+ return {};
113
+ }
100
114
 
101
115
  const fm = {};
102
116
  for (const line of match[1].split('\n')) {
@@ -105,11 +119,19 @@ export class RecipeParser {
105
119
  const key = line.slice(0, colonIdx).trim();
106
120
  let value = line.slice(colonIdx + 1).trim();
107
121
  if (value.startsWith('[') && value.endsWith(']')) {
108
- value = value.slice(1, -1).split(',').map(s => s.trim().replace(/^['"]|['"]$/g, ''));
109
- } else if (value === 'true') value = true;
110
- else if (value === 'false') value = false;
111
- else if (/^\d+$/.test(value)) value = parseInt(value, 10);
112
- else value = value.replace(/^['"]|['"]$/g, '');
122
+ value = value
123
+ .slice(1, -1)
124
+ .split(',')
125
+ .map((s) => s.trim().replace(/^['"]|['"]$/g, ''));
126
+ } else if (value === 'true') {
127
+ value = true;
128
+ } else if (value === 'false') {
129
+ value = false;
130
+ } else if (/^\d+$/.test(value)) {
131
+ value = parseInt(value, 10);
132
+ } else {
133
+ value = value.replace(/^['"]|['"]$/g, '');
134
+ }
113
135
  fm[key] = value;
114
136
  }
115
137
  }
@@ -143,8 +165,7 @@ export class RecipeParser {
143
165
 
144
166
  const content = fs.readFileSync(fullPath, 'utf8');
145
167
  const ext = path.extname(fullPath).toLowerCase();
146
- const langMap = { '.swift': 'swift', '.m': 'objc', '.h': 'objc', '.mm': 'objc', '.ts': 'typescript', '.js': 'javascript', '.py': 'python' };
147
- const language = langMap[ext] || 'swift';
168
+ const language = LanguageService.langFromExt(ext);
148
169
 
149
170
  // 尝试解析为完整 Recipe Markdown
150
171
  if (this.isCompleteRecipe(content)) {
@@ -163,20 +184,22 @@ export class RecipeParser {
163
184
  // 回退: 将整个文件内容作为代码片段
164
185
  const title = path.basename(fullPath, ext);
165
186
  return {
166
- items: [{
167
- title,
168
- summary: '',
169
- description: '',
170
- trigger: this.#generateTrigger(title),
171
- category: 'Utility',
172
- language,
173
- code: content,
174
- codeBlocks: [{ language, code: content }],
175
- usageGuide: '',
176
- headers: this.#extractHeaders(content),
177
- includeHeaders: false,
178
- frontmatter: {},
179
- }],
187
+ items: [
188
+ {
189
+ title,
190
+ summary: '',
191
+ description: '',
192
+ trigger: this.#generateTrigger(title),
193
+ category: 'Utility',
194
+ language,
195
+ code: content,
196
+ codeBlocks: [{ language, code: content }],
197
+ usageGuide: '',
198
+ headers: this.#extractHeaders(content),
199
+ includeHeaders: false,
200
+ frontmatter: {},
201
+ },
202
+ ],
180
203
  isMarked: false,
181
204
  };
182
205
  }
@@ -197,12 +220,16 @@ export class RecipeParser {
197
220
  // 尝试完整 Recipe 解析
198
221
  if (this.isCompleteRecipe(text)) {
199
222
  const parsed = this.parse(text);
200
- if (parsed) return parsed;
223
+ if (parsed) {
224
+ return parsed;
225
+ }
201
226
  }
202
227
 
203
228
  // 尝试批量解析
204
229
  const all = this.parseAll(text);
205
- if (all.length > 0) return all;
230
+ if (all.length > 0) {
231
+ return all;
232
+ }
206
233
 
207
234
  throw new Error('文本不是有效的 Recipe Markdown 格式');
208
235
  }
@@ -219,13 +246,15 @@ export class RecipeParser {
219
246
  throw new Error('文本内容为空');
220
247
  }
221
248
 
222
- const language = opts.language || 'swift';
249
+ const language = opts.language || 'unknown';
223
250
 
224
251
  // 先尝试标准解析
225
252
  try {
226
253
  const result = await this.parseFromText(text, opts);
227
254
  return result;
228
- } catch { /* 继续兜底逻辑 */ }
255
+ } catch {
256
+ /* 继续兜底逻辑 */
257
+ }
229
258
 
230
259
  // 提取代码块
231
260
  const codeBlocks = [];
@@ -235,12 +264,10 @@ export class RecipeParser {
235
264
  codeBlocks.push({ language: match[1] || language, code: match[2].trim() });
236
265
  }
237
266
 
238
- const code = codeBlocks.length > 0
239
- ? codeBlocks.map(b => b.code).join('\n\n')
240
- : text.trim();
267
+ const code = codeBlocks.length > 0 ? codeBlocks.map((b) => b.code).join('\n\n') : text.trim();
241
268
 
242
269
  // 简单标题推断
243
- const titleLine = text.split('\n').find(l => l.trim().startsWith('#'));
270
+ const titleLine = text.split('\n').find((l) => l.trim().startsWith('#'));
244
271
  const title = titleLine ? titleLine.replace(/^#+\s*/, '').trim() : 'Untitled Snippet';
245
272
 
246
273
  return {
@@ -270,7 +297,13 @@ export class RecipeParser {
270
297
  }
271
298
 
272
299
  #generateTrigger(title) {
273
- if (!title) return '';
274
- return title.toLowerCase().replace(/[^a-z0-9]+/g, '_').replace(/(^_|_$)/g, '').slice(0, 30);
300
+ if (!title) {
301
+ return '';
302
+ }
303
+ return title
304
+ .toLowerCase()
305
+ .replace(/[^a-z0-9]+/g, '_')
306
+ .replace(/(^_|_$)/g, '')
307
+ .slice(0, 30);
275
308
  }
276
309
  }
@@ -8,11 +8,11 @@ export class CoarseRanker {
8
8
 
9
9
  constructor(options = {}) {
10
10
  this.#weights = {
11
- bm25: options.bm25Weight ?? 0.30,
12
- semantic: options.semanticWeight ?? 0.30,
13
- quality: options.qualityWeight ?? 0.20,
14
- freshness: options.freshnessWeight ?? 0.10,
15
- popularity: options.popularityWeight ?? 0.10,
11
+ bm25: options.bm25Weight ?? 0.3,
12
+ semantic: options.semanticWeight ?? 0.3,
13
+ quality: options.qualityWeight ?? 0.2,
14
+ freshness: options.freshnessWeight ?? 0.1,
15
+ popularity: options.popularityWeight ?? 0.1,
16
16
  };
17
17
  }
18
18
 
@@ -22,24 +22,32 @@ export class CoarseRanker {
22
22
  * @returns {Array} — sorted with coarseScore
23
23
  */
24
24
  rank(candidates) {
25
- if (!candidates || candidates.length === 0) return [];
25
+ if (!candidates || candidates.length === 0) {
26
+ return [];
27
+ }
26
28
 
27
- return candidates.map(c => {
28
- const bm25 = this.#normalize(c.bm25Score || c.score || 0);
29
- const semantic = this.#normalize(c.semanticScore || 0);
30
- const quality = this.#computeQuality(c);
31
- const freshness = this.#computeFreshness(c);
32
- const popularity = this.#computePopularity(c);
29
+ return candidates
30
+ .map((c) => {
31
+ const bm25 = this.#normalize(c.bm25Score || c.score || 0);
32
+ const semantic = this.#normalize(c.semanticScore || 0);
33
+ const quality = this.#computeQuality(c);
34
+ const freshness = this.#computeFreshness(c);
35
+ const popularity = this.#computePopularity(c);
33
36
 
34
- const coarseScore =
35
- bm25 * this.#weights.bm25 +
36
- semantic * this.#weights.semantic +
37
- quality * this.#weights.quality +
38
- freshness * this.#weights.freshness +
39
- popularity * this.#weights.popularity;
37
+ const coarseScore =
38
+ bm25 * this.#weights.bm25 +
39
+ semantic * this.#weights.semantic +
40
+ quality * this.#weights.quality +
41
+ freshness * this.#weights.freshness +
42
+ popularity * this.#weights.popularity;
40
43
 
41
- return { ...c, coarseScore, coarseSignals: { bm25, semantic, quality, freshness, popularity } };
42
- }).sort((a, b) => b.coarseScore - a.coarseScore);
44
+ return {
45
+ ...c,
46
+ coarseScore,
47
+ coarseSignals: { bm25, semantic, quality, freshness, popularity },
48
+ };
49
+ })
50
+ .sort((a, b) => b.coarseScore - a.coarseScore);
43
51
  }
44
52
 
45
53
  /**
@@ -55,13 +63,13 @@ export class CoarseRanker {
55
63
  const hasTitle = !!candidate.title;
56
64
  const hasCode = !!(candidate.code || candidate.content);
57
65
  const hasDesc = !!(candidate.description || candidate.summary);
58
- score += (hasTitle ? 0.15 : 0) + (hasCode ? 0.15 : 0) + (hasDesc ? 0.10 : 0);
66
+ score += (hasTitle ? 0.15 : 0) + (hasCode ? 0.15 : 0) + (hasDesc ? 0.1 : 0);
59
67
 
60
68
  // 结构质量 (30%)
61
69
  const hasCat = !!candidate.category;
62
70
  const hasLang = !!candidate.language;
63
71
  const hasTags = Array.isArray(candidate.tags) && candidate.tags.length > 0;
64
- score += (hasCat ? 0.10 : 0) + (hasLang ? 0.10 : 0) + (hasTags ? 0.10 : 0);
72
+ score += (hasCat ? 0.1 : 0) + (hasLang ? 0.1 : 0) + (hasTags ? 0.1 : 0);
65
73
 
66
74
  // 代码可读性 (30%)
67
75
  const code = candidate.code || candidate.content || '';
@@ -75,14 +83,21 @@ export class CoarseRanker {
75
83
 
76
84
  #computeFreshness(candidate) {
77
85
  const updated = candidate.updatedAt || candidate.lastModified || candidate.createdAt;
78
- if (!updated) return 0.5;
86
+ if (!updated) {
87
+ return 0.5;
88
+ }
79
89
  // 自动识别秒级/毫秒级 Unix 时间戳 (秒级 ≤ 9999999999 即 2286 年)
80
- const ts = typeof updated === 'number' && updated > 0 && updated <= 9999999999
81
- ? updated * 1000
82
- : (typeof updated === 'number' ? updated : new Date(updated).getTime());
90
+ const ts =
91
+ typeof updated === 'number' && updated > 0 && updated <= 9999999999
92
+ ? updated * 1000
93
+ : typeof updated === 'number'
94
+ ? updated
95
+ : new Date(updated).getTime();
83
96
  const ageDays = (Date.now() - ts) / 86400000;
84
- if (ageDays < 0) return 1.0; // 未来时间戳视为最新
85
- return Math.exp(-0.693 * ageDays / 180); // 半衰期 180 天
97
+ if (ageDays < 0) {
98
+ return 1.0; // 未来时间戳视为最新
99
+ }
100
+ return Math.exp((-Math.LN2 * ageDays) / 180); // 半衰期 180 天
86
101
  }
87
102
 
88
103
  #computePopularity(candidate) {
@@ -15,11 +15,11 @@
15
15
  * - AI 不可用时自动降级到 Jaccard
16
16
  */
17
17
 
18
- import { tokenize } from './InvertedIndex.js';
19
18
  import { jaccardSimilarity } from '../../shared/similarity.js';
19
+ import { tokenize } from './InvertedIndex.js';
20
20
 
21
- const MAX_CANDIDATES = 40; // 超过此数量截断(控制 prompt 大小)
22
- const MAX_DOC_LEN = 300; // 每个文档最大字符数
21
+ const MAX_CANDIDATES = 40; // 超过此数量截断(控制 prompt 大小)
22
+ const MAX_DOC_LEN = 300; // 每个文档最大字符数
23
23
 
24
24
  export class CrossEncoderReranker {
25
25
  #aiProvider;
@@ -43,8 +43,12 @@ export class CrossEncoderReranker {
43
43
  * @returns {Promise<Array<object>>} — 附带 semanticScore 的候选列表(降序)
44
44
  */
45
45
  async rerank(query, candidates) {
46
- if (!candidates || candidates.length === 0) return [];
47
- if (!query) return candidates;
46
+ if (!candidates || candidates.length === 0) {
47
+ return [];
48
+ }
49
+ if (!query) {
50
+ return candidates;
51
+ }
48
52
 
49
53
  // 如果 AI Provider 不可用,降级到 Jaccard
50
54
  if (!this.#aiProvider || typeof this.#aiProvider.chatWithStructuredOutput !== 'function') {
@@ -58,16 +62,17 @@ export class CrossEncoderReranker {
58
62
  try {
59
63
  const scored = await this.#batchScore(query, head);
60
64
  // tail 部分给一个递减的低分以保持稳定排序
61
- const minScore = scored.length > 0
62
- ? Math.min(...scored.map(s => s.semanticScore)) * 0.5
63
- : 0;
65
+ const minScore =
66
+ scored.length > 0 ? Math.min(...scored.map((s) => s.semanticScore)) * 0.5 : 0;
64
67
  const tailScored = tail.map((c, i) => ({
65
68
  ...c,
66
69
  semanticScore: Math.max(minScore - (i + 1) * 0.001, 0),
67
70
  }));
68
71
  return [...scored, ...tailScored];
69
72
  } catch (err) {
70
- this.#logger.warn?.(`[CrossEncoderReranker] AI scoring failed, falling back to Jaccard: ${err.message}`);
73
+ this.#logger.warn?.(
74
+ `[CrossEncoderReranker] AI scoring failed, falling back to Jaccard: ${err.message}`
75
+ );
71
76
  return this.#jaccardFallback(query, candidates);
72
77
  }
73
78
  }
@@ -126,10 +131,12 @@ Return ONLY a JSON array, no markdown or explanation.`;
126
131
  }
127
132
 
128
133
  // 合并分数,未评分的给 0
129
- return candidates.map((c, i) => ({
130
- ...c,
131
- semanticScore: scoreMap.get(i) ?? 0,
132
- })).sort((a, b) => b.semanticScore - a.semanticScore);
134
+ return candidates
135
+ .map((c, i) => ({
136
+ ...c,
137
+ semanticScore: scoreMap.get(i) ?? 0,
138
+ }))
139
+ .sort((a, b) => b.semanticScore - a.semanticScore);
133
140
  }
134
141
 
135
142
  /**
@@ -151,13 +158,17 @@ Return ONLY a JSON array, no markdown or explanation.`;
151
158
  */
152
159
  #jaccardFallback(query, candidates) {
153
160
  const queryTokens = new Set(tokenize(query));
154
- if (queryTokens.size === 0) return candidates;
155
-
156
- return candidates.map(candidate => {
157
- const text = this.#extractDocText(candidate);
158
- const docTokens = new Set(tokenize(text));
159
- const score = jaccardSimilarity(queryTokens, docTokens);
160
- return { ...candidate, semanticScore: score };
161
- }).sort((a, b) => b.semanticScore - a.semanticScore);
161
+ if (queryTokens.size === 0) {
162
+ return candidates;
163
+ }
164
+
165
+ return candidates
166
+ .map((candidate) => {
167
+ const text = this.#extractDocText(candidate);
168
+ const docTokens = new Set(tokenize(text));
169
+ const score = jaccardSimilarity(queryTokens, docTokens);
170
+ return { ...candidate, semanticScore: score };
171
+ })
172
+ .sort((a, b) => b.semanticScore - a.semanticScore);
162
173
  }
163
174
  }
@@ -18,11 +18,15 @@ export function buildInvertedIndex(documents) {
18
18
 
19
19
  for (let i = 0; i < documents.length; i++) {
20
20
  const doc = documents[i];
21
- const text = [doc.title, doc.trigger, doc.content, doc.code, doc.description].filter(Boolean).join(' ');
21
+ const text = [doc.title, doc.trigger, doc.content, doc.code, doc.description]
22
+ .filter(Boolean)
23
+ .join(' ');
22
24
  const tokens = tokenize(text);
23
25
 
24
26
  for (const token of tokens) {
25
- if (!index.has(token)) index.set(token, new Set());
27
+ if (!index.has(token)) {
28
+ index.set(token, new Set());
29
+ }
26
30
  index.get(token).add(i);
27
31
  }
28
32
  }
@@ -38,13 +42,17 @@ export function buildInvertedIndex(documents) {
38
42
  */
39
43
  export function lookup(invertedIndex, query) {
40
44
  const queryTokens = tokenize(query);
41
- if (queryTokens.length === 0) return [];
45
+ if (queryTokens.length === 0) {
46
+ return [];
47
+ }
42
48
 
43
49
  const resultSet = new Set();
44
50
  for (const token of queryTokens) {
45
51
  const docs = invertedIndex.get(token);
46
52
  if (docs) {
47
- for (const docIdx of docs) resultSet.add(docIdx);
53
+ for (const docIdx of docs) {
54
+ resultSet.add(docIdx);
55
+ }
48
56
  }
49
57
  }
50
58
 
@@ -59,17 +67,23 @@ export function lookup(invertedIndex, query) {
59
67
  */
60
68
  export function lookupAll(invertedIndex, query) {
61
69
  const queryTokens = tokenize(query);
62
- if (queryTokens.length === 0) return [];
70
+ if (queryTokens.length === 0) {
71
+ return [];
72
+ }
63
73
 
64
74
  let result = null;
65
75
  for (const token of queryTokens) {
66
76
  const docs = invertedIndex.get(token);
67
- if (!docs) return [];
77
+ if (!docs) {
78
+ return [];
79
+ }
68
80
  if (result === null) {
69
81
  result = new Set(docs);
70
82
  } else {
71
83
  for (const idx of result) {
72
- if (!docs.has(idx)) result.delete(idx);
84
+ if (!docs.has(idx)) {
85
+ result.delete(idx);
86
+ }
73
87
  }
74
88
  }
75
89
  }