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.
Files changed (115) hide show
  1. package/README.md +12 -12
  2. package/bin/cli.js +53 -40
  3. package/config/constitution.yaml +9 -2
  4. package/dashboard/dist/assets/{icons-CH-H9x0E.js → icons-D4IWpDIk.js} +105 -100
  5. package/dashboard/dist/assets/index-CWBNcF9z.css +1 -0
  6. package/dashboard/dist/assets/index-DHtzhbuG.js +120 -0
  7. package/dashboard/dist/index.html +3 -3
  8. package/lib/cli/AiScanService.js +35 -36
  9. package/lib/cli/KnowledgeSyncService.js +345 -0
  10. package/lib/cli/SetupService.js +8 -26
  11. package/lib/cli/UpgradeService.js +28 -0
  12. package/lib/core/gateway/GatewayActionRegistry.js +48 -58
  13. package/lib/domain/index.js +16 -11
  14. package/lib/domain/knowledge/KnowledgeEntry.js +289 -0
  15. package/lib/domain/knowledge/KnowledgeRepository.js +123 -0
  16. package/lib/domain/knowledge/Lifecycle.js +99 -0
  17. package/lib/domain/knowledge/index.js +27 -0
  18. package/lib/domain/knowledge/values/Constraints.js +128 -0
  19. package/lib/domain/knowledge/values/Content.js +69 -0
  20. package/lib/domain/knowledge/values/Quality.js +81 -0
  21. package/lib/domain/knowledge/values/Reasoning.js +70 -0
  22. package/lib/domain/knowledge/values/Relations.js +142 -0
  23. package/lib/domain/knowledge/values/Stats.js +72 -0
  24. package/lib/domain/knowledge/values/index.js +9 -0
  25. package/lib/external/ai/AiProvider.js +85 -11
  26. package/lib/external/mcp/McpServer.js +7 -5
  27. package/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +18 -2
  28. package/lib/external/mcp/handlers/bootstrap.js +116 -11
  29. package/lib/external/mcp/handlers/browse.js +76 -73
  30. package/lib/external/mcp/handlers/candidate.js +26 -275
  31. package/lib/external/mcp/handlers/guard.js +2 -0
  32. package/lib/external/mcp/handlers/knowledge.js +267 -0
  33. package/lib/external/mcp/handlers/structure.js +25 -23
  34. package/lib/external/mcp/handlers/system.js +10 -12
  35. package/lib/external/mcp/tools.js +134 -140
  36. package/lib/http/HttpServer.js +14 -8
  37. package/lib/http/routes/ai.js +4 -3
  38. package/lib/http/routes/extract.js +48 -4
  39. package/lib/http/routes/knowledge.js +246 -0
  40. package/lib/http/routes/search.js +12 -17
  41. package/lib/infrastructure/database/migrations/016_unified_knowledge_entries.js +395 -0
  42. package/lib/infrastructure/database/migrations/017_camelcase_knowledge_entries.js +107 -0
  43. package/lib/infrastructure/external/XcodeAutomation.js +187 -103
  44. package/lib/injection/ServiceContainer.js +69 -60
  45. package/lib/repository/knowledge/KnowledgeRepository.impl.js +338 -0
  46. package/lib/service/automation/DirectiveDetector.js +2 -3
  47. package/lib/service/automation/FileWatcher.js +59 -28
  48. package/lib/service/automation/XcodeIntegration.js +931 -156
  49. package/lib/service/automation/handlers/AlinkHandler.js +5 -4
  50. package/lib/service/automation/handlers/CreateHandler.js +53 -19
  51. package/lib/service/automation/handlers/DraftHandler.js +1 -1
  52. package/lib/service/automation/handlers/GuardHandler.js +183 -20
  53. package/lib/service/automation/handlers/SearchHandler.js +25 -22
  54. package/lib/service/candidate/SimilarityService.js +2 -2
  55. package/lib/service/chat/AnalystAgent.js +9 -0
  56. package/lib/service/chat/CandidateGuardrail.js +22 -11
  57. package/lib/service/chat/ChatAgent.js +132 -54
  58. package/lib/service/chat/ContextWindow.js +5 -5
  59. package/lib/service/chat/HandoffProtocol.js +1 -0
  60. package/lib/service/chat/ProducerAgent.js +40 -13
  61. package/lib/service/chat/ReasoningLayer.js +854 -0
  62. package/lib/service/chat/ReasoningTrace.js +329 -0
  63. package/lib/service/chat/tools.js +308 -205
  64. package/lib/service/cursor/CursorDeliveryPipeline.js +279 -0
  65. package/lib/service/cursor/KnowledgeCompressor.js +87 -0
  66. package/lib/service/cursor/RulesGenerator.js +168 -0
  67. package/lib/service/cursor/SkillsSyncer.js +268 -0
  68. package/lib/service/cursor/TokenBudget.js +58 -0
  69. package/lib/service/cursor/TopicClassifier.js +141 -0
  70. package/lib/service/guard/GuardCheckEngine.js +99 -10
  71. package/lib/service/guard/GuardService.js +57 -46
  72. package/lib/service/knowledge/ConfidenceRouter.js +159 -0
  73. package/lib/service/knowledge/KnowledgeFileWriter.js +595 -0
  74. package/lib/service/knowledge/KnowledgeService.js +802 -0
  75. package/lib/service/recipe/RecipeParser.js +3 -12
  76. package/lib/service/search/SearchEngine.js +67 -22
  77. package/lib/service/skills/SignalCollector.js +14 -9
  78. package/lib/service/skills/SkillAdvisor.js +13 -11
  79. package/lib/service/snippet/SnippetFactory.js +5 -5
  80. package/lib/service/spm/SpmService.js +15 -48
  81. package/lib/shared/RecipeReadinessChecker.js +6 -11
  82. package/package.json +1 -1
  83. package/scripts/install-cursor-skill.js +0 -6
  84. package/scripts/migrate-md-to-knowledge.mjs +364 -0
  85. package/skills/autosnippet-analysis/SKILL.md +15 -7
  86. package/skills/autosnippet-candidates/SKILL.md +8 -8
  87. package/skills/autosnippet-coldstart/SKILL.md +8 -4
  88. package/skills/autosnippet-concepts/SKILL.md +7 -6
  89. package/skills/autosnippet-create/SKILL.md +13 -13
  90. package/skills/autosnippet-intent/SKILL.md +3 -2
  91. package/skills/autosnippet-lifecycle/SKILL.md +5 -5
  92. package/skills/autosnippet-recipes/SKILL.md +18 -6
  93. package/templates/constitution.yaml +1 -1
  94. package/templates/copilot-instructions.md +6 -6
  95. package/templates/recipes-setup/README.md +3 -3
  96. package/dashboard/dist/assets/index-CqJRvYRL.js +0 -197
  97. package/dashboard/dist/assets/index-DICm9PNa.css +0 -1
  98. package/lib/cli/CandidateSyncService.js +0 -261
  99. package/lib/cli/SyncService.js +0 -356
  100. package/lib/domain/candidate/Candidate.js +0 -196
  101. package/lib/domain/candidate/CandidateRepository.js +0 -107
  102. package/lib/domain/candidate/Reasoning.js +0 -52
  103. package/lib/domain/recipe/Recipe.js +0 -421
  104. package/lib/domain/recipe/RecipeRepository.js +0 -54
  105. package/lib/domain/types/CandidateStatus.js +0 -52
  106. package/lib/http/routes/candidates.js +0 -559
  107. package/lib/http/routes/recipes.js +0 -397
  108. package/lib/repository/candidate/CandidateRepository.impl.js +0 -230
  109. package/lib/repository/recipe/RecipeRepository.impl.js +0 -498
  110. package/lib/service/candidate/CandidateAggregator.js +0 -52
  111. package/lib/service/candidate/CandidateFileWriter.js +0 -383
  112. package/lib/service/candidate/CandidateService.js +0 -1001
  113. package/lib/service/recipe/RecipeFileWriter.js +0 -514
  114. package/lib/service/recipe/RecipeService.js +0 -786
  115. 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 约束规则的生命周期(现在统一存储在 Recipe 实体中)
