autosnippet 3.0.1 → 3.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +230 -324
- package/bin/api-server.js +1 -1
- package/bin/cli.js +204 -244
- package/bin/mcp-server.js +5 -3
- package/config/knowledge-base.config.js +132 -132
- package/dashboard/dist/assets/{icons-CEfgGaZi.js → icons-Cdq22n2i.js} +95 -100
- package/dashboard/dist/assets/index-ClkyPkDX.js +133 -0
- package/dashboard/dist/assets/index-t4QrJwv1.css +1 -0
- package/dashboard/dist/index.html +3 -3
- package/lib/bootstrap.js +8 -8
- package/lib/cli/AiScanService.js +86 -40
- package/lib/cli/KnowledgeSyncService.js +113 -74
- package/lib/cli/SetupService.js +439 -277
- package/lib/cli/UpgradeService.js +63 -100
- package/lib/core/AstAnalyzer.js +276 -597
- package/lib/core/ast/ProjectGraph.js +101 -40
- package/lib/core/ast/ensure-grammars.js +232 -0
- package/lib/core/ast/index.js +115 -0
- package/lib/core/ast/lang-dart.js +661 -0
- package/lib/core/ast/lang-go.js +530 -0
- package/lib/core/ast/lang-java.js +435 -0
- package/lib/core/ast/lang-javascript.js +272 -0
- package/lib/core/ast/lang-kotlin.js +423 -0
- package/lib/core/ast/lang-objc.js +388 -0
- package/lib/core/ast/lang-python.js +371 -0
- package/lib/core/ast/lang-swift.js +337 -0
- package/lib/core/ast/lang-typescript.js +503 -0
- package/lib/core/capability/CapabilityProbe.js +18 -9
- package/lib/core/constitution/Constitution.js +2 -3
- package/lib/core/constitution/ConstitutionValidator.js +65 -24
- package/lib/core/discovery/DartDiscoverer.js +534 -0
- package/lib/core/discovery/DiscovererRegistry.js +83 -0
- package/lib/core/discovery/GenericDiscoverer.js +225 -0
- package/lib/core/discovery/GoDiscoverer.js +541 -0
- package/lib/core/discovery/JvmDiscoverer.js +506 -0
- package/lib/core/discovery/NodeDiscoverer.js +466 -0
- package/lib/core/discovery/ProjectDiscoverer.js +93 -0
- package/lib/core/discovery/PythonDiscoverer.js +338 -0
- package/lib/core/discovery/SpmDiscoverer.js +5 -0
- package/lib/core/discovery/index.js +53 -0
- package/lib/core/enhancement/EnhancementPack.js +71 -0
- package/lib/core/enhancement/EnhancementRegistry.js +47 -0
- package/lib/core/enhancement/android-enhancement.js +102 -0
- package/lib/core/enhancement/django-enhancement.js +70 -0
- package/lib/core/enhancement/fastapi-enhancement.js +63 -0
- package/lib/core/enhancement/go-grpc-enhancement.js +152 -0
- package/lib/core/enhancement/go-web-enhancement.js +201 -0
- package/lib/core/enhancement/index.js +65 -0
- package/lib/core/enhancement/node-server-enhancement.js +88 -0
- package/lib/core/enhancement/react-enhancement.js +86 -0
- package/lib/core/enhancement/spring-enhancement.js +112 -0
- package/lib/core/enhancement/vue-enhancement.js +96 -0
- package/lib/core/gateway/Gateway.js +8 -9
- package/lib/core/gateway/GatewayActionRegistry.js +1 -1
- package/lib/core/permission/PermissionManager.js +12 -8
- package/lib/domain/index.js +13 -9
- package/lib/domain/knowledge/KnowledgeEntry.js +111 -101
- package/lib/domain/knowledge/KnowledgeRepository.js +0 -1
- package/lib/domain/knowledge/Lifecycle.js +22 -22
- package/lib/domain/knowledge/index.js +9 -12
- package/lib/domain/knowledge/values/Constraints.js +31 -21
- package/lib/domain/knowledge/values/Content.js +21 -13
- package/lib/domain/knowledge/values/Quality.js +31 -18
- package/lib/domain/knowledge/values/Reasoning.js +20 -12
- package/lib/domain/knowledge/values/Relations.js +37 -25
- package/lib/domain/knowledge/values/Stats.js +18 -12
- package/lib/domain/knowledge/values/index.js +4 -3
- package/lib/domain/snippet/Snippet.js +35 -10
- package/lib/external/ai/AiFactory.js +48 -16
- package/lib/external/ai/AiProvider.js +184 -90
- package/lib/external/ai/providers/ClaudeProvider.js +25 -12
- package/lib/external/ai/providers/GoogleGeminiProvider.js +59 -30
- package/lib/external/ai/providers/MockProvider.js +9 -3
- package/lib/external/ai/providers/OpenAiProvider.js +51 -29
- package/lib/external/mcp/McpServer.js +66 -36
- package/lib/external/mcp/errorHandler.js +23 -11
- package/lib/external/mcp/handlers/LanguageExtensions.js +138 -53
- package/lib/external/mcp/handlers/TargetClassifier.js +52 -16
- package/lib/external/mcp/handlers/bootstrap/pipeline/BootstrapSnapshot.js +81 -20
- package/lib/external/mcp/handlers/bootstrap/pipeline/EpisodicMemory.js +71 -42
- package/lib/external/mcp/handlers/bootstrap/pipeline/IncrementalBootstrap.js +9 -17
- package/lib/external/mcp/handlers/bootstrap/pipeline/ToolResultCache.js +14 -9
- package/lib/external/mcp/handlers/bootstrap/pipeline/dimension-context.js +15 -7
- package/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +352 -153
- package/lib/external/mcp/handlers/bootstrap/pipeline/tier-scheduler.js +52 -12
- package/lib/external/mcp/handlers/bootstrap/skills.js +143 -39
- package/lib/external/mcp/handlers/bootstrap.js +691 -168
- package/lib/external/mcp/handlers/browse.js +66 -22
- package/lib/external/mcp/handlers/candidate.js +118 -35
- package/lib/external/mcp/handlers/consolidated.js +49 -17
- package/lib/external/mcp/handlers/guard.js +104 -39
- package/lib/external/mcp/handlers/knowledge.js +60 -36
- package/lib/external/mcp/handlers/search.js +43 -14
- package/lib/external/mcp/handlers/skill.js +120 -45
- package/lib/external/mcp/handlers/structure.js +240 -86
- package/lib/external/mcp/handlers/system.js +42 -12
- package/lib/external/mcp/handlers/wiki.js +58 -33
- package/lib/external/mcp/tools.js +306 -123
- package/lib/http/HttpServer.js +72 -47
- package/lib/http/middleware/RateLimiter.js +5 -3
- package/lib/http/middleware/errorHandler.js +6 -1
- package/lib/http/middleware/requestLogger.js +14 -3
- package/lib/http/middleware/roleResolver.js +30 -23
- package/lib/http/routes/ai.js +387 -265
- package/lib/http/routes/auth.js +81 -61
- package/lib/http/routes/candidates.js +430 -320
- package/lib/http/routes/commands.js +289 -189
- package/lib/http/routes/extract.js +158 -125
- package/lib/http/routes/guardRules.js +309 -217
- package/lib/http/routes/knowledge.js +213 -154
- package/lib/http/routes/modules.js +578 -0
- package/lib/http/routes/monitoring.js +6 -6
- package/lib/http/routes/recipes.js +104 -93
- package/lib/http/routes/search.js +361 -305
- package/lib/http/routes/skills.js +145 -98
- package/lib/http/routes/snippets.js +42 -30
- package/lib/http/routes/spm.js +3 -405
- package/lib/http/routes/violations.js +113 -93
- package/lib/http/routes/wiki.js +211 -170
- package/lib/http/utils/routeHelpers.js +3 -1
- package/lib/http/utils/sse-sessions.js +16 -6
- package/lib/http/utils/sse.js +15 -5
- package/lib/infrastructure/audit/AuditLogger.js +5 -2
- package/lib/infrastructure/audit/AuditStore.js +10 -7
- package/lib/infrastructure/cache/CacheService.js +3 -1
- package/lib/infrastructure/cache/GraphCache.js +8 -4
- package/lib/infrastructure/cache/UnifiedCacheAdapter.js +1 -1
- package/lib/infrastructure/config/ConfigLoader.js +9 -5
- package/lib/infrastructure/config/Defaults.js +30 -10
- package/lib/infrastructure/config/Paths.js +28 -8
- package/lib/infrastructure/config/TriggerSymbol.js +22 -10
- package/lib/infrastructure/database/DatabaseConnection.js +15 -10
- package/lib/infrastructure/database/migrations/001_initial_schema.js +0 -1
- package/lib/infrastructure/external/ClipboardManager.js +6 -2
- package/lib/infrastructure/external/NativeUi.js +50 -43
- package/lib/infrastructure/external/OpenBrowser.js +14 -17
- package/lib/infrastructure/external/XcodeAutomation.js +14 -258
- package/lib/infrastructure/logging/Logger.js +46 -30
- package/lib/infrastructure/monitoring/ErrorTracker.js +7 -5
- package/lib/infrastructure/monitoring/PerformanceMonitor.js +12 -4
- package/lib/infrastructure/paths/HeaderResolver.js +25 -9
- package/lib/infrastructure/paths/PathFinder.js +34 -12
- package/lib/infrastructure/plugin/PluginManager.js +26 -8
- package/lib/infrastructure/realtime/RealtimeService.js +2 -2
- package/lib/infrastructure/vector/Chunker.js +22 -7
- package/lib/infrastructure/vector/IndexingPipeline.js +46 -22
- package/lib/infrastructure/vector/JsonVectorAdapter.js +90 -53
- package/lib/infrastructure/vector/VectorStore.js +28 -10
- package/lib/injection/ServiceContainer.js +247 -93
- package/lib/platform/ios/index.js +63 -0
- package/lib/platform/ios/routes/spm.js +437 -0
- package/lib/platform/ios/snippet/PlaceholderConverter.js +55 -0
- package/lib/platform/ios/snippet/XcodeCodec.js +112 -0
- package/lib/{service → platform/ios}/spm/DependencyGraph.js +41 -17
- package/lib/{service → platform/ios}/spm/PackageSwiftParser.js +41 -14
- package/lib/{service → platform/ios}/spm/PolicyEngine.js +9 -4
- package/lib/platform/ios/spm/SpmDiscoverer.js +122 -0
- package/lib/{service → platform/ios}/spm/SpmService.js +385 -127
- package/lib/{service/automation → platform/ios/xcode}/SaveEventFilter.js +8 -7
- package/lib/platform/ios/xcode/XcodeAutomation.js +350 -0
- package/lib/{service/automation → platform/ios/xcode}/XcodeIntegration.js +325 -145
- package/lib/repository/base/BaseRepository.js +7 -9
- package/lib/repository/knowledge/KnowledgeRepository.impl.js +98 -75
- package/lib/repository/token/TokenUsageStore.js +4 -2
- package/lib/service/automation/ActionPipeline.js +1 -1
- package/lib/service/automation/AutomationOrchestrator.js +8 -4
- package/lib/service/automation/ContextCollector.js +7 -5
- package/lib/service/automation/DirectiveDetector.js +23 -16
- package/lib/service/automation/FileWatcher.js +112 -56
- package/lib/service/automation/TriggerResolver.js +6 -4
- package/lib/service/automation/handlers/AlinkHandler.js +24 -12
- package/lib/service/automation/handlers/CreateHandler.js +19 -20
- package/lib/service/automation/handlers/DraftHandler.js +14 -8
- package/lib/service/automation/handlers/GuardHandler.js +93 -63
- package/lib/service/automation/handlers/HeaderHandler.js +1 -6
- package/lib/service/automation/handlers/SearchHandler.js +155 -88
- package/lib/service/bootstrap/BootstrapTaskManager.js +77 -35
- package/lib/service/candidate/SimilarityService.js +25 -9
- package/lib/service/chat/AnalystAgent.js +50 -24
- package/lib/service/chat/CandidateGuardrail.js +143 -17
- package/lib/service/chat/ChatAgent.js +655 -260
- package/lib/service/chat/ContextWindow.js +116 -71
- package/lib/service/chat/ConversationStore.js +77 -36
- package/lib/service/chat/EpisodicConsolidator.js +47 -23
- package/lib/service/chat/HandoffProtocol.js +98 -22
- package/lib/service/chat/Memory.js +34 -14
- package/lib/service/chat/ProducerAgent.js +40 -20
- package/lib/service/chat/ProjectSemanticMemory.js +109 -78
- package/lib/service/chat/ReasoningLayer.js +148 -70
- package/lib/service/chat/ReasoningTrace.js +44 -32
- package/lib/service/chat/TaskPipeline.js +39 -19
- package/lib/service/chat/ToolRegistry.js +48 -29
- package/lib/service/chat/WorkingMemory.js +44 -18
- package/lib/service/chat/tools.js +1096 -494
- package/lib/service/context/RecipeExtractor.js +132 -51
- package/lib/service/cursor/CursorDeliveryPipeline.js +82 -37
- package/lib/service/cursor/KnowledgeCompressor.js +25 -22
- package/lib/service/cursor/RulesGenerator.js +13 -7
- package/lib/service/cursor/SkillsSyncer.js +77 -27
- package/lib/service/cursor/TokenBudget.js +2 -2
- package/lib/service/cursor/TopicClassifier.js +54 -20
- package/lib/service/guard/ComplianceReporter.js +55 -43
- package/lib/service/guard/ExclusionManager.js +67 -29
- package/lib/service/guard/GuardCheckEngine.js +381 -86
- package/lib/service/guard/GuardFeedbackLoop.js +22 -10
- package/lib/service/guard/GuardService.js +29 -19
- package/lib/service/guard/RuleLearner.js +55 -23
- package/lib/service/guard/SourceFileCollector.js +27 -20
- package/lib/service/guard/ViolationsStore.js +43 -38
- package/lib/service/knowledge/CodeEntityGraph.js +147 -82
- package/lib/service/knowledge/ConfidenceRouter.js +12 -10
- package/lib/service/knowledge/KnowledgeFileWriter.js +147 -56
- package/lib/service/knowledge/KnowledgeGraphService.js +81 -34
- package/lib/service/knowledge/KnowledgeService.js +222 -112
- package/lib/service/module/ModuleService.js +969 -0
- package/lib/service/quality/FeedbackCollector.js +27 -15
- package/lib/service/quality/QualityScorer.js +78 -24
- package/lib/service/recipe/RecipeCandidateValidator.js +110 -44
- package/lib/service/recipe/RecipeParser.js +78 -45
- package/lib/service/search/CoarseRanker.js +43 -28
- package/lib/service/search/CrossEncoderReranker.js +32 -21
- package/lib/service/search/InvertedIndex.js +21 -7
- package/lib/service/search/MultiSignalRanker.js +90 -28
- package/lib/service/search/RetrievalFunnel.js +45 -24
- package/lib/service/search/SearchEngine.js +255 -103
- package/lib/service/skills/EventAggregator.js +32 -15
- package/lib/service/skills/SignalCollector.js +140 -64
- package/lib/service/skills/SkillAdvisor.js +79 -42
- package/lib/service/skills/SkillHooks.js +16 -14
- package/lib/service/snippet/PlaceholderConverter.js +5 -0
- package/lib/service/snippet/SnippetFactory.js +116 -99
- package/lib/service/snippet/SnippetInstaller.js +234 -62
- package/lib/service/snippet/codecs/SnippetCodec.js +67 -0
- package/lib/service/snippet/codecs/VSCodeCodec.js +102 -0
- package/lib/service/snippet/codecs/XcodeCodec.js +5 -0
- package/lib/service/wiki/WikiGenerator.js +637 -263
- package/lib/shared/DimensionCopyRegistry.js +472 -0
- package/lib/shared/LanguageService.js +399 -0
- package/lib/shared/PathGuard.js +45 -28
- package/lib/shared/RecipeReadinessChecker.js +72 -12
- package/lib/shared/constants.js +41 -41
- package/lib/shared/errors/BaseError.js +2 -2
- package/lib/shared/errors/index.js +4 -4
- package/lib/shared/similarity.js +25 -8
- package/lib/shared/token-utils.js +6 -2
- package/lib/shared/utils/common.js +12 -4
- package/package.json +49 -13
- package/scripts/bench-real-projects.mjs +256 -0
- package/scripts/build-native-ui.js +30 -30
- package/scripts/clear-old-vector-index.js +5 -35
- package/scripts/clear-vector-cache.js +7 -37
- package/scripts/collect-test-project-stats.mjs +160 -0
- package/scripts/diagnose-mcp.js +41 -32
- package/scripts/ensure-parse-package.js +6 -9
- package/scripts/generate-recipe-drafts.js +116 -77
- package/scripts/init-db.js +3 -20
- package/scripts/init-snippets.js +305 -0
- package/scripts/init-vector-db.js +173 -170
- package/scripts/install-cursor-skill.js +148 -104
- package/scripts/install-full.js +8 -21
- package/scripts/install-vscode-copilot.js +146 -145
- package/scripts/migrate-md-to-knowledge.mjs +139 -151
- package/scripts/postinstall-safe.js +5 -17
- package/scripts/recipe-audit.js +106 -82
- package/scripts/release.js +283 -323
- package/scripts/setup-mcp-config.js +60 -52
- package/scripts/verify-context-api.js +20 -20
- package/skills/autosnippet-analysis/SKILL.md +10 -6
- package/skills/autosnippet-candidates/SKILL.md +27 -26
- package/skills/autosnippet-coldstart/SKILL.md +555 -38
- package/skills/autosnippet-concepts/SKILL.md +349 -337
- package/skills/autosnippet-create/SKILL.md +5 -5
- package/skills/autosnippet-reference-dart/SKILL.md +543 -0
- package/skills/autosnippet-reference-go/SKILL.md +539 -0
- package/skills/autosnippet-reference-java/SKILL.md +534 -0
- package/skills/autosnippet-reference-jsts/SKILL.md +41 -9
- package/skills/autosnippet-reference-kotlin/SKILL.md +526 -0
- package/skills/autosnippet-reference-objc/SKILL.md +29 -6
- package/skills/autosnippet-reference-python/SKILL.md +800 -0
- package/skills/autosnippet-reference-swift/SKILL.md +70 -14
- package/skills/autosnippet-structure/SKILL.md +4 -4
- package/templates/cursor-rules/autosnippet-conventions.mdc +2 -2
- package/templates/recipes-setup/README.md +2 -2
- package/templates/recipes-setup/_template.md +1 -1
- package/dashboard/dist/assets/index-Bun3ld_J.css +0 -1
- package/dashboard/dist/assets/index-_Sk_Dmg3.js +0 -143
- package/resources/asd-entry/main.swift +0 -159
- package/scripts/build-asd-entry.js +0 -51
- package/scripts/init-xcode-snippets.js +0 -311
- package/template.json +0 -39
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { KnowledgeEntry } from '../../domain/knowledge/KnowledgeEntry.js';
|
|
2
|
-
import {
|
|
2
|
+
import { inferKind, Lifecycle } from '../../domain/knowledge/Lifecycle.js';
|
|
3
3
|
import Logger from '../../infrastructure/logging/Logger.js';
|
|
4
|
-
import {
|
|
4
|
+
import { ConflictError, NotFoundError, ValidationError } from '../../shared/errors/index.js';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* KnowledgeService — 统一知识服务
|
|
@@ -26,15 +26,15 @@ export class KnowledgeService {
|
|
|
26
26
|
* @param {import('../quality/QualityScorer.js').QualityScorer} [options.qualityScorer]
|
|
27
27
|
*/
|
|
28
28
|
constructor(repository, auditLogger, gateway, knowledgeGraphService, options = {}) {
|
|
29
|
-
this.repository
|
|
30
|
-
this.auditLogger
|
|
31
|
-
this.gateway
|
|
29
|
+
this.repository = repository;
|
|
30
|
+
this.auditLogger = auditLogger;
|
|
31
|
+
this.gateway = gateway;
|
|
32
32
|
this._knowledgeGraphService = knowledgeGraphService || null;
|
|
33
|
-
this._fileWriter
|
|
34
|
-
this._skillHooks
|
|
35
|
-
this._confidenceRouter
|
|
36
|
-
this._qualityScorer
|
|
37
|
-
this.logger
|
|
33
|
+
this._fileWriter = options.fileWriter || null;
|
|
34
|
+
this._skillHooks = options.skillHooks || null;
|
|
35
|
+
this._confidenceRouter = options.confidenceRouter || null;
|
|
36
|
+
this._qualityScorer = options.qualityScorer || null;
|
|
37
|
+
this.logger = Logger.getInstance();
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
/* ═══ CRUD ══════════════════════════════════════════════ */
|
|
@@ -56,9 +56,9 @@ export class KnowledgeService {
|
|
|
56
56
|
|
|
57
57
|
const entry = KnowledgeEntry.fromJSON({
|
|
58
58
|
...data,
|
|
59
|
-
lifecycle:
|
|
60
|
-
source:
|
|
61
|
-
createdBy:
|
|
59
|
+
lifecycle: Lifecycle.PENDING,
|
|
60
|
+
source: data.source || 'manual',
|
|
61
|
+
createdBy: context.userId,
|
|
62
62
|
});
|
|
63
63
|
|
|
64
64
|
if (!entry.isValid()) {
|
|
@@ -90,7 +90,7 @@ export class KnowledgeService {
|
|
|
90
90
|
this._syncRelationsToGraph(saved.id, saved.relations);
|
|
91
91
|
|
|
92
92
|
// 自动发现同域条目建立 related 边(best effort, 不阻塞)
|
|
93
|
-
this._autoDiscoverRelations(saved.id, saved).catch(err =>
|
|
93
|
+
this._autoDiscoverRelations(saved.id, saved).catch((err) =>
|
|
94
94
|
this.logger.warn('_autoDiscoverRelations error', { id: saved.id, error: err.message })
|
|
95
95
|
);
|
|
96
96
|
|
|
@@ -113,9 +113,13 @@ export class KnowledgeService {
|
|
|
113
113
|
|
|
114
114
|
// ── SkillHooks: onKnowledgeCreated (fire-and-forget) ──
|
|
115
115
|
if (this._skillHooks) {
|
|
116
|
-
this._skillHooks
|
|
117
|
-
|
|
118
|
-
|
|
116
|
+
this._skillHooks
|
|
117
|
+
.run('onKnowledgeCreated', saved, {
|
|
118
|
+
userId: context.userId,
|
|
119
|
+
})
|
|
120
|
+
.catch((err) =>
|
|
121
|
+
this.logger.warn('SkillHook onKnowledgeCreated error', { error: err.message })
|
|
122
|
+
);
|
|
119
123
|
}
|
|
120
124
|
|
|
121
125
|
return saved;
|
|
@@ -150,22 +154,43 @@ export class KnowledgeService {
|
|
|
150
154
|
*/
|
|
151
155
|
async update(id, data, context) {
|
|
152
156
|
try {
|
|
153
|
-
const
|
|
157
|
+
const _entry = await this._findOrThrow(id);
|
|
154
158
|
|
|
155
159
|
const UPDATABLE = [
|
|
156
|
-
'title',
|
|
157
|
-
'
|
|
158
|
-
'
|
|
159
|
-
'
|
|
160
|
-
'
|
|
160
|
+
'title',
|
|
161
|
+
'description',
|
|
162
|
+
'trigger',
|
|
163
|
+
'language',
|
|
164
|
+
'category',
|
|
165
|
+
'knowledgeType',
|
|
166
|
+
'complexity',
|
|
167
|
+
'scope',
|
|
168
|
+
'difficulty',
|
|
169
|
+
'content',
|
|
170
|
+
'relations',
|
|
171
|
+
'constraints',
|
|
172
|
+
'reasoning',
|
|
173
|
+
'tags',
|
|
174
|
+
'headers',
|
|
175
|
+
'headerPaths',
|
|
176
|
+
'moduleName',
|
|
177
|
+
'includeHeaders',
|
|
178
|
+
'agentNotes',
|
|
179
|
+
'aiInsight',
|
|
161
180
|
// Cursor 交付字段
|
|
162
|
-
'topicHint',
|
|
181
|
+
'topicHint',
|
|
182
|
+
'whenClause',
|
|
183
|
+
'doClause',
|
|
184
|
+
'dontClause',
|
|
185
|
+
'coreCode',
|
|
163
186
|
];
|
|
164
187
|
|
|
165
188
|
const dbUpdates = {};
|
|
166
189
|
|
|
167
190
|
for (const key of UPDATABLE) {
|
|
168
|
-
if (data[key] === undefined)
|
|
191
|
+
if (data[key] === undefined) {
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
169
194
|
|
|
170
195
|
switch (key) {
|
|
171
196
|
// 标量字段直传
|
|
@@ -232,13 +257,16 @@ export class KnowledgeService {
|
|
|
232
257
|
});
|
|
233
258
|
|
|
234
259
|
this.logger.info('Knowledge entry updated', {
|
|
235
|
-
id,
|
|
260
|
+
id,
|
|
261
|
+
updatedBy: context.userId,
|
|
262
|
+
fields: Object.keys(dbUpdates),
|
|
236
263
|
});
|
|
237
264
|
|
|
238
265
|
return updated;
|
|
239
266
|
} catch (error) {
|
|
240
267
|
this.logger.error('Error updating knowledge entry', {
|
|
241
|
-
id,
|
|
268
|
+
id,
|
|
269
|
+
error: error.message,
|
|
242
270
|
});
|
|
243
271
|
throw error;
|
|
244
272
|
}
|
|
@@ -267,13 +295,16 @@ export class KnowledgeService {
|
|
|
267
295
|
});
|
|
268
296
|
|
|
269
297
|
this.logger.info('Knowledge entry deleted', {
|
|
270
|
-
id,
|
|
298
|
+
id,
|
|
299
|
+
deletedBy: context.userId,
|
|
300
|
+
title: entry.title,
|
|
271
301
|
});
|
|
272
302
|
|
|
273
303
|
return { success: true, id };
|
|
274
304
|
} catch (error) {
|
|
275
305
|
this.logger.error('Error deleting knowledge entry', {
|
|
276
|
-
id,
|
|
306
|
+
id,
|
|
307
|
+
error: error.message,
|
|
277
308
|
});
|
|
278
309
|
throw error;
|
|
279
310
|
}
|
|
@@ -305,7 +336,9 @@ export class KnowledgeService {
|
|
|
305
336
|
const container = getServiceContainer();
|
|
306
337
|
if (container.services.cursorDeliveryPipeline) {
|
|
307
338
|
const pipeline = container.get('cursorDeliveryPipeline');
|
|
308
|
-
pipeline.deliver().catch(() => {
|
|
339
|
+
pipeline.deliver().catch(() => {
|
|
340
|
+
/* ignore */
|
|
341
|
+
});
|
|
309
342
|
}
|
|
310
343
|
})
|
|
311
344
|
.catch(() => {
|
|
@@ -335,22 +368,34 @@ export class KnowledgeService {
|
|
|
335
368
|
// ── 向后兼容别名 ──
|
|
336
369
|
|
|
337
370
|
/** @deprecated 简化后所有条目直接进 pending */
|
|
338
|
-
async submit(id, context) {
|
|
371
|
+
async submit(id, context) {
|
|
372
|
+
return this.get(id);
|
|
373
|
+
}
|
|
339
374
|
|
|
340
375
|
/** @deprecated 简化后 approve = publish */
|
|
341
|
-
async approve(id, context) {
|
|
376
|
+
async approve(id, context) {
|
|
377
|
+
return this.publish(id, context);
|
|
378
|
+
}
|
|
342
379
|
|
|
343
380
|
/** @deprecated 简化后无需 autoApprove */
|
|
344
|
-
async autoApprove(id, context) {
|
|
381
|
+
async autoApprove(id, context) {
|
|
382
|
+
return this.get(id);
|
|
383
|
+
}
|
|
345
384
|
|
|
346
385
|
/** @deprecated 简化后 reject = deprecate */
|
|
347
|
-
async reject(id, reason, context) {
|
|
386
|
+
async reject(id, reason, context) {
|
|
387
|
+
return this.deprecate(id, reason, context);
|
|
388
|
+
}
|
|
348
389
|
|
|
349
390
|
/** @deprecated 简化后 toDraft = reactivate */
|
|
350
|
-
async toDraft(id, context) {
|
|
391
|
+
async toDraft(id, context) {
|
|
392
|
+
return this.reactivate(id, context);
|
|
393
|
+
}
|
|
351
394
|
|
|
352
395
|
/** @deprecated 简化后 fastTrack = publish */
|
|
353
|
-
async fastTrack(id, context) {
|
|
396
|
+
async fastTrack(id, context) {
|
|
397
|
+
return this.publish(id, context);
|
|
398
|
+
}
|
|
354
399
|
|
|
355
400
|
/* ═══ 查询 ══════════════════════════════════════════════ */
|
|
356
401
|
|
|
@@ -365,19 +410,36 @@ export class KnowledgeService {
|
|
|
365
410
|
const { page = 1, pageSize = 20 } = pagination;
|
|
366
411
|
|
|
367
412
|
const dbFilters = {};
|
|
368
|
-
if (lifecycle)
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
if (
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
if (
|
|
375
|
-
|
|
413
|
+
if (lifecycle) {
|
|
414
|
+
dbFilters.lifecycle = lifecycle;
|
|
415
|
+
}
|
|
416
|
+
if (kind) {
|
|
417
|
+
dbFilters.kind = kind;
|
|
418
|
+
}
|
|
419
|
+
if (language) {
|
|
420
|
+
dbFilters.language = language;
|
|
421
|
+
}
|
|
422
|
+
if (category) {
|
|
423
|
+
dbFilters.category = category;
|
|
424
|
+
}
|
|
425
|
+
if (knowledgeType) {
|
|
426
|
+
dbFilters.knowledgeType = knowledgeType;
|
|
427
|
+
}
|
|
428
|
+
if (source) {
|
|
429
|
+
dbFilters.source = source;
|
|
430
|
+
}
|
|
431
|
+
if (scope) {
|
|
432
|
+
dbFilters.scope = scope;
|
|
433
|
+
}
|
|
434
|
+
if (tag) {
|
|
435
|
+
dbFilters._tagLike = tag;
|
|
436
|
+
}
|
|
376
437
|
|
|
377
438
|
return this.repository.findWithPagination(dbFilters, { page, pageSize });
|
|
378
439
|
} catch (error) {
|
|
379
440
|
this.logger.error('Error listing knowledge entries', {
|
|
380
|
-
error: error.message,
|
|
441
|
+
error: error.message,
|
|
442
|
+
filters,
|
|
381
443
|
});
|
|
382
444
|
throw error;
|
|
383
445
|
}
|
|
@@ -405,7 +467,8 @@ export class KnowledgeService {
|
|
|
405
467
|
return this.repository.search(keyword, { page, pageSize });
|
|
406
468
|
} catch (error) {
|
|
407
469
|
this.logger.error('Error searching knowledge', {
|
|
408
|
-
keyword,
|
|
470
|
+
keyword,
|
|
471
|
+
error: error.message,
|
|
409
472
|
});
|
|
410
473
|
throw error;
|
|
411
474
|
}
|
|
@@ -453,7 +516,8 @@ export class KnowledgeService {
|
|
|
453
516
|
return entry;
|
|
454
517
|
} catch (error) {
|
|
455
518
|
this.logger.error(`Error incrementing knowledge ${type}`, {
|
|
456
|
-
id,
|
|
519
|
+
id,
|
|
520
|
+
error: error.message,
|
|
457
521
|
});
|
|
458
522
|
throw error;
|
|
459
523
|
}
|
|
@@ -479,11 +543,11 @@ export class KnowledgeService {
|
|
|
479
543
|
// 更新 Quality 值对象
|
|
480
544
|
await this.repository.update(id, {
|
|
481
545
|
quality: JSON.stringify({
|
|
482
|
-
completeness:
|
|
483
|
-
adaptation:
|
|
546
|
+
completeness: result.dimensions.completeness,
|
|
547
|
+
adaptation: result.dimensions.metadata,
|
|
484
548
|
documentation: result.dimensions.format,
|
|
485
|
-
overall:
|
|
486
|
-
grade:
|
|
549
|
+
overall: result.score,
|
|
550
|
+
grade: result.grade,
|
|
487
551
|
}),
|
|
488
552
|
updatedAt: Math.floor(Date.now() / 1000),
|
|
489
553
|
});
|
|
@@ -496,13 +560,16 @@ export class KnowledgeService {
|
|
|
496
560
|
}
|
|
497
561
|
|
|
498
562
|
this.logger.info('Knowledge quality updated', {
|
|
499
|
-
id,
|
|
563
|
+
id,
|
|
564
|
+
score: result.score,
|
|
565
|
+
grade: result.grade,
|
|
500
566
|
});
|
|
501
567
|
|
|
502
568
|
return result;
|
|
503
569
|
} catch (error) {
|
|
504
570
|
this.logger.error('Error updating knowledge quality', {
|
|
505
|
-
id,
|
|
571
|
+
id,
|
|
572
|
+
error: error.message,
|
|
506
573
|
});
|
|
507
574
|
throw error;
|
|
508
575
|
}
|
|
@@ -522,30 +589,39 @@ export class KnowledgeService {
|
|
|
522
589
|
const result = entry[method](...entityArgs);
|
|
523
590
|
|
|
524
591
|
if (!result.success) {
|
|
525
|
-
throw new ConflictError(
|
|
526
|
-
result.error,
|
|
527
|
-
`Lifecycle ${method} failed for ${id}`,
|
|
528
|
-
);
|
|
592
|
+
throw new ConflictError(result.error, `Lifecycle ${method} failed for ${id}`);
|
|
529
593
|
}
|
|
530
594
|
|
|
531
595
|
// 构建 DB 更新
|
|
532
|
-
// 注意: 不在此处 JSON.stringify — repository.update() 内部
|
|
596
|
+
// 注意: 不在此处 JSON.stringify — repository.update() 内部
|
|
533
597
|
// 通过 _entityToRow() 统一执行序列化, 传入原始值即可
|
|
534
598
|
const dbUpdates = {
|
|
535
|
-
lifecycle:
|
|
536
|
-
lifecycleHistory:
|
|
537
|
-
updatedAt:
|
|
599
|
+
lifecycle: entry.lifecycle,
|
|
600
|
+
lifecycleHistory: entry.lifecycleHistory,
|
|
601
|
+
updatedAt: entry.updatedAt,
|
|
538
602
|
};
|
|
539
603
|
|
|
540
604
|
// 审核字段
|
|
541
|
-
if (entry.reviewedBy)
|
|
542
|
-
|
|
543
|
-
|
|
605
|
+
if (entry.reviewedBy) {
|
|
606
|
+
dbUpdates.reviewedBy = entry.reviewedBy;
|
|
607
|
+
}
|
|
608
|
+
if (entry.reviewedAt) {
|
|
609
|
+
dbUpdates.reviewedAt = entry.reviewedAt;
|
|
610
|
+
}
|
|
611
|
+
if (entry.rejectionReason !== null) {
|
|
612
|
+
dbUpdates.rejectionReason = entry.rejectionReason;
|
|
613
|
+
}
|
|
544
614
|
|
|
545
615
|
// 发布字段
|
|
546
|
-
if (entry.publishedAt)
|
|
547
|
-
|
|
548
|
-
|
|
616
|
+
if (entry.publishedAt) {
|
|
617
|
+
dbUpdates.publishedAt = entry.publishedAt;
|
|
618
|
+
}
|
|
619
|
+
if (entry.publishedBy) {
|
|
620
|
+
dbUpdates.publishedBy = entry.publishedBy;
|
|
621
|
+
}
|
|
622
|
+
if (entry.autoApprovable !== undefined) {
|
|
623
|
+
dbUpdates.autoApprovable = entry.autoApprovable ? 1 : 0;
|
|
624
|
+
}
|
|
549
625
|
|
|
550
626
|
const updated = await this.repository.update(id, dbUpdates);
|
|
551
627
|
|
|
@@ -555,7 +631,8 @@ export class KnowledgeService {
|
|
|
555
631
|
this._fileWriter.moveOnLifecycleChange(updated);
|
|
556
632
|
} catch (err) {
|
|
557
633
|
this.logger.warn('moveOnLifecycleChange failed (non-blocking)', {
|
|
558
|
-
id,
|
|
634
|
+
id,
|
|
635
|
+
error: err.message,
|
|
559
636
|
});
|
|
560
637
|
}
|
|
561
638
|
}
|
|
@@ -566,14 +643,17 @@ export class KnowledgeService {
|
|
|
566
643
|
});
|
|
567
644
|
|
|
568
645
|
this.logger.info(`Knowledge entry ${method}`, {
|
|
569
|
-
id,
|
|
646
|
+
id,
|
|
647
|
+
from: prevLifecycle,
|
|
648
|
+
to: entry.lifecycle,
|
|
570
649
|
actor: context.userId,
|
|
571
650
|
});
|
|
572
651
|
|
|
573
652
|
return updated;
|
|
574
653
|
} catch (error) {
|
|
575
654
|
this.logger.error(`Error in lifecycle ${method}`, {
|
|
576
|
-
id,
|
|
655
|
+
id,
|
|
656
|
+
error: error.message,
|
|
577
657
|
});
|
|
578
658
|
throw error;
|
|
579
659
|
}
|
|
@@ -611,15 +691,15 @@ export class KnowledgeService {
|
|
|
611
691
|
*/
|
|
612
692
|
_adaptForScorer(entry) {
|
|
613
693
|
return {
|
|
614
|
-
title:
|
|
615
|
-
trigger:
|
|
616
|
-
code:
|
|
617
|
-
language:
|
|
618
|
-
category:
|
|
619
|
-
summary:
|
|
694
|
+
title: entry.title,
|
|
695
|
+
trigger: entry.trigger,
|
|
696
|
+
code: entry.content?.pattern || entry.content?.markdown || '',
|
|
697
|
+
language: entry.language,
|
|
698
|
+
category: entry.category,
|
|
699
|
+
summary: entry.description || '',
|
|
620
700
|
usageGuide: entry.content?.markdown || entry.doClause || '',
|
|
621
|
-
headers:
|
|
622
|
-
tags:
|
|
701
|
+
headers: entry.headers || [],
|
|
702
|
+
tags: entry.tags || [],
|
|
623
703
|
};
|
|
624
704
|
}
|
|
625
705
|
|
|
@@ -632,30 +712,37 @@ export class KnowledgeService {
|
|
|
632
712
|
*/
|
|
633
713
|
async _autoDiscoverRelations(id, entry) {
|
|
634
714
|
const gs = this._knowledgeGraphService;
|
|
635
|
-
if (!gs)
|
|
715
|
+
if (!gs) {
|
|
716
|
+
return;
|
|
717
|
+
}
|
|
636
718
|
|
|
637
719
|
try {
|
|
638
720
|
const candidates = [];
|
|
639
721
|
|
|
640
|
-
//
|
|
722
|
+
// 仅与已发布 Recipe(active)建立关联,不与其他候选(pending)互关联
|
|
723
|
+
const activeOnly = { lifecycle: Lifecycle.ACTIVE };
|
|
724
|
+
|
|
725
|
+
// 按 moduleName 查同模块已发布条目
|
|
641
726
|
if (entry.moduleName) {
|
|
642
727
|
const sameModule = await this.repository.findWithPagination(
|
|
643
|
-
{ moduleName: entry.moduleName },
|
|
644
|
-
{ page: 1, pageSize: 20 }
|
|
728
|
+
{ ...activeOnly, moduleName: entry.moduleName },
|
|
729
|
+
{ page: 1, pageSize: 20 }
|
|
645
730
|
);
|
|
646
731
|
for (const r of sameModule.data) {
|
|
647
|
-
if (r.id !== id)
|
|
732
|
+
if (r.id !== id) {
|
|
733
|
+
candidates.push({ target: r.id, relation: 'related', weight: 0.8 });
|
|
734
|
+
}
|
|
648
735
|
}
|
|
649
736
|
}
|
|
650
737
|
|
|
651
|
-
// 按 category
|
|
738
|
+
// 按 category 查同类已发布条目(弱关联)
|
|
652
739
|
if (entry.category && candidates.length < 10) {
|
|
653
740
|
const sameCat = await this.repository.findWithPagination(
|
|
654
|
-
{ category: entry.category },
|
|
655
|
-
{ page: 1, pageSize: 10 }
|
|
741
|
+
{ ...activeOnly, category: entry.category },
|
|
742
|
+
{ page: 1, pageSize: 10 }
|
|
656
743
|
);
|
|
657
744
|
for (const r of sameCat.data) {
|
|
658
|
-
if (r.id !== id && !candidates.some(c => c.target === r.id)) {
|
|
745
|
+
if (r.id !== id && !candidates.some((c) => c.target === r.id)) {
|
|
659
746
|
candidates.push({ target: r.id, relation: 'related', weight: 0.4 });
|
|
660
747
|
}
|
|
661
748
|
}
|
|
@@ -665,19 +752,25 @@ export class KnowledgeService {
|
|
|
665
752
|
for (const c of candidates.slice(0, 10)) {
|
|
666
753
|
try {
|
|
667
754
|
gs.addEdge(id, 'knowledge', c.target, 'knowledge', c.relation, { weight: c.weight });
|
|
668
|
-
} catch {
|
|
755
|
+
} catch {
|
|
756
|
+
/* ignore duplicates */
|
|
757
|
+
}
|
|
669
758
|
}
|
|
670
759
|
|
|
671
760
|
// 将发现的关系写回 entry 的 relations 字段
|
|
672
761
|
if (candidates.length > 0) {
|
|
673
|
-
const relatedItems = candidates.slice(0, 10).map(c => ({
|
|
762
|
+
const relatedItems = candidates.slice(0, 10).map((c) => ({
|
|
674
763
|
target: c.target,
|
|
675
764
|
description: 'auto-discovered',
|
|
676
765
|
}));
|
|
677
|
-
const existingRelations =
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
766
|
+
const existingRelations =
|
|
767
|
+
typeof entry.relations?.toJSON === 'function'
|
|
768
|
+
? entry.relations.toJSON()
|
|
769
|
+
: entry.relations || {};
|
|
770
|
+
const merged = {
|
|
771
|
+
...existingRelations,
|
|
772
|
+
related: [...(existingRelations.related || []), ...relatedItems],
|
|
773
|
+
};
|
|
681
774
|
await this.repository.update(id, {
|
|
682
775
|
relations: JSON.stringify(merged),
|
|
683
776
|
updatedAt: Math.floor(Date.now() / 1000),
|
|
@@ -685,7 +778,8 @@ export class KnowledgeService {
|
|
|
685
778
|
}
|
|
686
779
|
} catch (err) {
|
|
687
780
|
this.logger.warn('Auto-discover relations failed (non-blocking)', {
|
|
688
|
-
id,
|
|
781
|
+
id,
|
|
782
|
+
error: err.message,
|
|
689
783
|
});
|
|
690
784
|
}
|
|
691
785
|
}
|
|
@@ -695,20 +789,26 @@ export class KnowledgeService {
|
|
|
695
789
|
*/
|
|
696
790
|
_syncRelationsToGraph(id, relations) {
|
|
697
791
|
const gs = this._knowledgeGraphService;
|
|
698
|
-
if (!gs)
|
|
792
|
+
if (!gs) {
|
|
793
|
+
return;
|
|
794
|
+
}
|
|
699
795
|
|
|
700
796
|
try {
|
|
701
|
-
gs.db
|
|
702
|
-
`DELETE FROM knowledge_edges WHERE from_id = ? AND from_type = 'knowledge'`
|
|
703
|
-
|
|
797
|
+
gs.db
|
|
798
|
+
.prepare(`DELETE FROM knowledge_edges WHERE from_id = ? AND from_type = 'knowledge'`)
|
|
799
|
+
.run(id);
|
|
704
800
|
|
|
705
|
-
if (!relations || typeof relations !== 'object')
|
|
801
|
+
if (!relations || typeof relations !== 'object') {
|
|
802
|
+
return;
|
|
803
|
+
}
|
|
706
804
|
|
|
707
805
|
// Relations 可能是 Relations 值对象或普通对象
|
|
708
806
|
const relObj = typeof relations.toJSON === 'function' ? relations.toJSON() : relations;
|
|
709
807
|
|
|
710
808
|
for (const [relType, targets] of Object.entries(relObj)) {
|
|
711
|
-
if (!Array.isArray(targets))
|
|
809
|
+
if (!Array.isArray(targets)) {
|
|
810
|
+
continue;
|
|
811
|
+
}
|
|
712
812
|
for (const t of targets) {
|
|
713
813
|
const targetId = t.target || t.id || (typeof t === 'string' ? t : null);
|
|
714
814
|
if (targetId) {
|
|
@@ -720,7 +820,8 @@ export class KnowledgeService {
|
|
|
720
820
|
}
|
|
721
821
|
} catch (err) {
|
|
722
822
|
this.logger.warn('Failed to sync relations to knowledge_edges', {
|
|
723
|
-
id,
|
|
823
|
+
id,
|
|
824
|
+
error: err.message,
|
|
724
825
|
});
|
|
725
826
|
}
|
|
726
827
|
}
|
|
@@ -730,12 +831,12 @@ export class KnowledgeService {
|
|
|
730
831
|
*/
|
|
731
832
|
_removeAllEdges(id) {
|
|
732
833
|
const gs = this._knowledgeGraphService;
|
|
733
|
-
if (!gs)
|
|
834
|
+
if (!gs) {
|
|
835
|
+
return;
|
|
836
|
+
}
|
|
734
837
|
|
|
735
838
|
try {
|
|
736
|
-
gs.db.prepare(
|
|
737
|
-
`DELETE FROM knowledge_edges WHERE from_id = ? OR to_id = ?`
|
|
738
|
-
).run(id, id);
|
|
839
|
+
gs.db.prepare(`DELETE FROM knowledge_edges WHERE from_id = ? OR to_id = ?`).run(id, id);
|
|
739
840
|
} catch (err) {
|
|
740
841
|
this.logger.warn('Failed to remove edges', { id, error: err.message });
|
|
741
842
|
}
|
|
@@ -747,20 +848,24 @@ export class KnowledgeService {
|
|
|
747
848
|
* 落盘到 .md 文件 + 回写 sourceFile
|
|
748
849
|
*/
|
|
749
850
|
_persistToFile(entry) {
|
|
750
|
-
if (!this._fileWriter)
|
|
851
|
+
if (!this._fileWriter) {
|
|
852
|
+
return;
|
|
853
|
+
}
|
|
751
854
|
try {
|
|
752
855
|
const oldSourceFile = entry.sourceFile;
|
|
753
856
|
this._fileWriter.persist(entry);
|
|
754
857
|
if (entry.sourceFile && entry.sourceFile !== oldSourceFile) {
|
|
755
|
-
this.repository.update(entry.id, { sourceFile: entry.sourceFile }).catch(err => {
|
|
858
|
+
this.repository.update(entry.id, { sourceFile: entry.sourceFile }).catch((err) => {
|
|
756
859
|
this.logger.warn('Failed to update sourceFile in DB', {
|
|
757
|
-
id: entry.id,
|
|
860
|
+
id: entry.id,
|
|
861
|
+
error: err.message,
|
|
758
862
|
});
|
|
759
863
|
});
|
|
760
864
|
}
|
|
761
865
|
} catch (err) {
|
|
762
866
|
this.logger.warn('Knowledge file persist failed (non-blocking)', {
|
|
763
|
-
id: entry?.id,
|
|
867
|
+
id: entry?.id,
|
|
868
|
+
error: err.message,
|
|
764
869
|
});
|
|
765
870
|
}
|
|
766
871
|
}
|
|
@@ -769,12 +874,15 @@ export class KnowledgeService {
|
|
|
769
874
|
* 删除 .md 文件
|
|
770
875
|
*/
|
|
771
876
|
_removeFile(entry) {
|
|
772
|
-
if (!this._fileWriter)
|
|
877
|
+
if (!this._fileWriter) {
|
|
878
|
+
return;
|
|
879
|
+
}
|
|
773
880
|
try {
|
|
774
881
|
this._fileWriter.remove(entry);
|
|
775
882
|
} catch (err) {
|
|
776
883
|
this.logger.warn('Knowledge file remove failed (non-blocking)', {
|
|
777
|
-
id: entry?.id,
|
|
884
|
+
id: entry?.id,
|
|
885
|
+
error: err.message,
|
|
778
886
|
});
|
|
779
887
|
}
|
|
780
888
|
}
|
|
@@ -795,7 +903,9 @@ export class KnowledgeService {
|
|
|
795
903
|
});
|
|
796
904
|
} catch (err) {
|
|
797
905
|
this.logger.warn('Audit log failed (non-blocking)', {
|
|
798
|
-
action,
|
|
906
|
+
action,
|
|
907
|
+
id,
|
|
908
|
+
error: err.message,
|
|
799
909
|
});
|
|
800
910
|
}
|
|
801
911
|
}
|