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
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * SearchEngine - 统一搜索引擎
3
- *
3
+ *
4
4
  * 三级搜索策略: keyword → BM25 ranking → semantic(可选)
5
5
  * 从 V1 SearchServiceV2 迁移,适配 V2 架构
6
6
  */
@@ -21,13 +21,15 @@ const BM25_B = 0.75;
21
21
  * 中文: 单字 + 二元组(bigram)— 无需分词词典即可支持子串匹配
22
22
  */
23
23
  export function tokenize(text) {
24
- if (!text) return [];
24
+ if (!text) {
25
+ return [];
26
+ }
25
27
  // 先拆 camelCase/PascalCase(必须在 toLowerCase 之前,否则大小写边界丢失)
26
28
  let expanded = text.replace(/([a-z])([A-Z])/g, '$1 $2');
27
29
  // 拆全大写前缀:URLSession → URL Session, UITableView → UI Table View
28
30
  expanded = expanded.replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2');
29
31
  const normalized = expanded.toLowerCase().replace(/[^\p{L}\p{N}\s_-]/gu, ' ');
30
- const rawTokens = normalized.split(/[\s_-]+/).filter(t => t.length >= 1);
32
+ const rawTokens = normalized.split(/[\s_-]+/).filter((t) => t.length >= 1);
31
33
 
32
34
  const tokens = [];
33
35
  // CJK 正则(中日韩统一表意文字 + 扩展区)
@@ -39,17 +41,25 @@ export function tokenize(text) {
39
41
  const cjkChars = raw.match(/[\u4e00-\u9fff\u3400-\u4dbf\uf900-\ufaff]+/g) || [];
40
42
  for (const seg of cjkChars) {
41
43
  // 单字
42
- for (const ch of seg) tokens.push(ch);
44
+ for (const ch of seg) {
45
+ tokens.push(ch);
46
+ }
43
47
  // bigram
44
- for (let i = 0; i < seg.length - 1; i++) tokens.push(seg[i] + seg[i + 1]);
48
+ for (let i = 0; i < seg.length - 1; i++) {
49
+ tokens.push(seg[i] + seg[i + 1]);
50
+ }
45
51
  // 完整片段(≥3 字时额外保留,提升精确匹配权重)
46
- if (seg.length >= 3) tokens.push(seg);
52
+ if (seg.length >= 3) {
53
+ tokens.push(seg);
54
+ }
47
55
  }
48
56
  // 非 CJK 部分(英文/数字)也保留
49
57
  const nonCjk = raw.replace(/[\u4e00-\u9fff\u3400-\u4dbf\uf900-\ufaff]+/g, ' ').trim();
50
58
  if (nonCjk) {
51
59
  for (const t of nonCjk.split(/\s+/)) {
52
- if (t.length >= 2) tokens.push(t);
60
+ if (t.length >= 2) {
61
+ tokens.push(t);
62
+ }
53
63
  }
54
64
  }
55
65
  } else if (raw.length >= 2) {
@@ -64,9 +74,9 @@ export function tokenize(text) {
64
74
  */
65
75
  export class BM25Scorer {
66
76
  constructor() {
67
- this.documents = []; // [{id, tokens, tokenFreq, length, meta}]
77
+ this.documents = []; // [{id, tokens, tokenFreq, length, meta}]
68
78
  this.avgLength = 0;
69
- this.docFreq = {}; // token → 出现在多少文档中
79
+ this.docFreq = {}; // token → 出现在多少文档中
70
80
  this.totalDocs = 0;
71
81
  this._totalLength = 0; // 累计文档长度,避免 O(N) 重算
72
82
  }
@@ -95,7 +105,9 @@ export class BM25Scorer {
95
105
  */
96
106
  search(query, limit = 20) {
97
107
  const queryTokens = tokenize(query);
98
- if (queryTokens.length === 0) return [];
108
+ if (queryTokens.length === 0) {
109
+ return [];
110
+ }
99
111
 
100
112
  const scores = [];
101
113
 
@@ -104,12 +116,15 @@ export class BM25Scorer {
104
116
  const dl = doc.length;
105
117
 
106
118
  for (const qt of queryTokens) {
107
- const tf = doc.tokenFreq[qt] || 0; // O(1) 查找,替代 O(T) filter
108
- if (tf === 0) continue;
119
+ const tf = doc.tokenFreq[qt] || 0; // O(1) 查找,替代 O(T) filter
120
+ if (tf === 0) {
121
+ continue;
122
+ }
109
123
 
110
124
  const df = this.docFreq[qt] || 0;
111
125
  const idf = Math.log((this.totalDocs - df + 0.5) / (df + 0.5) + 1);
112
- const tfNorm = (tf * (BM25_K1 + 1)) / (tf + BM25_K1 * (1 - BM25_B + BM25_B * (dl / this.avgLength)));
126
+ const tfNorm =
127
+ (tf * (BM25_K1 + 1)) / (tf + BM25_K1 * (1 - BM25_B + BM25_B * (dl / this.avgLength)));
113
128
  score += idf * tfNorm;
114
129
  }
115
130
 
@@ -163,28 +178,41 @@ export class SearchEngine {
163
178
  let entries = [];
164
179
 
165
180
  try {
166
- entries = this.db.prepare(
167
- `SELECT id, title, description, language, category, knowledgeType, kind,
181
+ entries = this.db
182
+ .prepare(
183
+ `SELECT id, title, description, language, category, knowledgeType, kind,
168
184
  content, lifecycle, tags, trigger, difficulty, quality, stats,
169
185
  updatedAt, createdAt
170
186
  FROM knowledge_entries WHERE lifecycle != 'deprecated'`
171
- ).all();
172
- entries = entries.map(e => ({
187
+ )
188
+ .all();
189
+ entries = entries.map((e) => ({
173
190
  ...e,
174
191
  status: e.lifecycle,
175
192
  }));
176
- } catch { /* table may not exist */ }
193
+ } catch {
194
+ /* table may not exist */
195
+ }
177
196
 
178
197
  for (const r of entries) {
179
198
  let contentText = '';
180
199
  try {
181
200
  const content = JSON.parse(r.content || '{}');
182
- contentText = [content.pattern, content.rationale, content.markdown].filter(Boolean).join(' ');
183
- } catch { /* ignore parse error */ }
201
+ contentText = [content.pattern, content.rationale, content.markdown]
202
+ .filter(Boolean)
203
+ .join(' ');
204
+ } catch {
205
+ /* ignore parse error */
206
+ }
184
207
  // 包含 tags + trigger 提升召回率
185
208
  let tagText = '';
186
209
  let parsedTags = [];
187
- try { parsedTags = JSON.parse(r.tags || '[]'); tagText = parsedTags.join(' '); } catch { /* ignore */ }
210
+ try {
211
+ parsedTags = JSON.parse(r.tags || '[]');
212
+ tagText = parsedTags.join(' ');
213
+ } catch {
214
+ /* ignore */
215
+ }
188
216
  // 解析 stats / quality JSON — 供排序信号使用
189
217
  let usageCount = 0;
190
218
  let authorityScore = 0;
@@ -192,18 +220,43 @@ export class SearchEngine {
192
220
  const stats = JSON.parse(r.stats || '{}');
193
221
  usageCount = (stats.adoptions || 0) + (stats.applications || 0) + (stats.searchHits || 0);
194
222
  authorityScore = stats.authority || 0;
195
- } catch { /* ignore */ }
223
+ } catch {
224
+ /* ignore */
225
+ }
196
226
  let qualityOverall = 0;
197
- try { qualityOverall = JSON.parse(r.quality || '{}').overall || 0; } catch { /* ignore */ }
198
- const text = [r.title, r.description, r.trigger, r.language, r.category, r.knowledgeType, tagText, contentText]
199
- .filter(Boolean).join(' ');
227
+ try {
228
+ qualityOverall = JSON.parse(r.quality || '{}').overall || 0;
229
+ } catch {
230
+ /* ignore */
231
+ }
232
+ const text = [
233
+ r.title,
234
+ r.description,
235
+ r.trigger,
236
+ r.language,
237
+ r.category,
238
+ r.knowledgeType,
239
+ tagText,
240
+ contentText,
241
+ ]
242
+ .filter(Boolean)
243
+ .join(' ');
200
244
  this.scorer.addDocument(r.id, text, {
201
- type: 'knowledge', title: r.title, trigger: r.trigger || '', status: r.status,
202
- knowledgeType: r.knowledgeType, kind: r.kind || 'pattern',
203
- language: r.language || '', category: r.category || '',
204
- updatedAt: r.updatedAt || null, createdAt: r.createdAt || null,
205
- difficulty: r.difficulty || 'intermediate', tags: parsedTags,
206
- usageCount, authorityScore, qualityScore: qualityOverall,
245
+ type: 'knowledge',
246
+ title: r.title,
247
+ trigger: r.trigger || '',
248
+ status: r.status,
249
+ knowledgeType: r.knowledgeType,
250
+ kind: r.kind || 'pattern',
251
+ language: r.language || '',
252
+ category: r.category || '',
253
+ updatedAt: r.updatedAt || null,
254
+ createdAt: r.createdAt || null,
255
+ difficulty: r.difficulty || 'intermediate',
256
+ tags: parsedTags,
257
+ usageCount,
258
+ authorityScore,
259
+ qualityScore: qualityOverall,
207
260
  });
208
261
  }
209
262
 
@@ -233,7 +286,7 @@ export class SearchEngine {
233
286
  */
234
287
  async search(query, options = {}) {
235
288
  const { type = 'all', limit = 20, mode = 'keyword', context } = options;
236
- const shouldRank = options.rank ?? (mode !== 'keyword');
289
+ const shouldRank = options.rank ?? mode !== 'keyword';
237
290
 
238
291
  if (!query || !query.trim()) {
239
292
  return { items: [], total: 0, query };
@@ -246,7 +299,9 @@ export class SearchEngine {
246
299
  : `${query}:${type}:${limit}:${mode}:${shouldRank ? 'r' : ''}:${options.groupByKind ? 'g' : ''}`;
247
300
  if (cacheKey) {
248
301
  const cached = this._getCache(cacheKey);
249
- if (cached) return cached;
302
+ if (cached) {
303
+ return cached;
304
+ }
250
305
  }
251
306
 
252
307
  // 确保索引已构建
@@ -262,7 +317,10 @@ export class SearchEngine {
262
317
  // 同时做 BM25 + semantic,融合去重取最优分数
263
318
  const [bm25Items, semResult] = await Promise.all([
264
319
  Promise.resolve(this._bm25Search(query, type, recallLimit)),
265
- this._semanticSearch(query, type, recallLimit).catch(() => ({ items: [], actualMode: 'bm25' })),
320
+ this._semanticSearch(query, type, recallLimit).catch(() => ({
321
+ items: [],
322
+ actualMode: 'bm25',
323
+ })),
266
324
  ]);
267
325
  const semItems = semResult.items || [];
268
326
  const merged = new Map();
@@ -279,7 +337,10 @@ export class SearchEngine {
279
337
  }
280
338
  }
281
339
  results = [...merged.values()].sort((a, b) => b.score - a.score);
282
- for (const it of results) { delete it._bm25; delete it._sem; }
340
+ for (const it of results) {
341
+ delete it._bm25;
342
+ delete it._sem;
343
+ }
283
344
  const semActuallyUsed = semResult.actualMode === 'semantic';
284
345
  actualMode = semActuallyUsed ? 'auto(bm25+semantic)' : 'auto(bm25-only)';
285
346
  break;
@@ -294,7 +355,6 @@ export class SearchEngine {
294
355
  actualMode = semResult.actualMode || 'semantic';
295
356
  break;
296
357
  }
297
- case 'keyword':
298
358
  default:
299
359
  results = this._keywordSearch(query, type, limit);
300
360
  break;
@@ -323,7 +383,9 @@ export class SearchEngine {
323
383
  }
324
384
  }
325
385
 
326
- if (cacheKey) this._setCache(cacheKey, response);
386
+ if (cacheKey) {
387
+ this._setCache(cacheKey, response);
388
+ }
327
389
  return response;
328
390
  }
329
391
 
@@ -336,13 +398,14 @@ export class SearchEngine {
336
398
  const normalized = this._normalizeForRanking(items);
337
399
  let ranked = this._coarseRanker.rank(normalized);
338
400
  ranked = this._multiSignalRanker.rank(ranked, {
339
- ...context, query,
401
+ ...context,
402
+ query,
340
403
  scenario: context?.intent || 'search',
341
404
  });
342
405
  if (context?.sessionHistory?.length > 0) {
343
406
  ranked = this._contextBoost(ranked, context);
344
407
  }
345
- return ranked.map(r => ({
408
+ return ranked.map((r) => ({
346
409
  ...r,
347
410
  recallScore: r.bm25Score || 0,
348
411
  score: r.contextScore || r.rankerScore || r.coarseScore || r.bm25Score || 0,
@@ -354,17 +417,23 @@ export class SearchEngine {
354
417
  * 保留原始 content 供下游消费者使用
355
418
  */
356
419
  _normalizeForRanking(items) {
357
- return items.map(item => {
420
+ return items.map((item) => {
358
421
  let codeText = '';
359
422
  if (item.content) {
360
423
  try {
361
424
  const parsed = typeof item.content === 'string' ? JSON.parse(item.content) : item.content;
362
425
  codeText = parsed.pattern || parsed.code || '';
363
- } catch { /* ignore */ }
426
+ } catch {
427
+ /* ignore */
428
+ }
364
429
  }
365
430
  let tags = item.tags || [];
366
431
  if (typeof tags === 'string') {
367
- try { tags = JSON.parse(tags); } catch { tags = []; }
432
+ try {
433
+ tags = JSON.parse(tags);
434
+ } catch {
435
+ tags = [];
436
+ }
368
437
  }
369
438
  return {
370
439
  ...item,
@@ -384,24 +453,34 @@ export class SearchEngine {
384
453
  */
385
454
  _contextBoost(items, context) {
386
455
  const { sessionHistory = [], language } = context || {};
387
- if (!sessionHistory.length) return items;
456
+ if (!sessionHistory.length) {
457
+ return items;
458
+ }
388
459
  const sessionKeywords = new Set();
389
460
  for (const turn of sessionHistory) {
390
461
  const tokens = tokenize(turn.content || turn.rawInput || '');
391
- for (const t of tokens) sessionKeywords.add(t);
462
+ for (const t of tokens) {
463
+ sessionKeywords.add(t);
464
+ }
392
465
  }
393
- return items.map(item => {
394
- let boost = 0;
395
- const textTokens = tokenize(
396
- [item.title, item.trigger, item.content].filter(Boolean).join(' ')
397
- );
398
- const overlap = textTokens.filter(t => sessionKeywords.has(t)).length;
399
- if (overlap > 0) boost += 0.2 * Math.min(overlap / 5, 1);
400
- if (language && item.language === language) boost += 0.1;
401
- const baseScore = item.rankerScore || item.coarseScore || item.score || 0;
402
- const contextScore = baseScore * (1 + boost);
403
- return { ...item, contextScore, contextBoost: boost };
404
- }).sort((a, b) => b.contextScore - a.contextScore);
466
+ return items
467
+ .map((item) => {
468
+ let boost = 0;
469
+ const textTokens = tokenize(
470
+ [item.title, item.trigger, item.content].filter(Boolean).join(' ')
471
+ );
472
+ const overlap = textTokens.filter((t) => sessionKeywords.has(t)).length;
473
+ if (overlap > 0) {
474
+ boost += 0.2 * Math.min(overlap / 5, 1);
475
+ }
476
+ if (language && item.language === language) {
477
+ boost += 0.1;
478
+ }
479
+ const baseScore = item.rankerScore || item.coarseScore || item.score || 0;
480
+ const contextScore = baseScore * (1 + boost);
481
+ return { ...item, contextScore, contextBoost: boost };
482
+ })
483
+ .sort((a, b) => b.contextScore - a.contextScore);
405
484
  }
406
485
 
407
486
  /**
@@ -411,31 +490,54 @@ export class SearchEngine {
411
490
  _keywordSearch(query, type, limit) {
412
491
  const results = [];
413
492
  // 转义 LIKE 通配符 (% → \%, _ → \_)
414
- const escaped = query.replace(/[%_\\]/g, ch => `\\${ch}`);
493
+ const escaped = query.replace(/[%_\\]/g, (ch) => `\\${ch}`);
415
494
  const pattern = `%${escaped}%`;
416
495
 
417
- if (type === 'all' || type === 'recipe' || type === 'knowledge' || type === 'rule' || type === 'solution') {
496
+ if (
497
+ type === 'all' ||
498
+ type === 'recipe' ||
499
+ type === 'knowledge' ||
500
+ type === 'rule' ||
501
+ type === 'solution'
502
+ ) {
418
503
  try {
419
504
  let rows = [];
420
505
  try {
421
- rows = this.db.prepare(
422
- `SELECT id, title, description, language, category, knowledgeType, kind, lifecycle as status, content, trigger, headers, moduleName, 'knowledge' as type
506
+ rows = this.db
507
+ .prepare(
508
+ `SELECT id, title, description, language, category, knowledgeType, kind, lifecycle as status, content, trigger, headers, moduleName, 'knowledge' as type
423
509
  FROM knowledge_entries
424
510
  WHERE lifecycle != 'deprecated' AND (title LIKE ? ESCAPE '\\' OR description LIKE ? ESCAPE '\\' OR trigger LIKE ? ESCAPE '\\' OR content LIKE ? ESCAPE '\\')
425
511
  LIMIT ?`
426
- ).all(pattern, pattern, pattern, pattern, limit);
427
- } catch { /* table may not exist */ }
512
+ )
513
+ .all(pattern, pattern, pattern, pattern, limit);
514
+ } catch {
515
+ /* table may not exist */
516
+ }
428
517
  // 基础相关性排序:trigger 精确 > 标题匹配 > 描述匹配 > 内容匹配
429
518
  const lowerQ = query.toLowerCase();
430
- results.push(...rows.map(r => {
431
- let score = 0.5;
432
- if (r.trigger && r.trigger.toLowerCase().includes(lowerQ)) score = 1.2;
433
- else if (r.title && r.title.toLowerCase().includes(lowerQ)) score = 1.0;
434
- else if (r.description && r.description.toLowerCase().includes(lowerQ)) score = 0.8;
435
- return { ...r, trigger: r.trigger || '', kind: r.kind || 'pattern', score: Math.round(score * 1000) / 1000 };
436
- }));
519
+ results.push(
520
+ ...rows.map((r) => {
521
+ let score = 0.5;
522
+ if (r.trigger?.toLowerCase().includes(lowerQ)) {
523
+ score = 1.2;
524
+ } else if (r.title?.toLowerCase().includes(lowerQ)) {
525
+ score = 1.0;
526
+ } else if (r.description?.toLowerCase().includes(lowerQ)) {
527
+ score = 0.8;
528
+ }
529
+ return {
530
+ ...r,
531
+ trigger: r.trigger || '',
532
+ kind: r.kind || 'pattern',
533
+ score: Math.round(score * 1000) / 1000,
534
+ };
535
+ })
536
+ );
437
537
  results.sort((a, b) => b.score - a.score);
438
- } catch { /* table may not exist */ }
538
+ } catch {
539
+ /* table may not exist */
540
+ }
439
541
  }
440
542
 
441
543
  return results.slice(0, limit);
@@ -449,13 +551,15 @@ export class SearchEngine {
449
551
 
450
552
  if (type !== 'all') {
451
553
  // All types now map to 'recipe' since everything is unified
452
- results = results.filter(r => {
453
- if (type === 'rule') return r.meta.knowledgeType === 'boundary-constraint';
554
+ results = results.filter((r) => {
555
+ if (type === 'rule') {
556
+ return r.meta.knowledgeType === 'boundary-constraint';
557
+ }
454
558
  return r.meta.type === 'recipe';
455
559
  });
456
560
  }
457
561
 
458
- const items = results.slice(0, limit).map(r => ({
562
+ const items = results.slice(0, limit).map((r) => ({
459
563
  id: r.id,
460
564
  title: r.meta.title,
461
565
  trigger: r.meta.trigger || '',
@@ -503,16 +607,21 @@ export class SearchEngine {
503
607
  try {
504
608
  let vectorResults;
505
609
  if (typeof this.vectorStore.hybridSearch === 'function') {
506
- const hybrid = await this.vectorStore.hybridSearch(queryEmbedding, query, { topK: limit * 2 });
507
- vectorResults = hybrid.map(r => ({
508
- id: r.item.id, similarity: r.score, score: r.score,
509
- content: r.item.content, metadata: r.item.metadata || {},
610
+ const hybrid = await this.vectorStore.hybridSearch(queryEmbedding, query, {
611
+ topK: limit * 2,
612
+ });
613
+ vectorResults = hybrid.map((r) => ({
614
+ id: r.item.id,
615
+ similarity: r.score,
616
+ score: r.score,
617
+ content: r.item.content,
618
+ metadata: r.item.metadata || {},
510
619
  }));
511
620
  } else {
512
621
  vectorResults = await this.vectorStore.query(queryEmbedding, limit * 2);
513
622
  }
514
623
  if (vectorResults && vectorResults.length > 0) {
515
- let results = vectorResults.map(vr => ({
624
+ let results = vectorResults.map((vr) => ({
516
625
  id: vr.id,
517
626
  title: vr.metadata?.title || vr.id,
518
627
  type: 'recipe',
@@ -521,8 +630,10 @@ export class SearchEngine {
521
630
  score: Math.round((vr.similarity || vr.score || 0) * 1000) / 1000,
522
631
  }));
523
632
  if (type !== 'all') {
524
- results = results.filter(r => {
525
- if (type === 'rule') return r.kind === 'rule';
633
+ results = results.filter((r) => {
634
+ if (type === 'rule') {
635
+ return r.kind === 'rule';
636
+ }
526
637
  return r.type === 'recipe';
527
638
  });
528
639
  }
@@ -532,7 +643,9 @@ export class SearchEngine {
532
643
  return { items: results, actualMode: 'semantic' };
533
644
  }
534
645
  } catch (vecErr) {
535
- this.logger.warn('Vector store query failed, falling back to BM25', { error: vecErr.message });
646
+ this.logger.warn('Vector store query failed, falling back to BM25', {
647
+ error: vecErr.message,
648
+ });
536
649
  }
537
650
  }
538
651
 
@@ -550,52 +663,87 @@ export class SearchEngine {
550
663
  * 用于向量搜索结果与 BM25 结果的一致性
551
664
  */
552
665
  _supplementDetails(items) {
553
- if (!items || items.length === 0) return;
666
+ if (!items || items.length === 0) {
667
+ return;
668
+ }
554
669
  try {
555
- const ids = items.map(it => it.id);
670
+ const ids = items.map((it) => it.id);
556
671
  const placeholders = ids.map(() => '?').join(',');
557
672
  let rows = [];
558
673
  try {
559
- rows = this.db.prepare(
560
- `SELECT id, content, description, trigger, headers, moduleName,
674
+ rows = this.db
675
+ .prepare(
676
+ `SELECT id, content, description, trigger, headers, moduleName,
561
677
  tags, language, category, updatedAt, createdAt, quality, stats, difficulty
562
678
  FROM knowledge_entries WHERE id IN (${placeholders})`
563
- ).all(...ids);
564
- } catch { /* table may not exist */ }
565
- const rowMap = new Map(rows.map(r => [r.id, r]));
679
+ )
680
+ .all(...ids);
681
+ } catch {
682
+ /* table may not exist */
683
+ }
684
+ const rowMap = new Map(rows.map((r) => [r.id, r]));
566
685
  for (const item of items) {
567
686
  const row = rowMap.get(item.id);
568
687
  if (row) {
569
688
  item.content = item.content || row.content || null;
570
689
  item.description = item.description || row.description || '';
571
690
  item.trigger = item.trigger || row.trigger || '';
572
- if (row.headers) item.headers = row.headers;
573
- if (row.moduleName) item.moduleName = row.moduleName;
691
+ if (row.headers) {
692
+ item.headers = row.headers;
693
+ }
694
+ if (row.moduleName) {
695
+ item.moduleName = row.moduleName;
696
+ }
574
697
  // 排序信号补充 — 确保 Funnel/Ranker 有真实数据
575
- if (!item.language && row.language) item.language = row.language;
576
- if (!item.category && row.category) item.category = row.category;
577
- if (!item.updatedAt && row.updatedAt) item.updatedAt = row.updatedAt;
578
- if (!item.createdAt && row.createdAt) item.createdAt = row.createdAt;
579
- if (!item.difficulty && row.difficulty) item.difficulty = row.difficulty;
698
+ if (!item.language && row.language) {
699
+ item.language = row.language;
700
+ }
701
+ if (!item.category && row.category) {
702
+ item.category = row.category;
703
+ }
704
+ if (!item.updatedAt && row.updatedAt) {
705
+ item.updatedAt = row.updatedAt;
706
+ }
707
+ if (!item.createdAt && row.createdAt) {
708
+ item.createdAt = row.createdAt;
709
+ }
710
+ if (!item.difficulty && row.difficulty) {
711
+ item.difficulty = row.difficulty;
712
+ }
580
713
  // 解析 tags
581
714
  if (!item.tags || (Array.isArray(item.tags) && item.tags.length === 0)) {
582
- try { item.tags = JSON.parse(row.tags || '[]'); } catch { /* ignore */ }
715
+ try {
716
+ item.tags = JSON.parse(row.tags || '[]');
717
+ } catch {
718
+ /* ignore */
719
+ }
583
720
  }
584
721
  // 解析 quality JSON → qualityScore
585
722
  if (!item.qualityScore) {
586
- try { item.qualityScore = JSON.parse(row.quality || '{}').overall || 0; } catch { /* ignore */ }
723
+ try {
724
+ item.qualityScore = JSON.parse(row.quality || '{}').overall || 0;
725
+ } catch {
726
+ /* ignore */
727
+ }
587
728
  }
588
729
  // 解析 stats JSON → usageCount + authorityScore
589
730
  if (!item.usageCount) {
590
731
  try {
591
732
  const stats = JSON.parse(row.stats || '{}');
592
- item.usageCount = (stats.adoptions || 0) + (stats.applications || 0) + (stats.searchHits || 0);
593
- if (!item.authorityScore) item.authorityScore = stats.authority || 0;
594
- } catch { /* ignore */ }
733
+ item.usageCount =
734
+ (stats.adoptions || 0) + (stats.applications || 0) + (stats.searchHits || 0);
735
+ if (!item.authorityScore) {
736
+ item.authorityScore = stats.authority || 0;
737
+ }
738
+ } catch {
739
+ /* ignore */
740
+ }
595
741
  }
596
742
  }
597
743
  }
598
- } catch { /* DB may not be available */ }
744
+ } catch {
745
+ /* DB may not be available */
746
+ }
599
747
  }
600
748
 
601
749
  /**
@@ -623,7 +771,9 @@ export class SearchEngine {
623
771
 
624
772
  _getCache(key) {
625
773
  const entry = this._cache.get(key);
626
- if (!entry) return null;
774
+ if (!entry) {
775
+ return null;
776
+ }
627
777
  if (Date.now() - entry.time > this._cacheMaxAge) {
628
778
  this._cache.delete(key);
629
779
  return null;
@@ -641,7 +791,9 @@ export class SearchEngine {
641
791
  const keys = this._cache.keys();
642
792
  for (let i = 0; i < toDelete; i++) {
643
793
  const k = keys.next().value;
644
- if (k !== undefined) this._cache.delete(k);
794
+ if (k !== undefined) {
795
+ this._cache.delete(k);
796
+ }
645
797
  }
646
798
  }
647
799
  this._cache.set(key, { data, time: Date.now() });