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.
- package/README.md +230 -324
- package/bin/api-server.js +1 -1
- package/bin/cli.js +204 -244
- package/bin/mcp-server.js +5 -3
- package/config/knowledge-base.config.js +132 -132
- package/dashboard/dist/assets/{icons-CEfgGaZi.js → icons-Cdq22n2i.js} +95 -100
- package/dashboard/dist/assets/index-ClkyPkDX.js +133 -0
- package/dashboard/dist/assets/index-t4QrJwv1.css +1 -0
- package/dashboard/dist/index.html +3 -3
- package/lib/bootstrap.js +8 -8
- package/lib/cli/AiScanService.js +86 -40
- package/lib/cli/KnowledgeSyncService.js +113 -74
- package/lib/cli/SetupService.js +439 -277
- package/lib/cli/UpgradeService.js +63 -100
- package/lib/core/AstAnalyzer.js +276 -597
- package/lib/core/ast/ProjectGraph.js +101 -40
- package/lib/core/ast/ensure-grammars.js +232 -0
- package/lib/core/ast/index.js +115 -0
- package/lib/core/ast/lang-dart.js +661 -0
- package/lib/core/ast/lang-go.js +530 -0
- package/lib/core/ast/lang-java.js +435 -0
- package/lib/core/ast/lang-javascript.js +272 -0
- package/lib/core/ast/lang-kotlin.js +423 -0
- package/lib/core/ast/lang-objc.js +388 -0
- package/lib/core/ast/lang-python.js +371 -0
- package/lib/core/ast/lang-swift.js +337 -0
- package/lib/core/ast/lang-typescript.js +503 -0
- package/lib/core/capability/CapabilityProbe.js +18 -9
- package/lib/core/constitution/Constitution.js +2 -3
- package/lib/core/constitution/ConstitutionValidator.js +65 -24
- package/lib/core/discovery/DartDiscoverer.js +534 -0
- package/lib/core/discovery/DiscovererRegistry.js +83 -0
- package/lib/core/discovery/GenericDiscoverer.js +225 -0
- package/lib/core/discovery/GoDiscoverer.js +541 -0
- package/lib/core/discovery/JvmDiscoverer.js +506 -0
- package/lib/core/discovery/NodeDiscoverer.js +466 -0
- package/lib/core/discovery/ProjectDiscoverer.js +93 -0
- package/lib/core/discovery/PythonDiscoverer.js +338 -0
- package/lib/core/discovery/SpmDiscoverer.js +5 -0
- package/lib/core/discovery/index.js +53 -0
- package/lib/core/enhancement/EnhancementPack.js +71 -0
- package/lib/core/enhancement/EnhancementRegistry.js +47 -0
- package/lib/core/enhancement/android-enhancement.js +102 -0
- package/lib/core/enhancement/django-enhancement.js +70 -0
- package/lib/core/enhancement/fastapi-enhancement.js +63 -0
- package/lib/core/enhancement/go-grpc-enhancement.js +152 -0
- package/lib/core/enhancement/go-web-enhancement.js +201 -0
- package/lib/core/enhancement/index.js +65 -0
- package/lib/core/enhancement/node-server-enhancement.js +88 -0
- package/lib/core/enhancement/react-enhancement.js +86 -0
- package/lib/core/enhancement/spring-enhancement.js +112 -0
- package/lib/core/enhancement/vue-enhancement.js +96 -0
- package/lib/core/gateway/Gateway.js +8 -9
- package/lib/core/gateway/GatewayActionRegistry.js +1 -1
- package/lib/core/permission/PermissionManager.js +12 -8
- package/lib/domain/index.js +13 -9
- package/lib/domain/knowledge/KnowledgeEntry.js +111 -101
- package/lib/domain/knowledge/KnowledgeRepository.js +0 -1
- package/lib/domain/knowledge/Lifecycle.js +22 -22
- package/lib/domain/knowledge/index.js +9 -12
- package/lib/domain/knowledge/values/Constraints.js +31 -21
- package/lib/domain/knowledge/values/Content.js +21 -13
- package/lib/domain/knowledge/values/Quality.js +31 -18
- package/lib/domain/knowledge/values/Reasoning.js +20 -12
- package/lib/domain/knowledge/values/Relations.js +37 -25
- package/lib/domain/knowledge/values/Stats.js +18 -12
- package/lib/domain/knowledge/values/index.js +4 -3
- package/lib/domain/snippet/Snippet.js +35 -10
- package/lib/external/ai/AiFactory.js +48 -16
- package/lib/external/ai/AiProvider.js +184 -90
- package/lib/external/ai/providers/ClaudeProvider.js +25 -12
- package/lib/external/ai/providers/GoogleGeminiProvider.js +59 -30
- package/lib/external/ai/providers/MockProvider.js +9 -3
- package/lib/external/ai/providers/OpenAiProvider.js +51 -29
- package/lib/external/mcp/McpServer.js +66 -36
- package/lib/external/mcp/errorHandler.js +23 -11
- package/lib/external/mcp/handlers/LanguageExtensions.js +138 -53
- package/lib/external/mcp/handlers/TargetClassifier.js +52 -16
- package/lib/external/mcp/handlers/bootstrap/pipeline/BootstrapSnapshot.js +81 -20
- package/lib/external/mcp/handlers/bootstrap/pipeline/EpisodicMemory.js +71 -42
- package/lib/external/mcp/handlers/bootstrap/pipeline/IncrementalBootstrap.js +9 -17
- package/lib/external/mcp/handlers/bootstrap/pipeline/ToolResultCache.js +14 -9
- package/lib/external/mcp/handlers/bootstrap/pipeline/dimension-context.js +15 -7
- package/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +352 -153
- package/lib/external/mcp/handlers/bootstrap/pipeline/tier-scheduler.js +52 -12
- package/lib/external/mcp/handlers/bootstrap/skills.js +143 -39
- package/lib/external/mcp/handlers/bootstrap.js +691 -168
- package/lib/external/mcp/handlers/browse.js +66 -22
- package/lib/external/mcp/handlers/candidate.js +118 -35
- package/lib/external/mcp/handlers/consolidated.js +49 -17
- package/lib/external/mcp/handlers/guard.js +104 -39
- package/lib/external/mcp/handlers/knowledge.js +60 -36
- package/lib/external/mcp/handlers/search.js +43 -14
- package/lib/external/mcp/handlers/skill.js +120 -45
- package/lib/external/mcp/handlers/structure.js +240 -86
- package/lib/external/mcp/handlers/system.js +42 -12
- package/lib/external/mcp/handlers/wiki.js +58 -33
- package/lib/external/mcp/tools.js +306 -123
- package/lib/http/HttpServer.js +72 -47
- package/lib/http/middleware/RateLimiter.js +5 -3
- package/lib/http/middleware/errorHandler.js +6 -1
- package/lib/http/middleware/requestLogger.js +14 -3
- package/lib/http/middleware/roleResolver.js +30 -23
- package/lib/http/routes/ai.js +387 -265
- package/lib/http/routes/auth.js +81 -61
- package/lib/http/routes/candidates.js +430 -320
- package/lib/http/routes/commands.js +289 -189
- package/lib/http/routes/extract.js +158 -125
- package/lib/http/routes/guardRules.js +309 -217
- package/lib/http/routes/knowledge.js +213 -154
- package/lib/http/routes/modules.js +578 -0
- package/lib/http/routes/monitoring.js +6 -6
- package/lib/http/routes/recipes.js +104 -93
- package/lib/http/routes/search.js +361 -305
- package/lib/http/routes/skills.js +145 -98
- package/lib/http/routes/snippets.js +42 -30
- package/lib/http/routes/spm.js +3 -405
- package/lib/http/routes/violations.js +113 -93
- package/lib/http/routes/wiki.js +211 -170
- package/lib/http/utils/routeHelpers.js +3 -1
- package/lib/http/utils/sse-sessions.js +16 -6
- package/lib/http/utils/sse.js +15 -5
- package/lib/infrastructure/audit/AuditLogger.js +5 -2
- package/lib/infrastructure/audit/AuditStore.js +10 -7
- package/lib/infrastructure/cache/CacheService.js +3 -1
- package/lib/infrastructure/cache/GraphCache.js +8 -4
- package/lib/infrastructure/cache/UnifiedCacheAdapter.js +1 -1
- package/lib/infrastructure/config/ConfigLoader.js +9 -5
- package/lib/infrastructure/config/Defaults.js +30 -10
- package/lib/infrastructure/config/Paths.js +28 -8
- package/lib/infrastructure/config/TriggerSymbol.js +22 -10
- package/lib/infrastructure/database/DatabaseConnection.js +15 -10
- package/lib/infrastructure/database/migrations/001_initial_schema.js +0 -1
- package/lib/infrastructure/external/ClipboardManager.js +6 -2
- package/lib/infrastructure/external/NativeUi.js +50 -43
- package/lib/infrastructure/external/OpenBrowser.js +14 -17
- package/lib/infrastructure/external/XcodeAutomation.js +14 -258
- package/lib/infrastructure/logging/Logger.js +46 -30
- package/lib/infrastructure/monitoring/ErrorTracker.js +7 -5
- package/lib/infrastructure/monitoring/PerformanceMonitor.js +12 -4
- package/lib/infrastructure/paths/HeaderResolver.js +25 -9
- package/lib/infrastructure/paths/PathFinder.js +34 -12
- package/lib/infrastructure/plugin/PluginManager.js +26 -8
- package/lib/infrastructure/realtime/RealtimeService.js +2 -2
- package/lib/infrastructure/vector/Chunker.js +22 -7
- package/lib/infrastructure/vector/IndexingPipeline.js +46 -22
- package/lib/infrastructure/vector/JsonVectorAdapter.js +90 -53
- package/lib/infrastructure/vector/VectorStore.js +28 -10
- package/lib/injection/ServiceContainer.js +247 -93
- package/lib/platform/ios/index.js +63 -0
- package/lib/platform/ios/routes/spm.js +437 -0
- package/lib/platform/ios/snippet/PlaceholderConverter.js +55 -0
- package/lib/platform/ios/snippet/XcodeCodec.js +112 -0
- package/lib/{service → platform/ios}/spm/DependencyGraph.js +41 -17
- package/lib/{service → platform/ios}/spm/PackageSwiftParser.js +41 -14
- package/lib/{service → platform/ios}/spm/PolicyEngine.js +9 -4
- package/lib/platform/ios/spm/SpmDiscoverer.js +122 -0
- package/lib/{service → platform/ios}/spm/SpmService.js +385 -127
- package/lib/{service/automation → platform/ios/xcode}/SaveEventFilter.js +8 -7
- package/lib/platform/ios/xcode/XcodeAutomation.js +350 -0
- package/lib/{service/automation → platform/ios/xcode}/XcodeIntegration.js +325 -145
- package/lib/repository/base/BaseRepository.js +7 -9
- package/lib/repository/knowledge/KnowledgeRepository.impl.js +98 -75
- package/lib/repository/token/TokenUsageStore.js +4 -2
- package/lib/service/automation/ActionPipeline.js +1 -1
- package/lib/service/automation/AutomationOrchestrator.js +8 -4
- package/lib/service/automation/ContextCollector.js +7 -5
- package/lib/service/automation/DirectiveDetector.js +23 -16
- package/lib/service/automation/FileWatcher.js +112 -56
- package/lib/service/automation/TriggerResolver.js +6 -4
- package/lib/service/automation/handlers/AlinkHandler.js +24 -12
- package/lib/service/automation/handlers/CreateHandler.js +19 -20
- package/lib/service/automation/handlers/DraftHandler.js +14 -8
- package/lib/service/automation/handlers/GuardHandler.js +93 -63
- package/lib/service/automation/handlers/HeaderHandler.js +1 -6
- package/lib/service/automation/handlers/SearchHandler.js +155 -88
- package/lib/service/bootstrap/BootstrapTaskManager.js +77 -35
- package/lib/service/candidate/SimilarityService.js +25 -9
- package/lib/service/chat/AnalystAgent.js +50 -24
- package/lib/service/chat/CandidateGuardrail.js +143 -17
- package/lib/service/chat/ChatAgent.js +655 -260
- package/lib/service/chat/ContextWindow.js +116 -71
- package/lib/service/chat/ConversationStore.js +77 -36
- package/lib/service/chat/EpisodicConsolidator.js +47 -23
- package/lib/service/chat/HandoffProtocol.js +98 -22
- package/lib/service/chat/Memory.js +34 -14
- package/lib/service/chat/ProducerAgent.js +40 -20
- package/lib/service/chat/ProjectSemanticMemory.js +109 -78
- package/lib/service/chat/ReasoningLayer.js +148 -70
- package/lib/service/chat/ReasoningTrace.js +44 -32
- package/lib/service/chat/TaskPipeline.js +39 -19
- package/lib/service/chat/ToolRegistry.js +48 -29
- package/lib/service/chat/WorkingMemory.js +44 -18
- package/lib/service/chat/tools.js +1096 -494
- package/lib/service/context/RecipeExtractor.js +132 -51
- package/lib/service/cursor/CursorDeliveryPipeline.js +82 -37
- package/lib/service/cursor/KnowledgeCompressor.js +25 -22
- package/lib/service/cursor/RulesGenerator.js +13 -7
- package/lib/service/cursor/SkillsSyncer.js +77 -27
- package/lib/service/cursor/TokenBudget.js +2 -2
- package/lib/service/cursor/TopicClassifier.js +54 -20
- package/lib/service/guard/ComplianceReporter.js +55 -43
- package/lib/service/guard/ExclusionManager.js +67 -29
- package/lib/service/guard/GuardCheckEngine.js +381 -86
- package/lib/service/guard/GuardFeedbackLoop.js +22 -10
- package/lib/service/guard/GuardService.js +29 -19
- package/lib/service/guard/RuleLearner.js +55 -23
- package/lib/service/guard/SourceFileCollector.js +27 -20
- package/lib/service/guard/ViolationsStore.js +43 -38
- package/lib/service/knowledge/CodeEntityGraph.js +147 -82
- package/lib/service/knowledge/ConfidenceRouter.js +12 -10
- package/lib/service/knowledge/KnowledgeFileWriter.js +147 -56
- package/lib/service/knowledge/KnowledgeGraphService.js +81 -34
- package/lib/service/knowledge/KnowledgeService.js +222 -112
- package/lib/service/module/ModuleService.js +969 -0
- package/lib/service/quality/FeedbackCollector.js +27 -15
- package/lib/service/quality/QualityScorer.js +78 -24
- package/lib/service/recipe/RecipeCandidateValidator.js +110 -44
- package/lib/service/recipe/RecipeParser.js +78 -45
- package/lib/service/search/CoarseRanker.js +43 -28
- package/lib/service/search/CrossEncoderReranker.js +32 -21
- package/lib/service/search/InvertedIndex.js +21 -7
- package/lib/service/search/MultiSignalRanker.js +90 -28
- package/lib/service/search/RetrievalFunnel.js +45 -24
- package/lib/service/search/SearchEngine.js +255 -103
- package/lib/service/skills/EventAggregator.js +32 -15
- package/lib/service/skills/SignalCollector.js +140 -64
- package/lib/service/skills/SkillAdvisor.js +79 -42
- package/lib/service/skills/SkillHooks.js +16 -14
- package/lib/service/snippet/PlaceholderConverter.js +5 -0
- package/lib/service/snippet/SnippetFactory.js +116 -99
- package/lib/service/snippet/SnippetInstaller.js +234 -62
- package/lib/service/snippet/codecs/SnippetCodec.js +67 -0
- package/lib/service/snippet/codecs/VSCodeCodec.js +102 -0
- package/lib/service/snippet/codecs/XcodeCodec.js +5 -0
- package/lib/service/wiki/WikiGenerator.js +637 -263
- package/lib/shared/DimensionCopyRegistry.js +472 -0
- package/lib/shared/LanguageService.js +399 -0
- package/lib/shared/PathGuard.js +45 -28
- package/lib/shared/RecipeReadinessChecker.js +72 -12
- package/lib/shared/constants.js +41 -41
- package/lib/shared/errors/BaseError.js +2 -2
- package/lib/shared/errors/index.js +4 -4
- package/lib/shared/similarity.js +25 -8
- package/lib/shared/token-utils.js +6 -2
- package/lib/shared/utils/common.js +12 -4
- package/package.json +49 -13
- package/scripts/bench-real-projects.mjs +256 -0
- package/scripts/build-native-ui.js +30 -30
- package/scripts/clear-old-vector-index.js +5 -35
- package/scripts/clear-vector-cache.js +7 -37
- package/scripts/collect-test-project-stats.mjs +160 -0
- package/scripts/diagnose-mcp.js +41 -32
- package/scripts/ensure-parse-package.js +6 -9
- package/scripts/generate-recipe-drafts.js +116 -77
- package/scripts/init-db.js +3 -20
- package/scripts/init-snippets.js +305 -0
- package/scripts/init-vector-db.js +173 -170
- package/scripts/install-cursor-skill.js +148 -104
- package/scripts/install-full.js +8 -21
- package/scripts/install-vscode-copilot.js +146 -145
- package/scripts/migrate-md-to-knowledge.mjs +139 -151
- package/scripts/postinstall-safe.js +5 -17
- package/scripts/recipe-audit.js +106 -82
- package/scripts/release.js +283 -323
- package/scripts/setup-mcp-config.js +60 -52
- package/scripts/verify-context-api.js +20 -20
- package/skills/autosnippet-analysis/SKILL.md +10 -6
- package/skills/autosnippet-candidates/SKILL.md +27 -26
- package/skills/autosnippet-coldstart/SKILL.md +555 -38
- package/skills/autosnippet-concepts/SKILL.md +349 -337
- package/skills/autosnippet-create/SKILL.md +5 -5
- package/skills/autosnippet-reference-dart/SKILL.md +543 -0
- package/skills/autosnippet-reference-go/SKILL.md +539 -0
- package/skills/autosnippet-reference-java/SKILL.md +534 -0
- package/skills/autosnippet-reference-jsts/SKILL.md +41 -9
- package/skills/autosnippet-reference-kotlin/SKILL.md +526 -0
- package/skills/autosnippet-reference-objc/SKILL.md +29 -6
- package/skills/autosnippet-reference-python/SKILL.md +800 -0
- package/skills/autosnippet-reference-swift/SKILL.md +70 -14
- package/skills/autosnippet-structure/SKILL.md +4 -4
- package/templates/cursor-rules/autosnippet-conventions.mdc +2 -2
- package/templates/recipes-setup/README.md +2 -2
- package/templates/recipes-setup/_template.md +1 -1
- package/dashboard/dist/assets/index-Bun3ld_J.css +0 -1
- package/dashboard/dist/assets/index-_Sk_Dmg3.js +0 -143
- package/resources/asd-entry/main.swift +0 -159
- package/scripts/build-asd-entry.js +0 -51
- package/scripts/init-xcode-snippets.js +0 -311
- 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)
|
|
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)
|
|
44
|
+
for (const ch of seg) {
|
|
45
|
+
tokens.push(ch);
|
|
46
|
+
}
|
|
43
47
|
// bigram
|
|
44
|
-
for (let i = 0; i < seg.length - 1; i++)
|
|
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)
|
|
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)
|
|
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 = [];
|
|
77
|
+
this.documents = []; // [{id, tokens, tokenFreq, length, meta}]
|
|
68
78
|
this.avgLength = 0;
|
|
69
|
-
this.docFreq = {};
|
|
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)
|
|
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;
|
|
108
|
-
if (tf === 0)
|
|
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 =
|
|
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
|
|
167
|
-
|
|
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
|
-
|
|
172
|
-
|
|
187
|
+
)
|
|
188
|
+
.all();
|
|
189
|
+
entries = entries.map((e) => ({
|
|
173
190
|
...e,
|
|
174
191
|
status: e.lifecycle,
|
|
175
192
|
}));
|
|
176
|
-
} catch {
|
|
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]
|
|
183
|
-
|
|
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 {
|
|
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 {
|
|
223
|
+
} catch {
|
|
224
|
+
/* ignore */
|
|
225
|
+
}
|
|
196
226
|
let qualityOverall = 0;
|
|
197
|
-
try {
|
|
198
|
-
|
|
199
|
-
|
|
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',
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
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 ??
|
|
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)
|
|
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(() => ({
|
|
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) {
|
|
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)
|
|
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,
|
|
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 {
|
|
426
|
+
} catch {
|
|
427
|
+
/* ignore */
|
|
428
|
+
}
|
|
364
429
|
}
|
|
365
430
|
let tags = item.tags || [];
|
|
366
431
|
if (typeof tags === 'string') {
|
|
367
|
-
try {
|
|
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)
|
|
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)
|
|
462
|
+
for (const t of tokens) {
|
|
463
|
+
sessionKeywords.add(t);
|
|
464
|
+
}
|
|
392
465
|
}
|
|
393
|
-
return items
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
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 (
|
|
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
|
|
422
|
-
|
|
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
|
-
|
|
427
|
-
|
|
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(
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
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 {
|
|
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')
|
|
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, {
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
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')
|
|
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', {
|
|
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)
|
|
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
|
|
560
|
-
|
|
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
|
-
|
|
564
|
-
|
|
565
|
-
|
|
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)
|
|
573
|
-
|
|
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)
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
if (!item.
|
|
579
|
-
|
|
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 {
|
|
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 {
|
|
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 =
|
|
593
|
-
|
|
594
|
-
|
|
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 {
|
|
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)
|
|
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)
|
|
794
|
+
if (k !== undefined) {
|
|
795
|
+
this._cache.delete(k);
|
|
796
|
+
}
|
|
645
797
|
}
|
|
646
798
|
}
|
|
647
799
|
this._cache.set(key, { data, time: Date.now() });
|