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
|
@@ -57,37 +57,37 @@ export class SkillAdvisor {
|
|
|
57
57
|
try {
|
|
58
58
|
const guardInsights = this.#analyzeGuardPatterns();
|
|
59
59
|
analysisContext.guard = guardInsights.summary;
|
|
60
|
-
suggestions.push(...guardInsights.suggestions.filter(
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}
|
|
60
|
+
suggestions.push(...guardInsights.suggestions.filter((s) => !existingSkills.has(s.name)));
|
|
61
|
+
} catch {
|
|
62
|
+
/* silent */
|
|
63
|
+
}
|
|
64
64
|
|
|
65
65
|
// ── 维度 2: Memory 偏好积累 ──
|
|
66
66
|
try {
|
|
67
67
|
const memoryInsights = this.#analyzeMemoryPatterns();
|
|
68
68
|
analysisContext.memory = memoryInsights.summary;
|
|
69
|
-
suggestions.push(...memoryInsights.suggestions.filter(
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
}
|
|
69
|
+
suggestions.push(...memoryInsights.suggestions.filter((s) => !existingSkills.has(s.name)));
|
|
70
|
+
} catch {
|
|
71
|
+
/* silent */
|
|
72
|
+
}
|
|
73
73
|
|
|
74
74
|
// ── 维度 3: Recipe 分布与使用 ──
|
|
75
75
|
try {
|
|
76
76
|
const recipeInsights = this.#analyzeRecipePatterns();
|
|
77
77
|
analysisContext.recipes = recipeInsights.summary;
|
|
78
|
-
suggestions.push(...recipeInsights.suggestions.filter(
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
}
|
|
78
|
+
suggestions.push(...recipeInsights.suggestions.filter((s) => !existingSkills.has(s.name)));
|
|
79
|
+
} catch {
|
|
80
|
+
/* silent */
|
|
81
|
+
}
|
|
82
82
|
|
|
83
83
|
// ── 维度 4: 候选积压 ──
|
|
84
84
|
try {
|
|
85
85
|
const candidateInsights = this.#analyzeCandidatePatterns();
|
|
86
86
|
analysisContext.candidates = candidateInsights.summary;
|
|
87
|
-
suggestions.push(...candidateInsights.suggestions.filter(
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
}
|
|
87
|
+
suggestions.push(...candidateInsights.suggestions.filter((s) => !existingSkills.has(s.name)));
|
|
88
|
+
} catch {
|
|
89
|
+
/* silent */
|
|
90
|
+
}
|
|
91
91
|
|
|
92
92
|
// 按优先级排序:high > medium > low
|
|
93
93
|
const priorityOrder = { high: 0, medium: 1, low: 2 };
|
|
@@ -97,9 +97,10 @@ export class SkillAdvisor {
|
|
|
97
97
|
suggestions,
|
|
98
98
|
existingProjectSkills: [...existingSkills],
|
|
99
99
|
analysisContext,
|
|
100
|
-
hint:
|
|
101
|
-
|
|
102
|
-
|
|
100
|
+
hint:
|
|
101
|
+
suggestions.length > 0
|
|
102
|
+
? `发现 ${suggestions.length} 个 Skill 创建建议。你可以使用 autosnippet_skill({ operation: "create" }) 直接创建,也可以根据 rationale 自行判断是否需要。`
|
|
103
|
+
: '当前项目使用模式暂无明确的 Skill 创建建议。继续使用后会积累更多信号。',
|
|
103
104
|
};
|
|
104
105
|
}
|
|
105
106
|
|
|
@@ -109,11 +110,14 @@ export class SkillAdvisor {
|
|
|
109
110
|
|
|
110
111
|
#analyzeGuardPatterns() {
|
|
111
112
|
const suggestions = [];
|
|
112
|
-
if (!this.#db)
|
|
113
|
+
if (!this.#db) {
|
|
114
|
+
return { summary: 'DB 不可用', suggestions };
|
|
115
|
+
}
|
|
113
116
|
|
|
114
117
|
try {
|
|
115
118
|
// 查询 Guard 违规记录(audit_logs 中 action LIKE 'guard%' + result='violation')
|
|
116
|
-
const rows = this.#db
|
|
119
|
+
const rows = this.#db
|
|
120
|
+
.prepare(`
|
|
117
121
|
SELECT json_extract(operation_data, '$.ruleName') as ruleName,
|
|
118
122
|
COUNT(*) as cnt
|
|
119
123
|
FROM audit_logs
|
|
@@ -123,7 +127,8 @@ export class SkillAdvisor {
|
|
|
123
127
|
HAVING cnt >= 3
|
|
124
128
|
ORDER BY cnt DESC
|
|
125
129
|
LIMIT 5
|
|
126
|
-
`)
|
|
130
|
+
`)
|
|
131
|
+
.all();
|
|
127
132
|
|
|
128
133
|
if (rows.length > 0) {
|
|
129
134
|
const topRule = rows[0];
|
|
@@ -160,17 +165,29 @@ export class SkillAdvisor {
|
|
|
160
165
|
|
|
161
166
|
try {
|
|
162
167
|
const raw = fs.readFileSync(memoryPath, 'utf-8').trim();
|
|
163
|
-
if (!raw)
|
|
168
|
+
if (!raw) {
|
|
169
|
+
return { summary: '无 Memory 记录', suggestions };
|
|
170
|
+
}
|
|
164
171
|
|
|
165
|
-
const entries = raw
|
|
166
|
-
.
|
|
172
|
+
const entries = raw
|
|
173
|
+
.split('\n')
|
|
174
|
+
.map((l) => {
|
|
175
|
+
try {
|
|
176
|
+
return JSON.parse(l);
|
|
177
|
+
} catch {
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
})
|
|
167
181
|
.filter(Boolean);
|
|
168
182
|
|
|
169
|
-
const preferences = entries.filter(e => e.type === 'preference');
|
|
183
|
+
const preferences = entries.filter((e) => e.type === 'preference');
|
|
170
184
|
|
|
171
185
|
if (preferences.length >= 5) {
|
|
172
186
|
// 有足够多的偏好积累 → 建议归纳为 Skill
|
|
173
|
-
const sample = preferences
|
|
187
|
+
const sample = preferences
|
|
188
|
+
.slice(-5)
|
|
189
|
+
.map((p) => p.content)
|
|
190
|
+
.join('\n- ');
|
|
174
191
|
suggestions.push({
|
|
175
192
|
name: 'project-conventions',
|
|
176
193
|
description: `项目约定总结 — 基于 ${preferences.length} 条团队偏好自动推荐`,
|
|
@@ -196,26 +213,32 @@ export class SkillAdvisor {
|
|
|
196
213
|
|
|
197
214
|
#analyzeRecipePatterns() {
|
|
198
215
|
const suggestions = [];
|
|
199
|
-
if (!this.#db)
|
|
216
|
+
if (!this.#db) {
|
|
217
|
+
return { summary: 'DB 不可用', suggestions };
|
|
218
|
+
}
|
|
200
219
|
|
|
201
220
|
try {
|
|
202
221
|
// 按 category 分布
|
|
203
|
-
const categories = this.#db
|
|
222
|
+
const categories = this.#db
|
|
223
|
+
.prepare(`
|
|
204
224
|
SELECT category, COUNT(*) as cnt
|
|
205
225
|
FROM knowledge_entries
|
|
206
226
|
WHERE lifecycle = 'active' AND category IS NOT NULL AND category != ''
|
|
207
227
|
GROUP BY category
|
|
208
228
|
ORDER BY cnt DESC
|
|
209
|
-
`)
|
|
229
|
+
`)
|
|
230
|
+
.all();
|
|
210
231
|
|
|
211
232
|
// 按 language 分布
|
|
212
|
-
const languages = this.#db
|
|
233
|
+
const languages = this.#db
|
|
234
|
+
.prepare(`
|
|
213
235
|
SELECT language, COUNT(*) as cnt
|
|
214
236
|
FROM knowledge_entries
|
|
215
237
|
WHERE lifecycle = 'active' AND language IS NOT NULL AND language != ''
|
|
216
238
|
GROUP BY language
|
|
217
239
|
ORDER BY cnt DESC
|
|
218
|
-
`)
|
|
240
|
+
`)
|
|
241
|
+
.all();
|
|
219
242
|
|
|
220
243
|
// 高频使用但无自定义 Skill 的 category
|
|
221
244
|
const topCategory = categories[0];
|
|
@@ -227,14 +250,19 @@ export class SkillAdvisor {
|
|
|
227
250
|
rationale: `项目中 ${topCategory.category} 类 Recipe 高达 ${topCategory.cnt} 条,占比最大。创建一个 Skill 汇总此类别的核心设计模式、常见用法和注意事项,让 AI 在处理相关代码时有更精准的参考。`,
|
|
228
251
|
source: 'recipe_distribution',
|
|
229
252
|
priority: 'low',
|
|
230
|
-
signals: {
|
|
253
|
+
signals: {
|
|
254
|
+
category: topCategory.category,
|
|
255
|
+
recipeCount: topCategory.cnt,
|
|
256
|
+
allCategories: categories,
|
|
257
|
+
},
|
|
231
258
|
});
|
|
232
259
|
}
|
|
233
260
|
|
|
234
261
|
// 高使用量 Recipe 统计(V3: stats JSON 中的 adoptions + applications)
|
|
235
262
|
let hotRecipes = [];
|
|
236
263
|
try {
|
|
237
|
-
hotRecipes = this.#db
|
|
264
|
+
hotRecipes = this.#db
|
|
265
|
+
.prepare(`
|
|
238
266
|
SELECT title, category,
|
|
239
267
|
(COALESCE(json_extract(stats, '$.adoptions'), 0) + COALESCE(json_extract(stats, '$.applications'), 0)) as total_usage
|
|
240
268
|
FROM knowledge_entries
|
|
@@ -242,8 +270,11 @@ export class SkillAdvisor {
|
|
|
242
270
|
AND (COALESCE(json_extract(stats, '$.adoptions'), 0) + COALESCE(json_extract(stats, '$.applications'), 0)) >= 5
|
|
243
271
|
ORDER BY total_usage DESC
|
|
244
272
|
LIMIT 10
|
|
245
|
-
`)
|
|
246
|
-
|
|
273
|
+
`)
|
|
274
|
+
.all();
|
|
275
|
+
} catch {
|
|
276
|
+
/* 查询失败时降级为空 */
|
|
277
|
+
}
|
|
247
278
|
|
|
248
279
|
return {
|
|
249
280
|
summary: { categories: categories.length, languages, hotRecipeCount: hotRecipes.length },
|
|
@@ -260,17 +291,21 @@ export class SkillAdvisor {
|
|
|
260
291
|
|
|
261
292
|
#analyzeCandidatePatterns() {
|
|
262
293
|
const suggestions = [];
|
|
263
|
-
if (!this.#db)
|
|
294
|
+
if (!this.#db) {
|
|
295
|
+
return { summary: 'DB 不可用', suggestions };
|
|
296
|
+
}
|
|
264
297
|
|
|
265
298
|
try {
|
|
266
299
|
// V3: candidates 已合并到 knowledge_entries,ifecycle='pending' 即为候选
|
|
267
|
-
const stats = this.#db
|
|
300
|
+
const stats = this.#db
|
|
301
|
+
.prepare(`
|
|
268
302
|
SELECT
|
|
269
303
|
COUNT(*) as total,
|
|
270
304
|
SUM(CASE WHEN lifecycle='pending' THEN 1 ELSE 0 END) as pending,
|
|
271
305
|
SUM(CASE WHEN lifecycle='deprecated' THEN 1 ELSE 0 END) as rejected
|
|
272
306
|
FROM knowledge_entries
|
|
273
|
-
`)
|
|
307
|
+
`)
|
|
308
|
+
.get();
|
|
274
309
|
|
|
275
310
|
// 大量被拒绝 → 提示候选质量 Skill
|
|
276
311
|
if (stats && stats.rejected >= 10) {
|
|
@@ -305,9 +340,11 @@ export class SkillAdvisor {
|
|
|
305
340
|
const dir = getProjectSkillsPath(this.#projectRoot);
|
|
306
341
|
try {
|
|
307
342
|
fs.readdirSync(dir, { withFileTypes: true })
|
|
308
|
-
.filter(d => d.isDirectory())
|
|
309
|
-
.forEach(d => names.add(d.name));
|
|
310
|
-
} catch {
|
|
343
|
+
.filter((d) => d.isDirectory())
|
|
344
|
+
.forEach((d) => names.add(d.name));
|
|
345
|
+
} catch {
|
|
346
|
+
/* no project skills */
|
|
347
|
+
}
|
|
311
348
|
return names;
|
|
312
349
|
}
|
|
313
350
|
}
|
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
import fs from 'node:fs';
|
|
17
17
|
import path from 'node:path';
|
|
18
18
|
import { fileURLToPath } from 'node:url';
|
|
19
|
-
import Logger from '../../infrastructure/logging/Logger.js';
|
|
20
19
|
import { getProjectSkillsPath } from '../../infrastructure/config/Paths.js';
|
|
20
|
+
import Logger from '../../infrastructure/logging/Logger.js';
|
|
21
21
|
|
|
22
22
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
23
23
|
const SKILLS_DIR = path.resolve(__dirname, '../../../skills');
|
|
@@ -31,18 +31,13 @@ function _getProjectSkillsDir() {
|
|
|
31
31
|
return getProjectSkillsPath(projectRoot);
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
const HOOK_NAMES = [
|
|
35
|
-
'onCandidateSubmit',
|
|
36
|
-
'onRecipeCreated',
|
|
37
|
-
'onGuardCheck',
|
|
38
|
-
'onBootstrapComplete',
|
|
39
|
-
];
|
|
34
|
+
const HOOK_NAMES = ['onCandidateSubmit', 'onRecipeCreated', 'onGuardCheck', 'onBootstrapComplete'];
|
|
40
35
|
|
|
41
36
|
export class SkillHooks {
|
|
42
37
|
constructor() {
|
|
43
38
|
this.logger = Logger.getInstance();
|
|
44
39
|
/** @type {Map<string, Function[]>} hookName → [handler, ...] */
|
|
45
|
-
this.hooks = new Map(HOOK_NAMES.map(n => [n, []]));
|
|
40
|
+
this.hooks = new Map(HOOK_NAMES.map((n) => [n, []]));
|
|
46
41
|
}
|
|
47
42
|
|
|
48
43
|
/**
|
|
@@ -83,14 +78,18 @@ export class SkillHooks {
|
|
|
83
78
|
*/
|
|
84
79
|
async run(hookName, ...args) {
|
|
85
80
|
const handlers = this.hooks.get(hookName);
|
|
86
|
-
if (!handlers || handlers.length === 0)
|
|
81
|
+
if (!handlers || handlers.length === 0) {
|
|
82
|
+
return undefined;
|
|
83
|
+
}
|
|
87
84
|
|
|
88
85
|
let result;
|
|
89
86
|
for (const handler of handlers) {
|
|
90
87
|
try {
|
|
91
88
|
result = await handler(...args);
|
|
92
89
|
// 如果是 blocking hook 且返回 block=true,立即中断
|
|
93
|
-
if (result?.block)
|
|
90
|
+
if (result?.block) {
|
|
91
|
+
return result;
|
|
92
|
+
}
|
|
94
93
|
} catch (err) {
|
|
95
94
|
this.logger.warn(`SkillHook error in ${hookName}`, { error: err.message });
|
|
96
95
|
}
|
|
@@ -111,16 +110,19 @@ export class SkillHooks {
|
|
|
111
110
|
async #loadFromDir(dir, loaded) {
|
|
112
111
|
let dirs;
|
|
113
112
|
try {
|
|
114
|
-
dirs = fs
|
|
115
|
-
.
|
|
116
|
-
.
|
|
113
|
+
dirs = fs
|
|
114
|
+
.readdirSync(dir, { withFileTypes: true })
|
|
115
|
+
.filter((d) => d.isDirectory())
|
|
116
|
+
.map((d) => d.name);
|
|
117
117
|
} catch {
|
|
118
118
|
return; // 目录不存在
|
|
119
119
|
}
|
|
120
120
|
|
|
121
121
|
for (const name of dirs) {
|
|
122
122
|
const hooksPath = path.join(dir, name, 'hooks.js');
|
|
123
|
-
if (!fs.existsSync(hooksPath))
|
|
123
|
+
if (!fs.existsSync(hooksPath)) {
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
124
126
|
try {
|
|
125
127
|
const mod = await import(hooksPath);
|
|
126
128
|
loaded.set(name, mod.default || mod);
|
|
@@ -1,56 +1,58 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* SnippetFactory —
|
|
3
|
-
* 纯内存 Recipe → XML 转换器,不依赖 DB
|
|
2
|
+
* SnippetFactory — IDE 无关的 Snippet 生成工厂
|
|
4
3
|
*
|
|
5
|
-
*
|
|
4
|
+
* 职责:
|
|
5
|
+
* 1. Recipe → SnippetSpec (IDE 无关的中间表示)
|
|
6
|
+
* 2. 查询/列表操作 (listSnippets, getSnippet)
|
|
7
|
+
* 3. 委托 Codec 生成最终 IDE 格式 (generate, generateBatch)
|
|
8
|
+
*
|
|
9
|
+
* Codec 注册:
|
|
10
|
+
* factory.registerCodec(codec) — 注册 XcodeCodec / VSCodeCodec
|
|
11
|
+
* factory.generate(spec, 'xcode') — 按 target 生成
|
|
6
12
|
*/
|
|
7
13
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
const LANGUAGE_MAP = {
|
|
11
|
-
swift: 'Xcode.SourceCodeLanguage.Swift',
|
|
12
|
-
'objective-c': 'Xcode.SourceCodeLanguage.Objective-C',
|
|
13
|
-
objc: 'Xcode.SourceCodeLanguage.Objective-C',
|
|
14
|
-
c: 'Xcode.SourceCodeLanguage.C',
|
|
15
|
-
'c++': 'Xcode.SourceCodeLanguage.C-Plus-Plus',
|
|
16
|
-
javascript: 'Xcode.SourceCodeLanguage.JavaScript',
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
const SNIPPET_TEMPLATE = `<?xml version="1.0" encoding="UTF-8"?>
|
|
20
|
-
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
21
|
-
<plist version="1.0">
|
|
22
|
-
<dict>
|
|
23
|
-
\t<key>IDECodeSnippetCompletionPrefix</key>
|
|
24
|
-
\t<string>{completion}</string>
|
|
25
|
-
\t<key>IDECodeSnippetCompletionScopes</key>
|
|
26
|
-
\t<array>
|
|
27
|
-
\t\t<string>All</string>
|
|
28
|
-
\t</array>
|
|
29
|
-
\t<key>IDECodeSnippetContents</key>
|
|
30
|
-
\t<string>{content}</string>
|
|
31
|
-
\t<key>IDECodeSnippetIdentifier</key>
|
|
32
|
-
\t<string>{identifier}</string>
|
|
33
|
-
\t<key>IDECodeSnippetLanguage</key>
|
|
34
|
-
\t<string>{language}</string>
|
|
35
|
-
\t<key>IDECodeSnippetSummary</key>
|
|
36
|
-
\t<string>{summary}</string>
|
|
37
|
-
\t<key>IDECodeSnippetTitle</key>
|
|
38
|
-
\t<string>{title}</string>
|
|
39
|
-
\t<key>IDECodeSnippetUserSnippet</key>
|
|
40
|
-
\t<true/>
|
|
41
|
-
\t<key>IDECodeSnippetVersion</key>
|
|
42
|
-
\t<integer>2</integer>
|
|
43
|
-
</dict>
|
|
44
|
-
</plist>`;
|
|
14
|
+
import { XcodeCodec } from './codecs/XcodeCodec.js';
|
|
45
15
|
|
|
46
16
|
export class SnippetFactory {
|
|
17
|
+
/** @type {Map<string, import('./codecs/SnippetCodec.js').SnippetCodec>} */
|
|
18
|
+
#codecs = new Map();
|
|
19
|
+
|
|
47
20
|
/**
|
|
48
|
-
* @param {object} [knowledgeRepository] — KnowledgeRepositoryImpl
|
|
21
|
+
* @param {object} [knowledgeRepository] — KnowledgeRepositoryImpl(可选)
|
|
49
22
|
*/
|
|
50
23
|
constructor(knowledgeRepository) {
|
|
51
24
|
this._recipeRepo = knowledgeRepository || null;
|
|
52
25
|
}
|
|
53
26
|
|
|
27
|
+
// ─────────────── Codec 注册 ───────────────
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* 注册一个 IDE codec
|
|
31
|
+
* @param {import('./codecs/SnippetCodec.js').SnippetCodec} codec
|
|
32
|
+
*/
|
|
33
|
+
registerCodec(codec) {
|
|
34
|
+
this.#codecs.set(codec.id, codec);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* 获取已注册的 codec
|
|
39
|
+
* @param {string} target — 'xcode' | 'vscode'
|
|
40
|
+
* @returns {import('./codecs/SnippetCodec.js').SnippetCodec|undefined}
|
|
41
|
+
*/
|
|
42
|
+
getCodec(target) {
|
|
43
|
+
return this.#codecs.get(target);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* 获取所有已注册 codec 的 ID 列表
|
|
48
|
+
* @returns {string[]}
|
|
49
|
+
*/
|
|
50
|
+
getRegisteredTargets() {
|
|
51
|
+
return [...this.#codecs.keys()];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ─────────────── 依赖注入 ───────────────
|
|
55
|
+
|
|
54
56
|
/**
|
|
55
57
|
* 运行时注入 knowledgeRepository(用于延迟绑定场景)
|
|
56
58
|
*/
|
|
@@ -61,17 +63,23 @@ export class SnippetFactory {
|
|
|
61
63
|
// ─────────────── Recipe → Snippet 查询 ───────────────
|
|
62
64
|
|
|
63
65
|
/**
|
|
64
|
-
* 从 Recipe 列表实时生成 Snippet
|
|
66
|
+
* 从 Recipe 列表实时生成 Snippet 列表
|
|
65
67
|
* @param {object} [filters] — { language, category, keyword }
|
|
66
68
|
* @param {object} [pagination]
|
|
67
69
|
* @returns {Promise<Array>}
|
|
68
70
|
*/
|
|
69
71
|
async listSnippets(filters = {}, pagination = { page: 1, pageSize: 50 }) {
|
|
70
|
-
if (!this._recipeRepo)
|
|
72
|
+
if (!this._recipeRepo) {
|
|
73
|
+
return [];
|
|
74
|
+
}
|
|
71
75
|
|
|
72
76
|
const dbFilters = { status: 'active' };
|
|
73
|
-
if (filters.language)
|
|
74
|
-
|
|
77
|
+
if (filters.language) {
|
|
78
|
+
dbFilters.language = filters.language;
|
|
79
|
+
}
|
|
80
|
+
if (filters.category) {
|
|
81
|
+
dbFilters.category = filters.category;
|
|
82
|
+
}
|
|
75
83
|
|
|
76
84
|
let result;
|
|
77
85
|
if (filters.keyword) {
|
|
@@ -81,55 +89,70 @@ export class SnippetFactory {
|
|
|
81
89
|
}
|
|
82
90
|
|
|
83
91
|
const recipes = result?.data || result?.items || [];
|
|
84
|
-
return recipes.map(r => this.fromRecipe(r));
|
|
92
|
+
return recipes.map((r) => this.fromRecipe(r));
|
|
85
93
|
}
|
|
86
94
|
|
|
87
95
|
/**
|
|
88
96
|
* 从单个 Recipe ID 实时生成 Snippet
|
|
89
97
|
*/
|
|
90
98
|
async getSnippet(recipeId) {
|
|
91
|
-
if (!this._recipeRepo)
|
|
99
|
+
if (!this._recipeRepo) {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
92
102
|
const recipe = await this._recipeRepo.findById(recipeId);
|
|
93
|
-
if (!recipe)
|
|
103
|
+
if (!recipe) {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
94
106
|
return this.fromRecipe(recipe);
|
|
95
107
|
}
|
|
96
108
|
|
|
97
|
-
// ───────────────
|
|
109
|
+
// ─────────────── Codec 委托生成 ───────────────
|
|
98
110
|
|
|
99
111
|
/**
|
|
100
|
-
* 从 spec
|
|
101
|
-
* @param {object} spec
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
* @param {string} spec.completion — 触发补全前缀
|
|
105
|
-
* @param {string} spec.summary — 描述
|
|
106
|
-
* @param {string|string[]} spec.code — 代码内容(字符串或行数组)
|
|
107
|
-
* @param {string} spec.language — 语言标识符
|
|
108
|
-
* @returns {string} — XML plist 内容
|
|
112
|
+
* 使用指定 codec 从 spec 生成 IDE 格式内容
|
|
113
|
+
* @param {object} spec — SnippetSpec
|
|
114
|
+
* @param {string} [target='xcode'] — codec ID
|
|
115
|
+
* @returns {string}
|
|
109
116
|
*/
|
|
110
|
-
generate(spec) {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
117
|
+
generate(spec, target = 'xcode') {
|
|
118
|
+
const codec = this.#resolveCodec(target);
|
|
119
|
+
return codec.generate(spec);
|
|
120
|
+
}
|
|
114
121
|
|
|
115
|
-
|
|
116
|
-
|
|
122
|
+
/**
|
|
123
|
+
* 批量生成 (委托 codec)
|
|
124
|
+
* @param {Array} recipes
|
|
125
|
+
* @param {string} [target='xcode']
|
|
126
|
+
* @returns {Array<{ filename: string, content: string, spec: object }> | { filename: string, content: string, specs: object[] }}
|
|
127
|
+
*/
|
|
128
|
+
generateBatch(recipes, target = 'xcode') {
|
|
129
|
+
const codec = this.#resolveCodec(target);
|
|
130
|
+
const specs = recipes.map((r) => this.fromRecipe(r));
|
|
117
131
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
132
|
+
const bundleFilename = codec.getBundleFilename();
|
|
133
|
+
if (bundleFilename) {
|
|
134
|
+
// VSCode 模式: 单 bundle 文件
|
|
135
|
+
return {
|
|
136
|
+
filename: bundleFilename,
|
|
137
|
+
content: codec.generateBundle(specs),
|
|
138
|
+
specs,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
125
141
|
|
|
126
|
-
|
|
142
|
+
// Xcode 模式: 每个 snippet 一个文件
|
|
143
|
+
return specs.map((spec) => ({
|
|
144
|
+
filename: `${spec.identifier}${codec.fileExtension}`,
|
|
145
|
+
content: codec.generate(spec),
|
|
146
|
+
spec,
|
|
147
|
+
}));
|
|
127
148
|
}
|
|
128
149
|
|
|
150
|
+
// ─────────────── Recipe → SnippetSpec ───────────────
|
|
151
|
+
|
|
129
152
|
/**
|
|
130
|
-
* 从 Recipe/Candidate 生成 snippet spec
|
|
153
|
+
* 从 Recipe/Candidate 生成 IDE 无关的 snippet spec
|
|
131
154
|
* @param {object} recipe — { id, title, trigger, code, description, language }
|
|
132
|
-
* @returns {object} —
|
|
155
|
+
* @returns {object} — SnippetSpec
|
|
133
156
|
*/
|
|
134
157
|
fromRecipe(recipe) {
|
|
135
158
|
return {
|
|
@@ -138,38 +161,32 @@ export class SnippetFactory {
|
|
|
138
161
|
completion: recipe.trigger || this.#slugify(recipe.title),
|
|
139
162
|
summary: recipe.description || recipe.summary || '',
|
|
140
163
|
code: recipe.code,
|
|
141
|
-
language: recipe.language || '
|
|
164
|
+
language: recipe.language || 'unknown',
|
|
142
165
|
};
|
|
143
166
|
}
|
|
144
167
|
|
|
168
|
+
// ─────────────── Private ───────────────
|
|
169
|
+
|
|
145
170
|
/**
|
|
146
|
-
*
|
|
147
|
-
* @
|
|
148
|
-
* @returns {Array<{ filename: string, content: string, spec: object }>}
|
|
171
|
+
* @param {string} target
|
|
172
|
+
* @returns {import('./codecs/SnippetCodec.js').SnippetCodec}
|
|
149
173
|
*/
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
spec,
|
|
157
|
-
};
|
|
158
|
-
});
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
#escapeXml(str) {
|
|
162
|
-
if (!str) return '';
|
|
163
|
-
return String(str)
|
|
164
|
-
.replace(/&/g, '&')
|
|
165
|
-
.replace(/</g, '<')
|
|
166
|
-
.replace(/>/g, '>')
|
|
167
|
-
.replace(/"/g, '"')
|
|
168
|
-
.replace(/'/g, ''');
|
|
174
|
+
#resolveCodec(target) {
|
|
175
|
+
const codec = this.#codecs.get(target);
|
|
176
|
+
if (!codec) {
|
|
177
|
+
throw new Error(`No codec registered for target "${target}". Available: [${this.getRegisteredTargets().join(', ')}]`);
|
|
178
|
+
}
|
|
179
|
+
return codec;
|
|
169
180
|
}
|
|
170
181
|
|
|
171
182
|
#slugify(str) {
|
|
172
|
-
if (!str)
|
|
173
|
-
|
|
183
|
+
if (!str) {
|
|
184
|
+
return 'unnamed';
|
|
185
|
+
}
|
|
186
|
+
return str
|
|
187
|
+
.toLowerCase()
|
|
188
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
189
|
+
.replace(/(^-|-$)/g, '')
|
|
190
|
+
.slice(0, 50);
|
|
174
191
|
}
|
|
175
192
|
}
|