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
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
* 从 Markdown 文件提取 Recipe 元数据、代码块、语义标签、质量评分
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { readFileSync, existsSync } from 'node:fs';
|
|
7
6
|
import { createHash } from 'node:crypto';
|
|
8
|
-
import {
|
|
7
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
8
|
+
import { basename } from 'node:path';
|
|
9
|
+
import { LanguageService } from '../../shared/LanguageService.js';
|
|
9
10
|
|
|
10
11
|
export class RecipeExtractor {
|
|
11
12
|
#options;
|
|
@@ -25,7 +26,9 @@ export class RecipeExtractor {
|
|
|
25
26
|
* @returns {object|null}
|
|
26
27
|
*/
|
|
27
28
|
extractFromFile(filePath) {
|
|
28
|
-
if (!existsSync(filePath))
|
|
29
|
+
if (!existsSync(filePath)) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
29
32
|
const content = readFileSync(filePath, 'utf-8');
|
|
30
33
|
return this.extractFromContent(content, basename(filePath), filePath);
|
|
31
34
|
}
|
|
@@ -55,22 +58,25 @@ export class RecipeExtractor {
|
|
|
55
58
|
|
|
56
59
|
// 6. 语义标签
|
|
57
60
|
const semanticTags = this.#options.extractSemanticTags
|
|
58
|
-
? this.#extractSemanticTags(body, codeBlocks)
|
|
61
|
+
? this.#extractSemanticTags(body, codeBlocks)
|
|
62
|
+
: [];
|
|
59
63
|
|
|
60
64
|
// 7. 代码质量分析
|
|
61
65
|
const quality = this.#options.analyzeCodeQuality
|
|
62
|
-
? this.#analyzeCodeQuality(codeBlocks, body)
|
|
66
|
+
? this.#analyzeCodeQuality(codeBlocks, body)
|
|
67
|
+
: {};
|
|
63
68
|
|
|
64
69
|
// 8. 内容 hash
|
|
65
70
|
const contentHash = this.#options.contentHashEnabled
|
|
66
|
-
? createHash('sha256').update(content).digest('hex').slice(0, 16)
|
|
71
|
+
? createHash('sha256').update(content).digest('hex').slice(0, 16)
|
|
72
|
+
: null;
|
|
67
73
|
|
|
68
74
|
return {
|
|
69
75
|
id: frontmatter.id || this.#generateId(filePath || filename),
|
|
70
76
|
title,
|
|
71
77
|
language,
|
|
72
78
|
category,
|
|
73
|
-
code: codeBlocks.map(b => b.code).join('\n\n'),
|
|
79
|
+
code: codeBlocks.map((b) => b.code).join('\n\n'),
|
|
74
80
|
description: frontmatter.description || this.#extractDescription(body),
|
|
75
81
|
content: body,
|
|
76
82
|
filePath,
|
|
@@ -90,7 +96,9 @@ export class RecipeExtractor {
|
|
|
90
96
|
|
|
91
97
|
#parseFrontmatter(content) {
|
|
92
98
|
const match = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
93
|
-
if (!match)
|
|
99
|
+
if (!match) {
|
|
100
|
+
return { frontmatter: {}, body: content };
|
|
101
|
+
}
|
|
94
102
|
|
|
95
103
|
const frontmatter = {};
|
|
96
104
|
const lines = match[1].split('\n');
|
|
@@ -101,11 +109,19 @@ export class RecipeExtractor {
|
|
|
101
109
|
let value = line.slice(colonIdx + 1).trim();
|
|
102
110
|
// 简单 YAML 值解析
|
|
103
111
|
if (value.startsWith('[') && value.endsWith(']')) {
|
|
104
|
-
value = value
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
else value
|
|
112
|
+
value = value
|
|
113
|
+
.slice(1, -1)
|
|
114
|
+
.split(',')
|
|
115
|
+
.map((s) => s.trim().replace(/^['"]|['"]$/g, ''));
|
|
116
|
+
} else if (value === 'true') {
|
|
117
|
+
value = true;
|
|
118
|
+
} else if (value === 'false') {
|
|
119
|
+
value = false;
|
|
120
|
+
} else if (/^\d+$/.test(value)) {
|
|
121
|
+
value = parseInt(value, 10);
|
|
122
|
+
} else {
|
|
123
|
+
value = value.replace(/^['"]|['"]$/g, '');
|
|
124
|
+
}
|
|
109
125
|
frontmatter[key] = value;
|
|
110
126
|
}
|
|
111
127
|
}
|
|
@@ -142,15 +158,34 @@ export class RecipeExtractor {
|
|
|
142
158
|
// 从代码块推断
|
|
143
159
|
if (codeBlocks.length > 0) {
|
|
144
160
|
const lang = codeBlocks[0].language;
|
|
145
|
-
if (lang && lang !== 'text')
|
|
161
|
+
if (lang && lang !== 'text') {
|
|
162
|
+
return lang;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
// 从文件名推断 —— 委托给 LanguageService
|
|
166
|
+
const detected = LanguageService.inferLang(filename);
|
|
167
|
+
if (detected !== 'unknown') {
|
|
168
|
+
return detected;
|
|
146
169
|
}
|
|
147
|
-
// 从文件名推断
|
|
148
|
-
const ext = extname(filename).toLowerCase();
|
|
149
|
-
const map = { '.swift': 'swift', '.js': 'javascript', '.ts': 'typescript', '.py': 'python' };
|
|
150
|
-
if (map[ext]) return map[ext];
|
|
151
170
|
// 从内容关键词推断
|
|
152
|
-
if (/\bSwiftUI\b|\bUIKit\b|\bfunc\s/.test(body))
|
|
153
|
-
|
|
171
|
+
if (/\bSwiftUI\b|\bUIKit\b|\bfunc\s/.test(body)) {
|
|
172
|
+
return 'swift';
|
|
173
|
+
}
|
|
174
|
+
if (/\bimport\s+React\b|\bconst\s/.test(body)) {
|
|
175
|
+
return 'javascript';
|
|
176
|
+
}
|
|
177
|
+
if (/\bdef\s+\w+.*:/.test(body)) {
|
|
178
|
+
return 'python';
|
|
179
|
+
}
|
|
180
|
+
if (/\bclass\s+\w+.*\{/.test(body) && /\bimport\s+java\./.test(body)) {
|
|
181
|
+
return 'java';
|
|
182
|
+
}
|
|
183
|
+
if (/\bpackage\s+\w+/.test(body) && /\bfunc\s/.test(body)) {
|
|
184
|
+
return 'go';
|
|
185
|
+
}
|
|
186
|
+
if (/\bfun\s+\w+/.test(body) && /\bval\s|\bvar\s/.test(body)) {
|
|
187
|
+
return 'kotlin';
|
|
188
|
+
}
|
|
154
189
|
return 'markdown';
|
|
155
190
|
}
|
|
156
191
|
|
|
@@ -159,21 +194,42 @@ export class RecipeExtractor {
|
|
|
159
194
|
#inferCategory(title, body, language) {
|
|
160
195
|
const text = `${title} ${body}`.toLowerCase();
|
|
161
196
|
const categories = [
|
|
162
|
-
{
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
{ name: '
|
|
167
|
-
{
|
|
168
|
-
|
|
169
|
-
|
|
197
|
+
{
|
|
198
|
+
name: 'networking',
|
|
199
|
+
keywords: ['network', 'api', 'http', 'url', 'fetch', 'request', 'response'],
|
|
200
|
+
},
|
|
201
|
+
{ name: 'ui', keywords: ['ui', 'view', 'button', 'label', 'layout', 'component', 'render'] },
|
|
202
|
+
{
|
|
203
|
+
name: 'storage',
|
|
204
|
+
keywords: ['storage', 'database', 'cache', 'persist', 'save', 'file', 'coredata'],
|
|
205
|
+
},
|
|
206
|
+
{ name: 'testing', keywords: ['test', 'spec', 'assert', 'mock', 'expect', 'coverage'] },
|
|
207
|
+
{
|
|
208
|
+
name: 'security',
|
|
209
|
+
keywords: ['security', 'auth', 'encrypt', 'token', 'permission', 'keychain'],
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
name: 'performance',
|
|
213
|
+
keywords: ['performance', 'optimize', 'speed', 'memory', 'async', 'concurrency'],
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
name: 'error-handling',
|
|
217
|
+
keywords: ['error', 'exception', 'catch', 'throw', 'fault', 'recovery'],
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
name: 'architecture',
|
|
221
|
+
keywords: ['mvvm', 'mvc', 'pattern', 'dependency', 'inject', 'protocol', 'design'],
|
|
222
|
+
},
|
|
170
223
|
];
|
|
171
224
|
|
|
172
225
|
let bestCat = 'general';
|
|
173
226
|
let bestScore = 0;
|
|
174
227
|
for (const { name, keywords } of categories) {
|
|
175
|
-
const score = keywords.filter(kw => text.includes(kw)).length;
|
|
176
|
-
if (score > bestScore) {
|
|
228
|
+
const score = keywords.filter((kw) => text.includes(kw)).length;
|
|
229
|
+
if (score > bestScore) {
|
|
230
|
+
bestScore = score;
|
|
231
|
+
bestCat = name;
|
|
232
|
+
}
|
|
177
233
|
}
|
|
178
234
|
return bestCat;
|
|
179
235
|
}
|
|
@@ -183,23 +239,25 @@ export class RecipeExtractor {
|
|
|
183
239
|
#extractSemanticTags(body, codeBlocks) {
|
|
184
240
|
const tags = new Set();
|
|
185
241
|
const text = body.toLowerCase();
|
|
186
|
-
const code = codeBlocks.map(b => b.code).join('\n');
|
|
242
|
+
const code = codeBlocks.map((b) => b.code).join('\n');
|
|
187
243
|
|
|
188
244
|
// 关键词标签
|
|
189
245
|
const tagPatterns = [
|
|
190
|
-
{ tag: 'async',
|
|
246
|
+
{ tag: 'async', pattern: /\basync\b|\bawait\b|\bPromise\b/i },
|
|
191
247
|
{ tag: 'error-handling', pattern: /\btry\b.*\bcatch\b|\bthrow\b|\bError\b/i },
|
|
192
|
-
{ tag: 'generics',
|
|
193
|
-
{ tag: 'protocol',
|
|
194
|
-
{ tag: 'closure',
|
|
195
|
-
{ tag: 'testing',
|
|
196
|
-
{ tag: 'reactive',
|
|
197
|
-
{ tag: 'caching',
|
|
248
|
+
{ tag: 'generics', pattern: /\b<\w+>\b|<T>|<Element>/i },
|
|
249
|
+
{ tag: 'protocol', pattern: /\bprotocol\b|\binterface\b|\bimplements\b/i },
|
|
250
|
+
{ tag: 'closure', pattern: /\bclosure\b|\bcallback\b|=>\s*{/i },
|
|
251
|
+
{ tag: 'testing', pattern: /\bXCTest\b|\bdescribe\b|\bit\b.*\bshould\b/i },
|
|
252
|
+
{ tag: 'reactive', pattern: /\bCombine\b|\bRxSwift\b|\bObservable\b|\buseState\b/i },
|
|
253
|
+
{ tag: 'caching', pattern: /\bcache\b|\bNSCache\b|\bmemoize\b/i },
|
|
198
254
|
{ tag: 'concurrency', pattern: /\bDispatchQueue\b|\bTask\s*{|\bactor\b/i },
|
|
199
255
|
];
|
|
200
256
|
|
|
201
257
|
for (const { tag, pattern } of tagPatterns) {
|
|
202
|
-
if (pattern.test(text) || pattern.test(code))
|
|
258
|
+
if (pattern.test(text) || pattern.test(code)) {
|
|
259
|
+
tags.add(tag);
|
|
260
|
+
}
|
|
203
261
|
}
|
|
204
262
|
|
|
205
263
|
return [...tags];
|
|
@@ -208,22 +266,34 @@ export class RecipeExtractor {
|
|
|
208
266
|
// --- Quality Analysis ---
|
|
209
267
|
|
|
210
268
|
#analyzeCodeQuality(codeBlocks, body) {
|
|
211
|
-
if (codeBlocks.length === 0)
|
|
269
|
+
if (codeBlocks.length === 0) {
|
|
270
|
+
return { score: 0.5, hasCode: false };
|
|
271
|
+
}
|
|
212
272
|
|
|
213
|
-
const allCode = codeBlocks.map(b => b.code).join('\n');
|
|
273
|
+
const allCode = codeBlocks.map((b) => b.code).join('\n');
|
|
214
274
|
let score = 0.5;
|
|
215
275
|
|
|
216
276
|
// 有测试 +0.1
|
|
217
|
-
if (/test|spec|assert|expect/i.test(allCode))
|
|
277
|
+
if (/test|spec|assert|expect/i.test(allCode)) {
|
|
278
|
+
score += 0.1;
|
|
279
|
+
}
|
|
218
280
|
// 有文档注释 +0.1
|
|
219
|
-
if (/\/\/\/|\/\*\*|"""/.test(allCode))
|
|
281
|
+
if (/\/\/\/|\/\*\*|"""/.test(allCode)) {
|
|
282
|
+
score += 0.1;
|
|
283
|
+
}
|
|
220
284
|
// 有错误处理 +0.1
|
|
221
|
-
if (/try|catch|throw|guard|Result</.test(allCode))
|
|
285
|
+
if (/try|catch|throw|guard|Result</.test(allCode)) {
|
|
286
|
+
score += 0.1;
|
|
287
|
+
}
|
|
222
288
|
// 合理长度 +0.1
|
|
223
289
|
const lines = allCode.split('\n').length;
|
|
224
|
-
if (lines >= 5 && lines <= 200)
|
|
290
|
+
if (lines >= 5 && lines <= 200) {
|
|
291
|
+
score += 0.1;
|
|
292
|
+
}
|
|
225
293
|
// 无安全红旗 +0.1
|
|
226
|
-
if (!/eval\(|exec\(|force_unwrap/.test(allCode))
|
|
294
|
+
if (!/eval\(|exec\(|force_unwrap/.test(allCode)) {
|
|
295
|
+
score += 0.1;
|
|
296
|
+
}
|
|
227
297
|
|
|
228
298
|
return {
|
|
229
299
|
score: Math.min(score, 1.0),
|
|
@@ -245,11 +315,22 @@ export class RecipeExtractor {
|
|
|
245
315
|
let inCode = false;
|
|
246
316
|
|
|
247
317
|
for (const line of lines) {
|
|
248
|
-
if (line.startsWith('```')) {
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
if (
|
|
318
|
+
if (line.startsWith('```')) {
|
|
319
|
+
inCode = !inCode;
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
if (inCode) {
|
|
323
|
+
continue;
|
|
324
|
+
}
|
|
325
|
+
if (line.startsWith('#')) {
|
|
326
|
+
continue;
|
|
327
|
+
}
|
|
328
|
+
if (line.trim().length > 0) {
|
|
329
|
+
paragraphs.push(line.trim());
|
|
330
|
+
}
|
|
331
|
+
if (paragraphs.length >= 3) {
|
|
332
|
+
break;
|
|
333
|
+
}
|
|
253
334
|
}
|
|
254
335
|
|
|
255
336
|
return paragraphs.join(' ').slice(0, 300) || '';
|
|
@@ -10,14 +10,14 @@
|
|
|
10
10
|
* 4. `asd upgrade` 时作为升级步骤执行
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
+
import fs from 'node:fs';
|
|
14
|
+
import path from 'node:path';
|
|
15
|
+
import { DELIVERY_RANK, KNOWLEDGE_CONFIDENCE } from '../../shared/constants.js';
|
|
13
16
|
import { KnowledgeCompressor } from './KnowledgeCompressor.js';
|
|
14
|
-
import { TopicClassifier } from './TopicClassifier.js';
|
|
15
17
|
import { RulesGenerator } from './RulesGenerator.js';
|
|
16
18
|
import { SkillsSyncer } from './SkillsSyncer.js';
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import path from 'node:path';
|
|
20
|
-
import fs from 'node:fs';
|
|
19
|
+
import { BUDGET } from './TokenBudget.js';
|
|
20
|
+
import { TopicClassifier } from './TopicClassifier.js';
|
|
21
21
|
|
|
22
22
|
export class CursorDeliveryPipeline {
|
|
23
23
|
/**
|
|
@@ -62,7 +62,9 @@ export class CursorDeliveryPipeline {
|
|
|
62
62
|
|
|
63
63
|
// 2. 分类:rules vs patterns vs facts vs documents
|
|
64
64
|
const { rules, patterns, documents } = this._classify(entries);
|
|
65
|
-
this.logger.info?.(
|
|
65
|
+
this.logger.info?.(
|
|
66
|
+
`[CursorDelivery] Classified: ${rules.length} rules, ${patterns.length} patterns, ${documents.length} documents`
|
|
67
|
+
);
|
|
66
68
|
|
|
67
69
|
// 3. 清理旧的动态生成文件
|
|
68
70
|
this.rulesGenerator.cleanDynamicFiles();
|
|
@@ -91,11 +93,13 @@ export class CursorDeliveryPipeline {
|
|
|
91
93
|
stats.totalTokensUsed = channelA.tokensUsed + channelB.totalTokens;
|
|
92
94
|
stats.duration = Date.now() - startTime;
|
|
93
95
|
|
|
94
|
-
this.logger.info?.(
|
|
95
|
-
`
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
96
|
+
this.logger.info?.(
|
|
97
|
+
`[CursorDelivery] Done in ${stats.duration}ms — ` +
|
|
98
|
+
`A: ${channelA.rulesCount} rules (${channelA.tokensUsed} tokens), ` +
|
|
99
|
+
`B: ${channelB.topicCount} topics (${channelB.totalTokens} tokens), ` +
|
|
100
|
+
`C: ${channelC.synced} skills synced, ` +
|
|
101
|
+
`D: ${channelD.documentsCount} documents`
|
|
102
|
+
);
|
|
99
103
|
|
|
100
104
|
return { channelA, channelB, channelC, channelD, stats };
|
|
101
105
|
} catch (error) {
|
|
@@ -133,7 +137,7 @@ export class CursorDeliveryPipeline {
|
|
|
133
137
|
);
|
|
134
138
|
const pendingItems = this._extractItems(pending);
|
|
135
139
|
// 过滤高置信度 pending(quality.confidence >= PENDING_MIN 或无 quality 字段)
|
|
136
|
-
const highConfPending = pendingItems.filter(e => {
|
|
140
|
+
const highConfPending = pendingItems.filter((e) => {
|
|
137
141
|
const conf = e.quality?.confidence;
|
|
138
142
|
return conf === undefined || conf === null || conf >= KNOWLEDGE_CONFIDENCE.PENDING_MIN;
|
|
139
143
|
});
|
|
@@ -150,9 +154,15 @@ export class CursorDeliveryPipeline {
|
|
|
150
154
|
* @private
|
|
151
155
|
*/
|
|
152
156
|
_extractItems(result) {
|
|
153
|
-
if (Array.isArray(result))
|
|
154
|
-
|
|
155
|
-
|
|
157
|
+
if (Array.isArray(result)) {
|
|
158
|
+
return result;
|
|
159
|
+
}
|
|
160
|
+
if (result?.items) {
|
|
161
|
+
return result.items;
|
|
162
|
+
}
|
|
163
|
+
if (result?.data) {
|
|
164
|
+
return result.data;
|
|
165
|
+
}
|
|
156
166
|
return [];
|
|
157
167
|
}
|
|
158
168
|
|
|
@@ -162,7 +172,10 @@ export class CursorDeliveryPipeline {
|
|
|
162
172
|
* @private
|
|
163
173
|
*/
|
|
164
174
|
_classify(entries) {
|
|
165
|
-
const rules = [],
|
|
175
|
+
const rules = [],
|
|
176
|
+
patterns = [],
|
|
177
|
+
facts = [],
|
|
178
|
+
documents = [];
|
|
166
179
|
for (const entry of entries) {
|
|
167
180
|
if (entry.knowledgeType === 'dev-document') {
|
|
168
181
|
documents.push(entry);
|
|
@@ -171,7 +184,7 @@ export class CursorDeliveryPipeline {
|
|
|
171
184
|
} else if (entry.kind === 'fact') {
|
|
172
185
|
facts.push(entry);
|
|
173
186
|
} else {
|
|
174
|
-
patterns.push(entry);
|
|
187
|
+
patterns.push(entry); // 无 kind 或 kind='pattern' → pattern
|
|
175
188
|
}
|
|
176
189
|
}
|
|
177
190
|
return { rules, patterns, facts, documents };
|
|
@@ -195,10 +208,16 @@ export class CursorDeliveryPipeline {
|
|
|
195
208
|
*/
|
|
196
209
|
_rankScore(entry) {
|
|
197
210
|
let score = 0;
|
|
198
|
-
score +=
|
|
211
|
+
score +=
|
|
212
|
+
(entry.quality?.confidence || KNOWLEDGE_CONFIDENCE.RANK_DEFAULT) *
|
|
213
|
+
DELIVERY_RANK.CONFIDENCE_WEIGHT;
|
|
199
214
|
score += (entry.quality?.authorityScore || 0) * DELIVERY_RANK.AUTHORITY_WEIGHT;
|
|
200
|
-
score +=
|
|
201
|
-
|
|
215
|
+
score +=
|
|
216
|
+
Math.min(entry.stats?.useCount || 0, DELIVERY_RANK.USE_COUNT_MAX) *
|
|
217
|
+
DELIVERY_RANK.USE_COUNT_WEIGHT;
|
|
218
|
+
if (entry.lifecycle === 'active') {
|
|
219
|
+
score += DELIVERY_RANK.ACTIVE_BONUS;
|
|
220
|
+
}
|
|
202
221
|
return score;
|
|
203
222
|
}
|
|
204
223
|
|
|
@@ -216,7 +235,9 @@ export class CursorDeliveryPipeline {
|
|
|
216
235
|
}
|
|
217
236
|
|
|
218
237
|
const result = this.rulesGenerator.writeAlwaysOnRules(ruleLines);
|
|
219
|
-
this.logger.info?.(
|
|
238
|
+
this.logger.info?.(
|
|
239
|
+
`[CursorDelivery] Channel A: ${result.rulesCount} rules → ${result.filePath}`
|
|
240
|
+
);
|
|
220
241
|
return result;
|
|
221
242
|
}
|
|
222
243
|
|
|
@@ -241,7 +262,9 @@ export class CursorDeliveryPipeline {
|
|
|
241
262
|
|
|
242
263
|
// 压缩为 When/Do/Don't
|
|
243
264
|
const compressed = this.compressor.compressToWhenDoDont(top);
|
|
244
|
-
if (compressed.length === 0)
|
|
265
|
+
if (compressed.length === 0) {
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
245
268
|
|
|
246
269
|
// 格式化为 Markdown
|
|
247
270
|
const body = this.compressor.formatWhenDoDont(compressed);
|
|
@@ -255,9 +278,14 @@ export class CursorDeliveryPipeline {
|
|
|
255
278
|
result.topicCount++;
|
|
256
279
|
result.patternsCount += compressed.length;
|
|
257
280
|
result.totalTokens += writeResult.tokensUsed;
|
|
258
|
-
result.topics[topic] = {
|
|
281
|
+
result.topics[topic] = {
|
|
282
|
+
patternsCount: compressed.length,
|
|
283
|
+
tokensUsed: writeResult.tokensUsed,
|
|
284
|
+
};
|
|
259
285
|
|
|
260
|
-
this.logger.info?.(
|
|
286
|
+
this.logger.info?.(
|
|
287
|
+
`[CursorDelivery] Channel B: ${topic} — ${compressed.length} patterns → ${writeResult.filePath}`
|
|
288
|
+
);
|
|
261
289
|
}
|
|
262
290
|
|
|
263
291
|
return result;
|
|
@@ -272,7 +300,7 @@ export class CursorDeliveryPipeline {
|
|
|
272
300
|
const syncResult = await this.skillsSyncer.sync();
|
|
273
301
|
this.logger.info?.(
|
|
274
302
|
`[CursorDelivery] Channel C: ${syncResult.synced.length} synced, ` +
|
|
275
|
-
|
|
303
|
+
`${syncResult.skipped.length} skipped, ${syncResult.errors.length} errors`
|
|
276
304
|
);
|
|
277
305
|
return {
|
|
278
306
|
synced: syncResult.synced.length,
|
|
@@ -282,7 +310,12 @@ export class CursorDeliveryPipeline {
|
|
|
282
310
|
};
|
|
283
311
|
} catch (err) {
|
|
284
312
|
this.logger.error?.(`[CursorDelivery] Channel C error: ${err.message}`);
|
|
285
|
-
return {
|
|
313
|
+
return {
|
|
314
|
+
synced: 0,
|
|
315
|
+
skipped: 0,
|
|
316
|
+
errors: 1,
|
|
317
|
+
details: { synced: [], skipped: [], errors: [err.message] },
|
|
318
|
+
};
|
|
286
319
|
}
|
|
287
320
|
}
|
|
288
321
|
|
|
@@ -345,7 +378,9 @@ export class CursorDeliveryPipeline {
|
|
|
345
378
|
'---',
|
|
346
379
|
'',
|
|
347
380
|
markdown,
|
|
348
|
-
]
|
|
381
|
+
]
|
|
382
|
+
.filter(Boolean)
|
|
383
|
+
.join('\n');
|
|
349
384
|
|
|
350
385
|
const docPath = path.join(refsDir, `${slug}.md`);
|
|
351
386
|
fs.writeFileSync(docPath, docContent, 'utf8');
|
|
@@ -359,10 +394,12 @@ export class CursorDeliveryPipeline {
|
|
|
359
394
|
skillLines.push('For full-text search across all documents:');
|
|
360
395
|
skillLines.push('- `autosnippet_search("your query")`');
|
|
361
396
|
|
|
362
|
-
fs.writeFileSync(path.join(devdocsDir, 'SKILL.md'), skillLines.join('\n')
|
|
397
|
+
fs.writeFileSync(path.join(devdocsDir, 'SKILL.md'), `${skillLines.join('\n')}\n`, 'utf8');
|
|
363
398
|
result.documentsCount = documents.length;
|
|
364
399
|
|
|
365
|
-
this.logger.info?.(
|
|
400
|
+
this.logger.info?.(
|
|
401
|
+
`[CursorDelivery] Channel D: ${result.documentsCount} documents → ${refsDir}`
|
|
402
|
+
);
|
|
366
403
|
return result;
|
|
367
404
|
}
|
|
368
405
|
|
|
@@ -371,11 +408,13 @@ export class CursorDeliveryPipeline {
|
|
|
371
408
|
* @private
|
|
372
409
|
*/
|
|
373
410
|
_slugify(text) {
|
|
374
|
-
return
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
411
|
+
return (
|
|
412
|
+
text
|
|
413
|
+
.toLowerCase()
|
|
414
|
+
.replace(/[^a-z0-9\u4e00-\u9fff]+/g, '-')
|
|
415
|
+
.replace(/^-+|-+$/g, '')
|
|
416
|
+
.substring(0, 80) || 'untitled'
|
|
417
|
+
);
|
|
379
418
|
}
|
|
380
419
|
|
|
381
420
|
/**
|
|
@@ -395,9 +434,13 @@ export class CursorDeliveryPipeline {
|
|
|
395
434
|
const targetRulesDir = path.join(targetDir, 'rules');
|
|
396
435
|
fs.mkdirSync(targetRulesDir, { recursive: true });
|
|
397
436
|
for (const file of fs.readdirSync(cursorRulesDir)) {
|
|
398
|
-
if (!file.startsWith('autosnippet-'))
|
|
437
|
+
if (!file.startsWith('autosnippet-')) {
|
|
438
|
+
continue;
|
|
439
|
+
}
|
|
399
440
|
const src = path.join(cursorRulesDir, file);
|
|
400
|
-
if (!fs.statSync(src).isFile())
|
|
441
|
+
if (!fs.statSync(src).isFile()) {
|
|
442
|
+
continue;
|
|
443
|
+
}
|
|
401
444
|
// .mdc → .md
|
|
402
445
|
const destName = file.endsWith('.mdc') ? file.replace(/\.mdc$/, '.md') : file;
|
|
403
446
|
fs.copyFileSync(src, path.join(targetRulesDir, destName));
|
|
@@ -409,7 +452,9 @@ export class CursorDeliveryPipeline {
|
|
|
409
452
|
if (fs.existsSync(cursorSkillsDir)) {
|
|
410
453
|
const targetSkillsDir = path.join(targetDir, 'skills');
|
|
411
454
|
for (const entry of fs.readdirSync(cursorSkillsDir, { withFileTypes: true })) {
|
|
412
|
-
if (!entry.isDirectory() || !entry.name.startsWith('autosnippet-'))
|
|
455
|
+
if (!entry.isDirectory() || !entry.name.startsWith('autosnippet-')) {
|
|
456
|
+
continue;
|
|
457
|
+
}
|
|
413
458
|
this._copyDirRecursive(
|
|
414
459
|
path.join(cursorSkillsDir, entry.name),
|
|
415
460
|
path.join(targetSkillsDir, entry.name)
|
|
@@ -9,7 +9,6 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
export class KnowledgeCompressor {
|
|
12
|
-
|
|
13
12
|
/**
|
|
14
13
|
* Channel A — 一行式规则
|
|
15
14
|
* @param {Array<Object>} entries - KnowledgeEntry 数组 (kind='rule')
|
|
@@ -17,8 +16,8 @@ export class KnowledgeCompressor {
|
|
|
17
16
|
*/
|
|
18
17
|
compressToRuleLine(entries) {
|
|
19
18
|
return entries
|
|
20
|
-
.filter(e => e.doClause)
|
|
21
|
-
.map(e => {
|
|
19
|
+
.filter((e) => e.doClause) // 无 doClause → 跳过,不猜
|
|
20
|
+
.map((e) => {
|
|
22
21
|
let line = e.doClause;
|
|
23
22
|
if (e.dontClause) {
|
|
24
23
|
// AI 可能返回 "Don't ..." / "Do not ..." 开头,去掉冗余前缀
|
|
@@ -37,13 +36,15 @@ export class KnowledgeCompressor {
|
|
|
37
36
|
compressToWhenDoDont(entries) {
|
|
38
37
|
const seen = new Set();
|
|
39
38
|
return entries
|
|
40
|
-
.filter(e => e.trigger && e.whenClause && e.doClause)
|
|
41
|
-
.map(e => {
|
|
39
|
+
.filter((e) => e.trigger && e.whenClause && e.doClause) // 缺任一 → 跳过
|
|
40
|
+
.map((e) => {
|
|
42
41
|
let trigger = e.trigger.startsWith('@') ? e.trigger : `@${e.trigger}`;
|
|
43
42
|
// trigger 去重(AI 应保证唯一,但防御性检查)
|
|
44
43
|
if (seen.has(trigger)) {
|
|
45
44
|
let i = 2;
|
|
46
|
-
while (seen.has(`${trigger}-${i}`))
|
|
45
|
+
while (seen.has(`${trigger}-${i}`)) {
|
|
46
|
+
i++;
|
|
47
|
+
}
|
|
47
48
|
trigger = `${trigger}-${i}`;
|
|
48
49
|
}
|
|
49
50
|
seen.add(trigger);
|
|
@@ -65,22 +66,24 @@ export class KnowledgeCompressor {
|
|
|
65
66
|
*/
|
|
66
67
|
formatWhenDoDont(compressed, language = '') {
|
|
67
68
|
const lang = language || '';
|
|
68
|
-
return compressed
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
69
|
+
return compressed
|
|
70
|
+
.map((item) => {
|
|
71
|
+
const lines = [`### ${item.trigger}`];
|
|
72
|
+
lines.push(`- **When**: ${item.when}`);
|
|
73
|
+
lines.push(`- **Do**: ${item.do}`);
|
|
74
|
+
if (item.dont) {
|
|
75
|
+
const stripped = item.dont.replace(/^(Don't|Do not|Never)\s+/i, '');
|
|
76
|
+
lines.push(`- **Don't**: ${stripped}`);
|
|
77
|
+
}
|
|
78
|
+
if (item.template) {
|
|
79
|
+
lines.push('');
|
|
80
|
+
lines.push(`\`\`\`${lang}`);
|
|
81
|
+
lines.push(item.template);
|
|
82
|
+
lines.push('```');
|
|
83
|
+
}
|
|
84
|
+
return lines.join('\n');
|
|
85
|
+
})
|
|
86
|
+
.join('\n\n');
|
|
84
87
|
}
|
|
85
88
|
}
|
|
86
89
|
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
import fs from 'node:fs';
|
|
10
10
|
import path from 'node:path';
|
|
11
|
-
import {
|
|
11
|
+
import { BUDGET, estimateTokens } from './TokenBudget.js';
|
|
12
12
|
|
|
13
13
|
export class RulesGenerator {
|
|
14
14
|
/**
|
|
@@ -75,7 +75,7 @@ export class RulesGenerator {
|
|
|
75
75
|
const truncated = [];
|
|
76
76
|
let used = estimateTokens(description) + 50;
|
|
77
77
|
for (const line of lines) {
|
|
78
|
-
used += estimateTokens(line
|
|
78
|
+
used += estimateTokens(`${line}\n`);
|
|
79
79
|
if (used <= BUDGET.CHANNEL_B_MAX_PER_FILE) {
|
|
80
80
|
truncated.push(line);
|
|
81
81
|
}
|
|
@@ -99,14 +99,20 @@ export class RulesGenerator {
|
|
|
99
99
|
* 保留静态模板文件(autosnippet-conventions.mdc, autosnippet-skills.mdc)
|
|
100
100
|
*/
|
|
101
101
|
cleanDynamicFiles() {
|
|
102
|
-
if (!fs.existsSync(this.rulesDir))
|
|
102
|
+
if (!fs.existsSync(this.rulesDir)) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
103
105
|
|
|
104
106
|
const dynamicPrefixes = ['autosnippet-project-rules', 'autosnippet-patterns-'];
|
|
105
107
|
const files = fs.readdirSync(this.rulesDir);
|
|
106
108
|
for (const file of files) {
|
|
107
|
-
if (dynamicPrefixes.some(p => file.startsWith(p))) {
|
|
109
|
+
if (dynamicPrefixes.some((p) => file.startsWith(p))) {
|
|
108
110
|
const filePath = path.join(this.rulesDir, file);
|
|
109
|
-
try {
|
|
111
|
+
try {
|
|
112
|
+
fs.unlinkSync(filePath);
|
|
113
|
+
} catch {
|
|
114
|
+
/* ignore */
|
|
115
|
+
}
|
|
110
116
|
}
|
|
111
117
|
}
|
|
112
118
|
}
|
|
@@ -132,7 +138,7 @@ export class RulesGenerator {
|
|
|
132
138
|
'- `autosnippet_search({ query })` — search knowledge base (auto mode: BM25 + semantic)',
|
|
133
139
|
'- `autosnippet_search({ query, mode: "context" })` — context-aware search with history',
|
|
134
140
|
];
|
|
135
|
-
return lines.join('\n')
|
|
141
|
+
return `${lines.join('\n')}\n`;
|
|
136
142
|
}
|
|
137
143
|
|
|
138
144
|
/**
|
|
@@ -152,7 +158,7 @@ export class RulesGenerator {
|
|
|
152
158
|
'',
|
|
153
159
|
`For full code examples: \`autosnippet_search("${topic}")\``,
|
|
154
160
|
];
|
|
155
|
-
return lines.join('\n')
|
|
161
|
+
return `${lines.join('\n')}\n`;
|
|
156
162
|
}
|
|
157
163
|
|
|
158
164
|
/**
|