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.
- 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 +759 -243
- 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
|
@@ -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
|
|
11
|
-
const USAGE_HEADING_RE = /^##\s+(?:Usage\s*Guide|用法|使用指南)/
|
|
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)
|
|
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)
|
|
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)
|
|
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 =
|
|
58
|
-
|
|
59
|
-
|
|
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:
|
|
76
|
-
|
|
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)
|
|
90
|
-
|
|
91
|
-
|
|
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)
|
|
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
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
else value
|
|
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
|
|
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
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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)
|
|
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)
|
|
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 || '
|
|
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)
|
|
274
|
-
|
|
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:
|
|
12
|
-
semantic:
|
|
13
|
-
quality:
|
|
14
|
-
freshness:
|
|
15
|
-
popularity: options.popularityWeight ?? 0.
|
|
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)
|
|
25
|
+
if (!candidates || candidates.length === 0) {
|
|
26
|
+
return [];
|
|
27
|
+
}
|
|
26
28
|
|
|
27
|
-
return candidates
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
|
|
42
|
-
|
|
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.
|
|
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.
|
|
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)
|
|
86
|
+
if (!updated) {
|
|
87
|
+
return 0.5;
|
|
88
|
+
}
|
|
79
89
|
// 自动识别秒级/毫秒级 Unix 时间戳 (秒级 ≤ 9999999999 即 2286 年)
|
|
80
|
-
const ts =
|
|
81
|
-
|
|
82
|
-
|
|
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)
|
|
85
|
-
|
|
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;
|
|
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)
|
|
47
|
-
|
|
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 =
|
|
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?.(
|
|
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
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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)
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
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]
|
|
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))
|
|
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)
|
|
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)
|
|
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)
|
|
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)
|
|
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))
|
|
84
|
+
if (!docs.has(idx)) {
|
|
85
|
+
result.delete(idx);
|
|
86
|
+
}
|
|
73
87
|
}
|
|
74
88
|
}
|
|
75
89
|
}
|