9
- * Guard 规则 = knowledgeType: 'boundary-constraint' 的 Recipe,
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
- constructor(recipeRepository, auditLogger, gateway) {
14
- this.recipeRepository = recipeRepository;
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 的 Recipe
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 recipe = new Recipe({
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
- knowledgeType: KnowledgeType.BOUNDARY_CONSTRAINT,
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
- status: RecipeStatus.ACTIVE,
53
+ lifecycle: 'active',
50
54
  createdBy: context.userId,
51
55
  });
52
56
 
53
- const created = await this.recipeRepository.create(recipe);
57
+ const created = await this.knowledgeRepository.create(entry);
54
58
 
55
59
  await this.auditLogger.log({
56
60
  action: 'create_guard_rule',
57
- resourceType: 'recipe',
61
+ resourceType: 'knowledge_entry',
58
62
  resourceId: created.id,
59
63
  actor: context.userId,
60
- details: `Created guard recipe: ${data.name}`,
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
- * 启用规则(将 Recipe 状态设为 ACTIVE
76
+ * 启用规则(将 lifecycle 设为 active
73
77
  */
74
78
  async enableRule(ruleId, context) {
75
79
  try {
76
- const recipe = await this.recipeRepository.findById(ruleId);
77
- if (!recipe) throw new NotFoundError('Guard recipe not found', 'recipe', ruleId);
78
- if (recipe.status === RecipeStatus.ACTIVE) {
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.recipeRepository.update(ruleId, { status: RecipeStatus.ACTIVE });
86
+ await this.knowledgeRepository.update(ruleId, { lifecycle: 'active' });
83
87
 
84
88
  await this.auditLogger.log({
85
89
  action: 'enable_guard_rule',
86
- resourceType: 'recipe',
90
+ resourceType: 'knowledge_entry',
87
91
  resourceId: ruleId,
88
92
  actor: context.userId,
89
- details: `Enabled guard recipe: ${recipe.title}`,
93
+ details: `Enabled guard rule: ${entry.title}`,
90
94
  timestamp: Math.floor(Date.now() / 1000),
91
95
  });
92
96
 
93
- return this.recipeRepository.findById(ruleId);
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
- * 禁用规则(将 Recipe 状态设为 DEPRECATED
105
+ * 禁用规则(将 lifecycle 设为 deprecated
102
106
  */
103
107
  async disableRule(ruleId, reason, context) {
104
108
  try {
105
- const recipe = await this.recipeRepository.findById(ruleId);
106
- if (!recipe) throw new NotFoundError('Guard recipe not found', 'recipe', ruleId);
107
- if (recipe.status === RecipeStatus.DEPRECATED) {
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.recipeRepository.update(ruleId, {
116
- status: RecipeStatus.DEPRECATED,
117
- deprecation_reason: reason,
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: 'recipe',
126
+ resourceType: 'knowledge_entry',
124
127
  resourceId: ruleId,
125
128
  actor: context.userId,
126
- details: `Disabled guard recipe: ${reason}`,
129
+ details: `Disabled guard rule: ${reason}`,
127
130
  timestamp: Math.floor(Date.now() / 1000),
128
131
  });
129
132
 
130
- return this.recipeRepository.findById(ruleId);
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
- // 获取所有包含 guards active Recipe
149
- const guardRecipes = await this.recipeRepository.findWithGuards(language);
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 recipe of guardRecipes) {
153
- for (const guard of (recipe.constraints?.guards || [])) {
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: recipe.id,
160
- ruleName: recipe.title,
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', { recipeId: recipe.id, error: e.message });
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
- * 查询规则列表(boundary-constraint Recipes)
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.recipeRepository.findByKnowledgeType(
191
- KnowledgeType.BOUNDARY_CONSTRAINT, { page, pageSize }
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
- // 搜索所有 Recipes 然后过滤 boundary-constraint
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 === KnowledgeType.BOUNDARY_CONSTRAINT
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
- // 基于 recipes 表的 boundary-constraint 统计
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;