autosnippet 2.9.0 → 2.11.0
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 +12 -12
- package/bin/cli.js +53 -40
- package/config/constitution.yaml +9 -2
- package/dashboard/dist/assets/{icons-CH-H9x0E.js → icons-D4IWpDIk.js} +105 -100
- package/dashboard/dist/assets/index-CWBNcF9z.css +1 -0
- package/dashboard/dist/assets/index-DHtzhbuG.js +120 -0
- package/dashboard/dist/index.html +3 -3
- package/lib/cli/AiScanService.js +35 -36
- package/lib/cli/KnowledgeSyncService.js +345 -0
- package/lib/cli/SetupService.js +8 -26
- package/lib/cli/UpgradeService.js +28 -0
- package/lib/core/gateway/GatewayActionRegistry.js +48 -58
- package/lib/domain/index.js +16 -11
- package/lib/domain/knowledge/KnowledgeEntry.js +289 -0
- package/lib/domain/knowledge/KnowledgeRepository.js +123 -0
- package/lib/domain/knowledge/Lifecycle.js +99 -0
- package/lib/domain/knowledge/index.js +27 -0
- package/lib/domain/knowledge/values/Constraints.js +128 -0
- package/lib/domain/knowledge/values/Content.js +69 -0
- package/lib/domain/knowledge/values/Quality.js +81 -0
- package/lib/domain/knowledge/values/Reasoning.js +70 -0
- package/lib/domain/knowledge/values/Relations.js +142 -0
- package/lib/domain/knowledge/values/Stats.js +72 -0
- package/lib/domain/knowledge/values/index.js +9 -0
- package/lib/external/ai/AiProvider.js +85 -11
- package/lib/external/mcp/McpServer.js +7 -5
- package/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +18 -2
- package/lib/external/mcp/handlers/bootstrap.js +116 -11
- package/lib/external/mcp/handlers/browse.js +76 -73
- package/lib/external/mcp/handlers/candidate.js +26 -275
- package/lib/external/mcp/handlers/guard.js +2 -0
- package/lib/external/mcp/handlers/knowledge.js +267 -0
- package/lib/external/mcp/handlers/structure.js +25 -23
- package/lib/external/mcp/handlers/system.js +10 -12
- package/lib/external/mcp/tools.js +134 -140
- package/lib/http/HttpServer.js +14 -8
- package/lib/http/routes/ai.js +4 -3
- package/lib/http/routes/extract.js +48 -4
- package/lib/http/routes/knowledge.js +246 -0
- package/lib/http/routes/search.js +12 -17
- package/lib/infrastructure/database/migrations/016_unified_knowledge_entries.js +395 -0
- package/lib/infrastructure/database/migrations/017_camelcase_knowledge_entries.js +107 -0
- package/lib/infrastructure/external/XcodeAutomation.js +187 -103
- package/lib/injection/ServiceContainer.js +69 -60
- package/lib/repository/knowledge/KnowledgeRepository.impl.js +338 -0
- package/lib/service/automation/DirectiveDetector.js +2 -3
- package/lib/service/automation/FileWatcher.js +59 -28
- package/lib/service/automation/XcodeIntegration.js +931 -156
- package/lib/service/automation/handlers/AlinkHandler.js +5 -4
- package/lib/service/automation/handlers/CreateHandler.js +53 -19
- package/lib/service/automation/handlers/DraftHandler.js +1 -1
- package/lib/service/automation/handlers/GuardHandler.js +183 -20
- package/lib/service/automation/handlers/SearchHandler.js +25 -22
- package/lib/service/candidate/SimilarityService.js +2 -2
- package/lib/service/chat/AnalystAgent.js +9 -0
- package/lib/service/chat/CandidateGuardrail.js +22 -11
- package/lib/service/chat/ChatAgent.js +132 -54
- package/lib/service/chat/ContextWindow.js +5 -5
- package/lib/service/chat/HandoffProtocol.js +1 -0
- package/lib/service/chat/ProducerAgent.js +40 -13
- package/lib/service/chat/ReasoningLayer.js +854 -0
- package/lib/service/chat/ReasoningTrace.js +329 -0
- package/lib/service/chat/tools.js +308 -205
- package/lib/service/cursor/CursorDeliveryPipeline.js +279 -0
- package/lib/service/cursor/KnowledgeCompressor.js +87 -0
- package/lib/service/cursor/RulesGenerator.js +168 -0
- package/lib/service/cursor/SkillsSyncer.js +268 -0
- package/lib/service/cursor/TokenBudget.js +58 -0
- package/lib/service/cursor/TopicClassifier.js +141 -0
- package/lib/service/guard/GuardCheckEngine.js +99 -10
- package/lib/service/guard/GuardService.js +57 -46
- package/lib/service/knowledge/ConfidenceRouter.js +159 -0
- package/lib/service/knowledge/KnowledgeFileWriter.js +595 -0
- package/lib/service/knowledge/KnowledgeService.js +802 -0
- package/lib/service/recipe/RecipeParser.js +3 -12
- package/lib/service/search/SearchEngine.js +67 -22
- package/lib/service/skills/SignalCollector.js +14 -9
- package/lib/service/skills/SkillAdvisor.js +13 -11
- package/lib/service/snippet/SnippetFactory.js +5 -5
- package/lib/service/spm/SpmService.js +15 -48
- package/lib/shared/RecipeReadinessChecker.js +6 -11
- package/package.json +1 -1
- package/scripts/install-cursor-skill.js +0 -6
- package/scripts/migrate-md-to-knowledge.mjs +364 -0
- package/skills/autosnippet-analysis/SKILL.md +15 -7
- package/skills/autosnippet-candidates/SKILL.md +8 -8
- package/skills/autosnippet-coldstart/SKILL.md +8 -4
- package/skills/autosnippet-concepts/SKILL.md +7 -6
- package/skills/autosnippet-create/SKILL.md +13 -13
- package/skills/autosnippet-intent/SKILL.md +3 -2
- package/skills/autosnippet-lifecycle/SKILL.md +5 -5
- package/skills/autosnippet-recipes/SKILL.md +18 -6
- package/templates/constitution.yaml +1 -1
- package/templates/copilot-instructions.md +6 -6
- package/templates/recipes-setup/README.md +3 -3
- package/dashboard/dist/assets/index-CqJRvYRL.js +0 -197
- package/dashboard/dist/assets/index-DICm9PNa.css +0 -1
- package/lib/cli/CandidateSyncService.js +0 -261
- package/lib/cli/SyncService.js +0 -356
- package/lib/domain/candidate/Candidate.js +0 -196
- package/lib/domain/candidate/CandidateRepository.js +0 -107
- package/lib/domain/candidate/Reasoning.js +0 -52
- package/lib/domain/recipe/Recipe.js +0 -421
- package/lib/domain/recipe/RecipeRepository.js +0 -54
- package/lib/domain/types/CandidateStatus.js +0 -52
- package/lib/http/routes/candidates.js +0 -559
- package/lib/http/routes/recipes.js +0 -397
- package/lib/repository/candidate/CandidateRepository.impl.js +0 -230
- package/lib/repository/recipe/RecipeRepository.impl.js +0 -498
- package/lib/service/candidate/CandidateAggregator.js +0 -52
- package/lib/service/candidate/CandidateFileWriter.js +0 -383
- package/lib/service/candidate/CandidateService.js +0 -1001
- package/lib/service/recipe/RecipeFileWriter.js +0 -514
- package/lib/service/recipe/RecipeService.js +0 -786
- package/lib/service/recipe/RecipeStatsTracker.js +0 -148
|
@@ -1,36 +1,40 @@
|
|
|
1
|
-
import { Recipe, RecipeStatus, KnowledgeType } from '../../domain/index.js';
|
|
2
1
|
import Logger from '../../infrastructure/logging/Logger.js';
|
|
3
2
|
import { ValidationError, ConflictError, NotFoundError } from '../../shared/errors/index.js';
|
|
4
3
|
import { v4 as uuidv4 } from 'uuid';
|
|
5
4
|
|
|
6
5
|
/**
|
|
7
6
|
* GuardService
|
|
8
|
-
* 管理 Guard
|
|
9
|
-
* Guard 规则 = knowledgeType
|
|
7
|
+
* 管理 Guard 约束规则的生命周期 (V3: 使用 KnowledgeEntry / knowledgeRepository)
|
|
8
|
+
* Guard 规则 = kind='rule' + knowledgeType='boundary-constraint' 的 KnowledgeEntry,
|
|
10
9
|
* 具体 pattern 存在 constraints.guards[] 里
|
|
11
10
|
*/
|
|
12
11
|
export class GuardService {
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
/**
|
|
13
|
+
* @param {import('../../domain/knowledge/KnowledgeRepository.js').KnowledgeRepository} knowledgeRepository
|
|
14
|
+
*/
|
|
15
|
+
constructor(knowledgeRepository, auditLogger, gateway) {
|
|
16
|
+
this.knowledgeRepository = knowledgeRepository;
|
|
15
17
|
this.auditLogger = auditLogger;
|
|
16
18
|
this.gateway = gateway;
|
|
17
19
|
this.logger = Logger.getInstance();
|
|
18
20
|
}
|
|
19
21
|
|
|
20
22
|
/**
|
|
21
|
-
* 创建新规则 → 创建一个 knowledgeType=boundary-constraint 的
|
|
23
|
+
* 创建新规则 → 创建一个 kind=rule, knowledgeType=boundary-constraint 的 KnowledgeEntry
|
|
22
24
|
*/
|
|
23
25
|
async createRule(data, context) {
|
|
24
26
|
try {
|
|
25
27
|
this._validateCreateInput(data);
|
|
26
28
|
|
|
27
|
-
const
|
|
29
|
+
const { KnowledgeEntry } = await import('../../domain/knowledge/KnowledgeEntry.js');
|
|
30
|
+
const entry = KnowledgeEntry.fromJSON({
|
|
28
31
|
id: uuidv4(),
|
|
29
32
|
title: data.name,
|
|
30
33
|
description: data.description,
|
|
31
34
|
language: (data.languages || [])[0] || '',
|
|
32
35
|
category: data.category || 'guard',
|
|
33
|
-
|
|
36
|
+
kind: 'rule',
|
|
37
|
+
knowledgeType: 'boundary-constraint',
|
|
34
38
|
content: {
|
|
35
39
|
pattern: data.pattern || '',
|
|
36
40
|
rationale: data.note || data.sourceReason || '',
|
|
@@ -46,18 +50,18 @@ export class GuardService {
|
|
|
46
50
|
}],
|
|
47
51
|
},
|
|
48
52
|
tags: data.languages || [],
|
|
49
|
-
|
|
53
|
+
lifecycle: 'active',
|
|
50
54
|
createdBy: context.userId,
|
|
51
55
|
});
|
|
52
56
|
|
|
53
|
-
const created = await this.
|
|
57
|
+
const created = await this.knowledgeRepository.create(entry);
|
|
54
58
|
|
|
55
59
|
await this.auditLogger.log({
|
|
56
60
|
action: 'create_guard_rule',
|
|
57
|
-
resourceType: '
|
|
61
|
+
resourceType: 'knowledge_entry',
|
|
58
62
|
resourceId: created.id,
|
|
59
63
|
actor: context.userId,
|
|
60
|
-
details: `Created guard
|
|
64
|
+
details: `Created guard rule: ${data.name}`,
|
|
61
65
|
timestamp: Math.floor(Date.now() / 1000),
|
|
62
66
|
});
|
|
63
67
|
|
|
@@ -69,28 +73,28 @@ export class GuardService {
|
|
|
69
73
|
}
|
|
70
74
|
|
|
71
75
|
/**
|
|
72
|
-
* 启用规则(将
|
|
76
|
+
* 启用规则(将 lifecycle 设为 active)
|
|
73
77
|
*/
|
|
74
78
|
async enableRule(ruleId, context) {
|
|
75
79
|
try {
|
|
76
|
-
const
|
|
77
|
-
if (!
|
|
78
|
-
if (
|
|
80
|
+
const entry = await this.knowledgeRepository.findById(ruleId);
|
|
81
|
+
if (!entry) throw new NotFoundError('Guard rule not found', 'knowledge_entry', ruleId);
|
|
82
|
+
if (entry.lifecycle === 'active') {
|
|
79
83
|
throw new ConflictError('Rule is already enabled', 'Cannot enable an already enabled rule');
|
|
80
84
|
}
|
|
81
85
|
|
|
82
|
-
await this.
|
|
86
|
+
await this.knowledgeRepository.update(ruleId, { lifecycle: 'active' });
|
|
83
87
|
|
|
84
88
|
await this.auditLogger.log({
|
|
85
89
|
action: 'enable_guard_rule',
|
|
86
|
-
resourceType: '
|
|
90
|
+
resourceType: 'knowledge_entry',
|
|
87
91
|
resourceId: ruleId,
|
|
88
92
|
actor: context.userId,
|
|
89
|
-
details: `Enabled guard
|
|
93
|
+
details: `Enabled guard rule: ${entry.title}`,
|
|
90
94
|
timestamp: Math.floor(Date.now() / 1000),
|
|
91
95
|
});
|
|
92
96
|
|
|
93
|
-
return this.
|
|
97
|
+
return this.knowledgeRepository.findById(ruleId);
|
|
94
98
|
} catch (error) {
|
|
95
99
|
this.logger.error('Error enabling guard rule', { ruleId, error: error.message });
|
|
96
100
|
throw error;
|
|
@@ -98,13 +102,13 @@ export class GuardService {
|
|
|
98
102
|
}
|
|
99
103
|
|
|
100
104
|
/**
|
|
101
|
-
* 禁用规则(将
|
|
105
|
+
* 禁用规则(将 lifecycle 设为 deprecated)
|
|
102
106
|
*/
|
|
103
107
|
async disableRule(ruleId, reason, context) {
|
|
104
108
|
try {
|
|
105
|
-
const
|
|
106
|
-
if (!
|
|
107
|
-
if (
|
|
109
|
+
const entry = await this.knowledgeRepository.findById(ruleId);
|
|
110
|
+
if (!entry) throw new NotFoundError('Guard rule not found', 'knowledge_entry', ruleId);
|
|
111
|
+
if (entry.lifecycle === 'deprecated') {
|
|
108
112
|
throw new ConflictError('Rule is already disabled', 'Cannot disable an already disabled rule');
|
|
109
113
|
}
|
|
110
114
|
|
|
@@ -112,22 +116,21 @@ export class GuardService {
|
|
|
112
116
|
throw new ValidationError('Disable reason is required');
|
|
113
117
|
}
|
|
114
118
|
|
|
115
|
-
await this.
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
deprecated_at: Math.floor(Date.now() / 1000),
|
|
119
|
+
await this.knowledgeRepository.update(ruleId, {
|
|
120
|
+
lifecycle: 'deprecated',
|
|
121
|
+
rejectionReason: reason,
|
|
119
122
|
});
|
|
120
123
|
|
|
121
124
|
await this.auditLogger.log({
|
|
122
125
|
action: 'disable_guard_rule',
|
|
123
|
-
resourceType: '
|
|
126
|
+
resourceType: 'knowledge_entry',
|
|
124
127
|
resourceId: ruleId,
|
|
125
128
|
actor: context.userId,
|
|
126
|
-
details: `Disabled guard
|
|
129
|
+
details: `Disabled guard rule: ${reason}`,
|
|
127
130
|
timestamp: Math.floor(Date.now() / 1000),
|
|
128
131
|
});
|
|
129
132
|
|
|
130
|
-
return this.
|
|
133
|
+
return this.knowledgeRepository.findById(ruleId);
|
|
131
134
|
} catch (error) {
|
|
132
135
|
this.logger.error('Error disabling guard rule', { ruleId, error: error.message });
|
|
133
136
|
throw error;
|
|
@@ -136,6 +139,7 @@ export class GuardService {
|
|
|
136
139
|
|
|
137
140
|
/**
|
|
138
141
|
* 检查代码是否匹配 Guard 规则
|
|
142
|
+
* 查询所有 active 的 rule 实体的 constraints.guards[]
|
|
139
143
|
*/
|
|
140
144
|
async checkCode(code, options = {}) {
|
|
141
145
|
try {
|
|
@@ -145,19 +149,27 @@ export class GuardService {
|
|
|
145
149
|
|
|
146
150
|
const { language = null } = options;
|
|
147
151
|
|
|
148
|
-
//
|
|
149
|
-
|
|
152
|
+
// V3: 使用 findActiveRules() 查询 kind='rule' + lifecycle='active'
|
|
153
|
+
let guardEntries = await this.knowledgeRepository.findActiveRules();
|
|
154
|
+
|
|
155
|
+
// 按语言过滤
|
|
156
|
+
if (language) {
|
|
157
|
+
guardEntries = guardEntries.filter(
|
|
158
|
+
e => !e.language || e.language === language
|
|
159
|
+
);
|
|
160
|
+
}
|
|
150
161
|
|
|
151
162
|
const matches = [];
|
|
152
|
-
for (const
|
|
153
|
-
|
|
163
|
+
for (const entry of guardEntries) {
|
|
164
|
+
const guards = entry.constraints?.guards || [];
|
|
165
|
+
for (const guard of guards) {
|
|
154
166
|
try {
|
|
155
167
|
const regex = new RegExp(guard.pattern, 'gm');
|
|
156
168
|
const codeMatches = [...code.matchAll(regex)];
|
|
157
169
|
if (codeMatches.length > 0) {
|
|
158
170
|
matches.push({
|
|
159
|
-
ruleId:
|
|
160
|
-
ruleName:
|
|
171
|
+
ruleId: entry.id,
|
|
172
|
+
ruleName: entry.title,
|
|
161
173
|
severity: guard.severity || 'warning',
|
|
162
174
|
message: guard.message || '',
|
|
163
175
|
matches: codeMatches.map(m => ({
|
|
@@ -169,7 +181,7 @@ export class GuardService {
|
|
|
169
181
|
});
|
|
170
182
|
}
|
|
171
183
|
} catch (e) {
|
|
172
|
-
this.logger.warn('Error matching guard pattern', {
|
|
184
|
+
this.logger.warn('Error matching guard pattern', { entryId: entry.id, error: e.message });
|
|
173
185
|
}
|
|
174
186
|
}
|
|
175
187
|
}
|
|
@@ -182,13 +194,14 @@ export class GuardService {
|
|
|
182
194
|
}
|
|
183
195
|
|
|
184
196
|
/**
|
|
185
|
-
*
|
|
197
|
+
* 查询规则列表 (kind='rule' + knowledgeType='boundary-constraint')
|
|
186
198
|
*/
|
|
187
199
|
async listRules(filters = {}, pagination = {}) {
|
|
188
200
|
try {
|
|
189
201
|
const { page = 1, pageSize = 20 } = pagination;
|
|
190
|
-
return this.
|
|
191
|
-
|
|
202
|
+
return this.knowledgeRepository.findWithPagination(
|
|
203
|
+
{ kind: 'rule', knowledgeType: 'boundary-constraint' },
|
|
204
|
+
{ page, pageSize }
|
|
192
205
|
);
|
|
193
206
|
} catch (error) {
|
|
194
207
|
this.logger.error('Error listing rules', { error: error.message, filters });
|
|
@@ -202,10 +215,9 @@ export class GuardService {
|
|
|
202
215
|
async searchRules(keyword, pagination = {}) {
|
|
203
216
|
try {
|
|
204
217
|
const { page = 1, pageSize = 20 } = pagination;
|
|
205
|
-
|
|
206
|
-
const result = await this.recipeRepository.search(keyword, { page, pageSize });
|
|
218
|
+
const result = await this.knowledgeRepository.search(keyword, { page, pageSize });
|
|
207
219
|
result.data = (result.data || []).filter(
|
|
208
|
-
r => r.knowledgeType ===
|
|
220
|
+
r => r.kind === 'rule' && r.knowledgeType === 'boundary-constraint'
|
|
209
221
|
);
|
|
210
222
|
result.total = result.data.length;
|
|
211
223
|
return result;
|
|
@@ -220,8 +232,7 @@ export class GuardService {
|
|
|
220
232
|
*/
|
|
221
233
|
async getRuleStats() {
|
|
222
234
|
try {
|
|
223
|
-
|
|
224
|
-
return this.recipeRepository.getStats();
|
|
235
|
+
return this.knowledgeRepository.getStats();
|
|
225
236
|
} catch (error) {
|
|
226
237
|
this.logger.error('Error getting rule stats', { error: error.message });
|
|
227
238
|
throw error;
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import Logger from '../../infrastructure/logging/Logger.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* ConfidenceRouter — 知识条目自动审核路由器
|
|
5
|
+
*
|
|
6
|
+
* 根据 KnowledgeEntry 的 reasoning.confidence、质量评分、
|
|
7
|
+
* 内容完整性等信号判断是否可自动审核通过。
|
|
8
|
+
*
|
|
9
|
+
* 路由结果:
|
|
10
|
+
* auto_approve — 置信度高、内容完整,自动通过 + fastTrack
|
|
11
|
+
* pending — 需要人工审核
|
|
12
|
+
* reject — 置信度过低或不满足基本要求
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const DEFAULT_CONFIG = {
|
|
16
|
+
/** 自动通过的最低 confidence 阈值 */
|
|
17
|
+
autoApproveThreshold: 0.85,
|
|
18
|
+
/** 自动驳回的 confidence 阈值 */
|
|
19
|
+
rejectThreshold: 0.2,
|
|
20
|
+
/** 需要的最少内容字符数 */
|
|
21
|
+
minContentLength: 20,
|
|
22
|
+
/** 自动通过要求 reasoning.isValid() */
|
|
23
|
+
requireReasoning: true,
|
|
24
|
+
/** 来源白名单(这些来源可以适用更宽松的阈值) */
|
|
25
|
+
trustedSources: ['bootstrap', 'cursor-scan'],
|
|
26
|
+
/** 可信来源的自动通过阈值 */
|
|
27
|
+
trustedAutoApproveThreshold: 0.7,
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export class ConfidenceRouter {
|
|
31
|
+
/**
|
|
32
|
+
* @param {Object} [config] - 路由配置
|
|
33
|
+
* @param {import('../quality/QualityScorer.js').QualityScorer} [qualityScorer]
|
|
34
|
+
*/
|
|
35
|
+
constructor(config = {}, qualityScorer = null) {
|
|
36
|
+
this._config = { ...DEFAULT_CONFIG, ...config };
|
|
37
|
+
this._qualityScorer = qualityScorer;
|
|
38
|
+
this.logger = Logger.getInstance();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* 路由决策
|
|
43
|
+
* @param {import('../../domain/knowledge/KnowledgeEntry.js').KnowledgeEntry} entry
|
|
44
|
+
* @returns {Promise<{ action: 'auto_approve'|'pending'|'reject', reason: string, confidence?: number }>}
|
|
45
|
+
*/
|
|
46
|
+
async route(entry) {
|
|
47
|
+
const confidence = entry.reasoning?.confidence ?? 0;
|
|
48
|
+
const source = entry.source || 'manual';
|
|
49
|
+
const isTrusted = this._config.trustedSources.includes(source);
|
|
50
|
+
|
|
51
|
+
// ── 阶段 1: 基本过滤 — 内容不完整直接 pending ──
|
|
52
|
+
if (!entry.isValid()) {
|
|
53
|
+
return {
|
|
54
|
+
action: 'pending',
|
|
55
|
+
reason: 'Content incomplete (title or content missing)',
|
|
56
|
+
confidence,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ── 阶段 2: 低置信度驳回 ──
|
|
61
|
+
if (confidence < this._config.rejectThreshold && confidence > 0) {
|
|
62
|
+
return {
|
|
63
|
+
action: 'reject',
|
|
64
|
+
reason: `Confidence too low: ${confidence.toFixed(2)} < ${this._config.rejectThreshold}`,
|
|
65
|
+
confidence,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ── 阶段 3: 内容最短长度检查 ──
|
|
70
|
+
const contentLength = this._estimateContentLength(entry);
|
|
71
|
+
if (contentLength < this._config.minContentLength) {
|
|
72
|
+
return {
|
|
73
|
+
action: 'pending',
|
|
74
|
+
reason: `Content too short: ${contentLength} chars < ${this._config.minContentLength}`,
|
|
75
|
+
confidence,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// ── 阶段 4: Reasoning 检查 ──
|
|
80
|
+
if (this._config.requireReasoning && !entry.reasoning?.isValid?.()) {
|
|
81
|
+
// 无 reasoning 不驳回,但进入人工审核
|
|
82
|
+
return {
|
|
83
|
+
action: 'pending',
|
|
84
|
+
reason: 'Reasoning not provided or invalid',
|
|
85
|
+
confidence,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// ── 阶段 5: 质量评分(可选) ──
|
|
90
|
+
let qualityScore = null;
|
|
91
|
+
if (this._qualityScorer) {
|
|
92
|
+
try {
|
|
93
|
+
const scorerInput = {
|
|
94
|
+
title: entry.title,
|
|
95
|
+
trigger: entry.trigger,
|
|
96
|
+
code: entry.content?.pattern || entry.content?.markdown || '',
|
|
97
|
+
language: entry.language,
|
|
98
|
+
category: entry.category,
|
|
99
|
+
summary: entry.summaryCn || entry.summaryEn || '',
|
|
100
|
+
usageGuide: entry.usageGuideCn || entry.usageGuideEn || '',
|
|
101
|
+
headers: entry.headers || [],
|
|
102
|
+
tags: entry.tags || [],
|
|
103
|
+
};
|
|
104
|
+
const result = this._qualityScorer.score(scorerInput);
|
|
105
|
+
qualityScore = result.score;
|
|
106
|
+
} catch {
|
|
107
|
+
// 评分失败不阻塞路由
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// ── 阶段 6: 自动通过判定 ──
|
|
112
|
+
const threshold = isTrusted
|
|
113
|
+
? this._config.trustedAutoApproveThreshold
|
|
114
|
+
: this._config.autoApproveThreshold;
|
|
115
|
+
|
|
116
|
+
if (confidence >= threshold) {
|
|
117
|
+
// 如果有质量评分且太低,降级到 pending
|
|
118
|
+
if (qualityScore !== null && qualityScore < 0.3) {
|
|
119
|
+
return {
|
|
120
|
+
action: 'pending',
|
|
121
|
+
reason: `Confidence OK (${confidence.toFixed(2)}) but quality low (${qualityScore.toFixed(2)})`,
|
|
122
|
+
confidence,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
action: 'auto_approve',
|
|
128
|
+
reason: `Confidence ${confidence.toFixed(2)} >= threshold ${threshold} (source: ${source})`,
|
|
129
|
+
confidence,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// ── 默认: 需要人工审核 ──
|
|
134
|
+
return {
|
|
135
|
+
action: 'pending',
|
|
136
|
+
reason: `Confidence ${confidence.toFixed(2)} < threshold ${threshold}`,
|
|
137
|
+
confidence,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* 估算内容长度
|
|
143
|
+
*/
|
|
144
|
+
_estimateContentLength(entry) {
|
|
145
|
+
const content = entry.content;
|
|
146
|
+
if (!content) return 0;
|
|
147
|
+
|
|
148
|
+
const parts = [
|
|
149
|
+
content.pattern,
|
|
150
|
+
content.rationale,
|
|
151
|
+
content.markdown,
|
|
152
|
+
...(content.steps || []).map(s => typeof s === 'string' ? s : s?.description || ''),
|
|
153
|
+
].filter(Boolean);
|
|
154
|
+
|
|
155
|
+
return parts.reduce((sum, p) => sum + p.length, 0);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export default ConfidenceRouter;
|