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
|
@@ -69,16 +69,13 @@ export class RecipeParser {
|
|
|
69
69
|
return {
|
|
70
70
|
title,
|
|
71
71
|
summary: frontmatter.summary || frontmatter.description || '',
|
|
72
|
-
|
|
73
|
-
summary_en: frontmatter.summary_en || '',
|
|
72
|
+
description: frontmatter.description || frontmatter.summary || '',
|
|
74
73
|
trigger: frontmatter.trigger || this.#generateTrigger(title),
|
|
75
74
|
category: frontmatter.category || 'general',
|
|
76
75
|
language: frontmatter.language || (codeBlocks[0]?.language !== 'text' ? codeBlocks[0]?.language : 'swift'),
|
|
77
76
|
code: codeBlocks.map(b => b.code).join('\n\n'),
|
|
78
77
|
codeBlocks,
|
|
79
78
|
usageGuide,
|
|
80
|
-
usageGuide_cn: frontmatter.usageGuide_cn || '',
|
|
81
|
-
usageGuide_en: frontmatter.usageGuide_en || '',
|
|
82
79
|
headers,
|
|
83
80
|
includeHeaders: headers.length > 0,
|
|
84
81
|
frontmatter,
|
|
@@ -169,16 +166,13 @@ export class RecipeParser {
|
|
|
169
166
|
items: [{
|
|
170
167
|
title,
|
|
171
168
|
summary: '',
|
|
172
|
-
|
|
173
|
-
summary_en: '',
|
|
169
|
+
description: '',
|
|
174
170
|
trigger: this.#generateTrigger(title),
|
|
175
171
|
category: 'Utility',
|
|
176
172
|
language,
|
|
177
173
|
code: content,
|
|
178
174
|
codeBlocks: [{ language, code: content }],
|
|
179
175
|
usageGuide: '',
|
|
180
|
-
usageGuide_cn: '',
|
|
181
|
-
usageGuide_en: '',
|
|
182
176
|
headers: this.#extractHeaders(content),
|
|
183
177
|
includeHeaders: false,
|
|
184
178
|
frontmatter: {},
|
|
@@ -252,16 +246,13 @@ export class RecipeParser {
|
|
|
252
246
|
return {
|
|
253
247
|
title,
|
|
254
248
|
summary: '',
|
|
255
|
-
|
|
256
|
-
summary_en: '',
|
|
249
|
+
description: '',
|
|
257
250
|
trigger: this.#generateTrigger(title),
|
|
258
251
|
category: 'Utility',
|
|
259
252
|
language,
|
|
260
253
|
code,
|
|
261
254
|
codeBlocks: codeBlocks.length > 0 ? codeBlocks : [{ language, code }],
|
|
262
255
|
usageGuide: '',
|
|
263
|
-
usageGuide_cn: '',
|
|
264
|
-
usageGuide_en: '',
|
|
265
256
|
headers: this.#extractHeaders(code),
|
|
266
257
|
includeHeaders: false,
|
|
267
258
|
frontmatter: {},
|
|
@@ -15,6 +15,8 @@ const BM25_B = 0.75;
|
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
17
|
* 分词: 中英文混合分词
|
|
18
|
+
* 英文: camelCase / PascalCase 拆分 + 小写化
|
|
19
|
+
* 中文: 单字 + 二元组(bigram)— 无需分词词典即可支持子串匹配
|
|
18
20
|
*/
|
|
19
21
|
export function tokenize(text) {
|
|
20
22
|
if (!text) return [];
|
|
@@ -23,7 +25,35 @@ export function tokenize(text) {
|
|
|
23
25
|
// 拆全大写前缀:URLSession → URL Session, UITableView → UI Table View
|
|
24
26
|
expanded = expanded.replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2');
|
|
25
27
|
const normalized = expanded.toLowerCase().replace(/[^\p{L}\p{N}\s_-]/gu, ' ');
|
|
26
|
-
const
|
|
28
|
+
const rawTokens = normalized.split(/[\s_-]+/).filter(t => t.length >= 1);
|
|
29
|
+
|
|
30
|
+
const tokens = [];
|
|
31
|
+
// CJK 正则(中日韩统一表意文字 + 扩展区)
|
|
32
|
+
const cjkRe = /[\u4e00-\u9fff\u3400-\u4dbf\uf900-\ufaff]/;
|
|
33
|
+
|
|
34
|
+
for (const raw of rawTokens) {
|
|
35
|
+
if (cjkRe.test(raw)) {
|
|
36
|
+
// 中文片段:提取所有 CJK 连续子串,生成单字 + bigram 覆盖
|
|
37
|
+
const cjkChars = raw.match(/[\u4e00-\u9fff\u3400-\u4dbf\uf900-\ufaff]+/g) || [];
|
|
38
|
+
for (const seg of cjkChars) {
|
|
39
|
+
// 单字
|
|
40
|
+
for (const ch of seg) tokens.push(ch);
|
|
41
|
+
// bigram
|
|
42
|
+
for (let i = 0; i < seg.length - 1; i++) tokens.push(seg[i] + seg[i + 1]);
|
|
43
|
+
// 完整片段(≥3 字时额外保留,提升精确匹配权重)
|
|
44
|
+
if (seg.length >= 3) tokens.push(seg);
|
|
45
|
+
}
|
|
46
|
+
// 非 CJK 部分(英文/数字)也保留
|
|
47
|
+
const nonCjk = raw.replace(/[\u4e00-\u9fff\u3400-\u4dbf\uf900-\ufaff]+/g, ' ').trim();
|
|
48
|
+
if (nonCjk) {
|
|
49
|
+
for (const t of nonCjk.split(/\s+/)) {
|
|
50
|
+
if (t.length >= 2) tokens.push(t);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
} else if (raw.length >= 2) {
|
|
54
|
+
tokens.push(raw);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
27
57
|
return [...new Set(tokens)];
|
|
28
58
|
}
|
|
29
59
|
|
|
@@ -126,13 +156,21 @@ export class SearchEngine {
|
|
|
126
156
|
this._cache.clear();
|
|
127
157
|
|
|
128
158
|
try {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
159
|
+
let entries = [];
|
|
160
|
+
|
|
161
|
+
try {
|
|
162
|
+
entries = this.db.prepare(
|
|
163
|
+
`SELECT id, title, description, language, category, knowledgeType, kind,
|
|
164
|
+
content AS content_json, lifecycle, tags AS tags_json, trigger
|
|
165
|
+
FROM knowledge_entries WHERE lifecycle != 'deprecated'`
|
|
166
|
+
).all();
|
|
167
|
+
entries = entries.map(e => ({
|
|
168
|
+
...e,
|
|
169
|
+
status: e.lifecycle,
|
|
170
|
+
}));
|
|
171
|
+
} catch { /* table may not exist */ }
|
|
134
172
|
|
|
135
|
-
for (const r of
|
|
173
|
+
for (const r of entries) {
|
|
136
174
|
let contentText = '';
|
|
137
175
|
try {
|
|
138
176
|
const content = JSON.parse(r.content_json || '{}');
|
|
@@ -141,14 +179,14 @@ export class SearchEngine {
|
|
|
141
179
|
// 包含 tags + trigger 提升召回率
|
|
142
180
|
let tagText = '';
|
|
143
181
|
try { tagText = JSON.parse(r.tags_json || '[]').join(' '); } catch { /* ignore */ }
|
|
144
|
-
const text = [r.title, r.description, r.trigger, r.language, r.category, r.
|
|
182
|
+
const text = [r.title, r.description, r.trigger, r.language, r.category, r.knowledgeType, tagText, contentText]
|
|
145
183
|
.filter(Boolean).join(' ');
|
|
146
|
-
this.scorer.addDocument(r.id, text, { type: '
|
|
184
|
+
this.scorer.addDocument(r.id, text, { type: 'knowledge', title: r.title, trigger: r.trigger || '', status: r.status, knowledgeType: r.knowledgeType, kind: r.kind || 'pattern', language: r.language || '', category: r.category || '' });
|
|
147
185
|
}
|
|
148
186
|
|
|
149
187
|
this._indexed = true;
|
|
150
188
|
this.logger.info('Search index built', {
|
|
151
|
-
|
|
189
|
+
entries: entries.length,
|
|
152
190
|
total: this.scorer.totalDocs,
|
|
153
191
|
});
|
|
154
192
|
} catch (err) {
|
|
@@ -229,14 +267,17 @@ export class SearchEngine {
|
|
|
229
267
|
const escaped = query.replace(/[%_\\]/g, ch => `\\${ch}`);
|
|
230
268
|
const pattern = `%${escaped}%`;
|
|
231
269
|
|
|
232
|
-
if (type === 'all' || type === 'recipe' || type === 'rule' || type === 'solution') {
|
|
270
|
+
if (type === 'all' || type === 'recipe' || type === 'knowledge' || type === 'rule' || type === 'solution') {
|
|
233
271
|
try {
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
272
|
+
let rows = [];
|
|
273
|
+
try {
|
|
274
|
+
rows = this.db.prepare(
|
|
275
|
+
`SELECT id, title, description, language, category, knowledgeType, kind, lifecycle as status, content AS content_json, trigger, headers, moduleName, 'knowledge' as type
|
|
276
|
+
FROM knowledge_entries
|
|
277
|
+
WHERE lifecycle != 'deprecated' AND (title LIKE ? ESCAPE '\\' OR description LIKE ? ESCAPE '\\' OR trigger LIKE ? ESCAPE '\\' OR content LIKE ? ESCAPE '\\')
|
|
278
|
+
LIMIT ?`
|
|
279
|
+
).all(pattern, pattern, pattern, pattern, limit);
|
|
280
|
+
} catch { /* table may not exist */ }
|
|
240
281
|
// 基础相关性排序:trigger 精确 > 标题匹配 > 描述匹配 > 内容匹配
|
|
241
282
|
const lowerQ = query.toLowerCase();
|
|
242
283
|
results.push(...rows.map(r => {
|
|
@@ -341,7 +382,7 @@ export class SearchEngine {
|
|
|
341
382
|
}
|
|
342
383
|
|
|
343
384
|
/**
|
|
344
|
-
* 补充详细字段(
|
|
385
|
+
* 补充详细字段(content / description / trigger)— 批量 IN 查询
|
|
345
386
|
* 用于向量搜索结果与 BM25 结果的一致性
|
|
346
387
|
*/
|
|
347
388
|
_supplementDetails(items) {
|
|
@@ -349,17 +390,21 @@ export class SearchEngine {
|
|
|
349
390
|
try {
|
|
350
391
|
const ids = items.map(it => it.id);
|
|
351
392
|
const placeholders = ids.map(() => '?').join(',');
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
393
|
+
let rows = [];
|
|
394
|
+
try {
|
|
395
|
+
rows = this.db.prepare(
|
|
396
|
+
`SELECT id, content AS content_json, description, trigger, headers, moduleName FROM knowledge_entries WHERE id IN (${placeholders})`
|
|
397
|
+
).all(...ids);
|
|
398
|
+
} catch { /* table may not exist */ }
|
|
355
399
|
const rowMap = new Map(rows.map(r => [r.id, r]));
|
|
356
400
|
for (const item of items) {
|
|
357
401
|
const row = rowMap.get(item.id);
|
|
358
402
|
if (row) {
|
|
359
403
|
item.content_json = row.content_json || null;
|
|
360
|
-
item.dimensions_json = row.dimensions_json || null;
|
|
361
404
|
item.description = item.description || row.description || '';
|
|
362
405
|
item.trigger = item.trigger || row.trigger || '';
|
|
406
|
+
if (row.headers) item.headers = row.headers;
|
|
407
|
+
if (row.moduleName) item.moduleName = row.moduleName;
|
|
363
408
|
}
|
|
364
409
|
}
|
|
365
410
|
} catch { /* DB may not be available */ }
|
|
@@ -336,11 +336,16 @@ export class SignalCollector {
|
|
|
336
336
|
#collectRecipeSignals() {
|
|
337
337
|
try {
|
|
338
338
|
if (!this.#db) return [];
|
|
339
|
+
// V3: knowledge_entries 统一表,统计字段在 stats/quality JSON 中
|
|
339
340
|
const rows = this.#db.prepare(
|
|
340
|
-
`SELECT id, title,
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
341
|
+
`SELECT id, title, knowledgeType, category, language,
|
|
342
|
+
json_extract(stats, '$.adoptions') as adoption_count,
|
|
343
|
+
json_extract(stats, '$.applications') as application_count,
|
|
344
|
+
json_extract(quality, '$.overall') as quality_overall,
|
|
345
|
+
updatedAt
|
|
346
|
+
FROM knowledge_entries
|
|
347
|
+
WHERE lifecycle = 'active'
|
|
348
|
+
ORDER BY updatedAt DESC LIMIT 30`
|
|
344
349
|
).all();
|
|
345
350
|
return rows;
|
|
346
351
|
} catch { return []; }
|
|
@@ -349,12 +354,12 @@ export class SignalCollector {
|
|
|
349
354
|
#collectCandidateSignals() {
|
|
350
355
|
try {
|
|
351
356
|
if (!this.#db) return [];
|
|
357
|
+
// V3: candidates 已合并到 knowledge_entries,lifecycle='pending' 即为候选
|
|
352
358
|
const rows = this.#db.prepare(
|
|
353
|
-
`SELECT id, source, status, language, category,
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
ORDER BY created_at DESC LIMIT 30`
|
|
359
|
+
`SELECT id, source, lifecycle as status, language, category,
|
|
360
|
+
title, createdAt
|
|
361
|
+
FROM knowledge_entries WHERE lifecycle = 'pending'
|
|
362
|
+
ORDER BY createdAt DESC LIMIT 30`
|
|
358
363
|
).all();
|
|
359
364
|
return rows;
|
|
360
365
|
} catch { return []; }
|
|
@@ -202,8 +202,8 @@ export class SkillAdvisor {
|
|
|
202
202
|
// 按 category 分布
|
|
203
203
|
const categories = this.#db.prepare(`
|
|
204
204
|
SELECT category, COUNT(*) as cnt
|
|
205
|
-
FROM
|
|
206
|
-
WHERE category IS NOT NULL AND category != ''
|
|
205
|
+
FROM knowledge_entries
|
|
206
|
+
WHERE lifecycle = 'active' AND category IS NOT NULL AND category != ''
|
|
207
207
|
GROUP BY category
|
|
208
208
|
ORDER BY cnt DESC
|
|
209
209
|
`).all();
|
|
@@ -211,8 +211,8 @@ export class SkillAdvisor {
|
|
|
211
211
|
// 按 language 分布
|
|
212
212
|
const languages = this.#db.prepare(`
|
|
213
213
|
SELECT language, COUNT(*) as cnt
|
|
214
|
-
FROM
|
|
215
|
-
WHERE language IS NOT NULL AND language != ''
|
|
214
|
+
FROM knowledge_entries
|
|
215
|
+
WHERE lifecycle = 'active' AND language IS NOT NULL AND language != ''
|
|
216
216
|
GROUP BY language
|
|
217
217
|
ORDER BY cnt DESC
|
|
218
218
|
`).all();
|
|
@@ -231,14 +231,15 @@ export class SkillAdvisor {
|
|
|
231
231
|
});
|
|
232
232
|
}
|
|
233
233
|
|
|
234
|
-
// 高使用量 Recipe 统计(
|
|
234
|
+
// 高使用量 Recipe 统计(V3: stats JSON 中的 adoptions + applications)
|
|
235
235
|
let hotRecipes = [];
|
|
236
236
|
try {
|
|
237
237
|
hotRecipes = this.#db.prepare(`
|
|
238
238
|
SELECT title, category,
|
|
239
|
-
(
|
|
240
|
-
FROM
|
|
241
|
-
WHERE
|
|
239
|
+
(COALESCE(json_extract(stats, '$.adoptions'), 0) + COALESCE(json_extract(stats, '$.applications'), 0)) as total_usage
|
|
240
|
+
FROM knowledge_entries
|
|
241
|
+
WHERE lifecycle = 'active'
|
|
242
|
+
AND (COALESCE(json_extract(stats, '$.adoptions'), 0) + COALESCE(json_extract(stats, '$.applications'), 0)) >= 5
|
|
242
243
|
ORDER BY total_usage DESC
|
|
243
244
|
LIMIT 10
|
|
244
245
|
`).all();
|
|
@@ -262,12 +263,13 @@ export class SkillAdvisor {
|
|
|
262
263
|
if (!this.#db) return { summary: 'DB 不可用', suggestions };
|
|
263
264
|
|
|
264
265
|
try {
|
|
266
|
+
// V3: candidates 已合并到 knowledge_entries,ifecycle='pending' 即为候选
|
|
265
267
|
const stats = this.#db.prepare(`
|
|
266
268
|
SELECT
|
|
267
269
|
COUNT(*) as total,
|
|
268
|
-
SUM(CASE WHEN
|
|
269
|
-
SUM(CASE WHEN
|
|
270
|
-
FROM
|
|
270
|
+
SUM(CASE WHEN lifecycle='pending' THEN 1 ELSE 0 END) as pending,
|
|
271
|
+
SUM(CASE WHEN lifecycle='deprecated' THEN 1 ELSE 0 END) as rejected
|
|
272
|
+
FROM knowledge_entries
|
|
271
273
|
`).get();
|
|
272
274
|
|
|
273
275
|
// 大量被拒绝 → 提示候选质量 Skill
|
|
@@ -45,16 +45,16 @@ const SNIPPET_TEMPLATE = `<?xml version="1.0" encoding="UTF-8"?>
|
|
|
45
45
|
|
|
46
46
|
export class SnippetFactory {
|
|
47
47
|
/**
|
|
48
|
-
* @param {object} [
|
|
48
|
+
* @param {object} [knowledgeRepository] — KnowledgeRepositoryImpl(可选,用于列表查询)
|
|
49
49
|
*/
|
|
50
|
-
constructor(
|
|
51
|
-
this._recipeRepo =
|
|
50
|
+
constructor(knowledgeRepository) {
|
|
51
|
+
this._recipeRepo = knowledgeRepository || null;
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
/**
|
|
55
|
-
* 运行时注入
|
|
55
|
+
* 运行时注入 knowledgeRepository(用于延迟绑定场景)
|
|
56
56
|
*/
|
|
57
|
-
|
|
57
|
+
setKnowledgeRepository(repo) {
|
|
58
58
|
this._recipeRepo = repo;
|
|
59
59
|
}
|
|
60
60
|
|
|
@@ -777,33 +777,25 @@ export class SpmService {
|
|
|
777
777
|
}
|
|
778
778
|
|
|
779
779
|
/**
|
|
780
|
-
*
|
|
780
|
+
* 共用增强管线:补充 extract_recipes handler 未覆盖的增量增强。
|
|
781
|
+
* extract_recipes handler 已负责:normalizeSemanticFields + RecipeExtractor + QualityScorer + V3 结构化。
|
|
782
|
+
* 此方法仅做以下增量处理:
|
|
783
|
+
* - 确保 normalizeSemanticFields 已执行(幂等,防止非 ChatAgent 路径跳过)
|
|
784
|
+
* - 若 handler 未注入质量评分,由 SpmService 补充
|
|
781
785
|
*/
|
|
782
786
|
_enrichRecipes(recipes) {
|
|
783
787
|
for (const recipe of recipes) {
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
if (this.#recipeExtractor && recipe.code) {
|
|
787
|
-
try {
|
|
788
|
-
const extracted = this.#recipeExtractor.extractFromContent(
|
|
789
|
-
recipe.code, `${recipe.title || 'unknown'}.${recipe.language || 'swift'}`, ''
|
|
790
|
-
);
|
|
791
|
-
if (extracted.semanticTags?.length > 0) {
|
|
792
|
-
recipe.tags = [...new Set([...(recipe.tags || []), ...extracted.semanticTags])];
|
|
793
|
-
}
|
|
794
|
-
if ((!recipe.category || recipe.category === 'Utility') && extracted.category && extracted.category !== 'general') {
|
|
795
|
-
recipe.category = extracted.category;
|
|
796
|
-
}
|
|
797
|
-
} catch (e) {
|
|
798
|
-
this.#logger.debug(`[SpmService] RecipeExtractor enrichment failed: ${e.message}`);
|
|
799
|
-
}
|
|
800
|
-
}
|
|
801
|
-
|
|
802
|
-
if (this.#qualityScorer) {
|
|
788
|
+
// QualityScorer 评分(程序化,仅在未评分时补充)
|
|
789
|
+
if (!recipe.quality && this.#qualityScorer) {
|
|
803
790
|
try {
|
|
804
791
|
const scoreResult = this.#qualityScorer.score(recipe);
|
|
805
|
-
recipe.
|
|
806
|
-
|
|
792
|
+
recipe.quality = {
|
|
793
|
+
completeness: 0,
|
|
794
|
+
adaptation: 0,
|
|
795
|
+
documentation: 0,
|
|
796
|
+
overall: scoreResult.score ?? 0,
|
|
797
|
+
grade: scoreResult.grade || '',
|
|
798
|
+
};
|
|
807
799
|
} catch (e) {
|
|
808
800
|
this.#logger.debug(`[SpmService] QualityScorer failed: ${e.message}`);
|
|
809
801
|
}
|
|
@@ -1157,32 +1149,7 @@ export class SpmService {
|
|
|
1157
1149
|
* - 确保 rationale / knowledgeType / complexity / scope 就位
|
|
1158
1150
|
*/
|
|
1159
1151
|
static normalizeSemanticFields(recipe) {
|
|
1160
|
-
//
|
|
1161
|
-
if (Array.isArray(recipe.preconditions) && recipe.preconditions.length > 0) {
|
|
1162
|
-
if (!recipe.constraints) recipe.constraints = {};
|
|
1163
|
-
if (!recipe.constraints.preconditions) {
|
|
1164
|
-
recipe.constraints.preconditions = recipe.preconditions;
|
|
1165
|
-
}
|
|
1166
|
-
delete recipe.preconditions;
|
|
1167
|
-
}
|
|
1168
|
-
|
|
1169
|
-
// steps: string[] → [{title, description}]
|
|
1170
|
-
if (Array.isArray(recipe.steps)) {
|
|
1171
|
-
recipe.steps = recipe.steps.map((s, i) => {
|
|
1172
|
-
if (typeof s === 'string') {
|
|
1173
|
-
return { title: `Step ${i + 1}`, description: s, code: '' };
|
|
1174
|
-
}
|
|
1175
|
-
return s; // already structured
|
|
1176
|
-
});
|
|
1177
|
-
}
|
|
1178
|
-
|
|
1179
|
-
// knowledgeType 默认值
|
|
1180
|
-
if (!recipe.knowledgeType) recipe.knowledgeType = 'code-pattern';
|
|
1181
|
-
// complexity 默认值
|
|
1182
|
-
if (!recipe.complexity) recipe.complexity = 'intermediate';
|
|
1183
|
-
// scope 默认值
|
|
1184
|
-
if (!recipe.scope) recipe.scope = 'project-specific';
|
|
1185
|
-
|
|
1152
|
+
// V3: AI 已输出完整结构,不再填充默认值
|
|
1186
1153
|
return recipe;
|
|
1187
1154
|
}
|
|
1188
1155
|
|
|
@@ -19,7 +19,7 @@ const WHITELISTED_CATEGORIES = ['bootstrap', 'knowledge', 'general'];
|
|
|
19
19
|
/**
|
|
20
20
|
* 检查候选是否具备直接提升为 Recipe 的所有必要字段。
|
|
21
21
|
*
|
|
22
|
-
* @param {object} item 扁平字段对象(title, trigger,
|
|
22
|
+
* @param {object} item 扁平字段对象(title, trigger, description …)
|
|
23
23
|
* —— MCP handler 传入 tool params;
|
|
24
24
|
* ChatAgent / bootstrap 需先从 metadata 展开。
|
|
25
25
|
* @returns {{ ready: boolean, missing: string[], suggestions: string[] }}
|
|
@@ -43,15 +43,10 @@ export function checkRecipeReadiness(item) {
|
|
|
43
43
|
suggestions.push(`trigger "${item.trigger}" 应以 @ 开头`);
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
const
|
|
47
|
-
if (!
|
|
48
|
-
missing.push('
|
|
49
|
-
suggestions.push('
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
if (!item.summary_en) {
|
|
53
|
-
missing.push('summary_en');
|
|
54
|
-
suggestions.push('请提供英文摘要(≤100 words),提升检索与 AI 理解');
|
|
46
|
+
const description = item.description || item.summary;
|
|
47
|
+
if (!description) {
|
|
48
|
+
missing.push('description');
|
|
49
|
+
suggestions.push('请提供描述(≤100字)');
|
|
55
50
|
}
|
|
56
51
|
|
|
57
52
|
if (!Array.isArray(item.headers) || item.headers.length === 0) {
|
|
@@ -60,7 +55,7 @@ export function checkRecipeReadiness(item) {
|
|
|
60
55
|
}
|
|
61
56
|
|
|
62
57
|
// ── 建议 ──
|
|
63
|
-
if (!item.usageGuide
|
|
58
|
+
if (!item.usageGuide) {
|
|
64
59
|
missing.push('usageGuide');
|
|
65
60
|
suggestions.push('请提供使用指南(Markdown ### 章节格式)');
|
|
66
61
|
}
|
package/package.json
CHANGED
|
@@ -22,7 +22,6 @@ const require = createRequire(import.meta.url);
|
|
|
22
22
|
import fs from 'node:fs';
|
|
23
23
|
import path from 'node:path';
|
|
24
24
|
import * as defaults from '../lib/infrastructure/config/Defaults.js';
|
|
25
|
-
import { RecipeStatsTracker } from '../lib/service/recipe/RecipeStatsTracker.js';
|
|
26
25
|
|
|
27
26
|
const autoSnippetRoot = path.join(__dirname, '..');
|
|
28
27
|
const skillsSource = path.join(autoSnippetRoot, 'skills');
|
|
@@ -156,11 +155,6 @@ function buildProjectRecipesContext(projectRoot) {
|
|
|
156
155
|
if (!fs.existsSync(recipesDir)) return null;
|
|
157
156
|
const mdFiles = collectMdFiles(recipesDir, recipesDir).sort();
|
|
158
157
|
if (mdFiles.length === 0) return null;
|
|
159
|
-
let stats = { byFile: {} };
|
|
160
|
-
try {
|
|
161
|
-
const tracker = new RecipeStatsTracker(projectRoot);
|
|
162
|
-
stats = tracker.getStats();
|
|
163
|
-
} catch (_) {}
|
|
164
158
|
|
|
165
159
|
const lines = [
|
|
166
160
|
'# Project Recipes Index\n\n',
|