autosnippet 3.1.5 → 3.1.7
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/dashboard/dist/assets/{index-D9fV5GGQ.js → index-Ciz4nd_4.js} +2 -2
- package/dashboard/dist/index.html +1 -1
- package/lib/external/mcp/handlers/bootstrap/MissionBriefingBuilder.js +36 -5
- package/lib/external/mcp/handlers/bootstrap/base-dimensions.js +5 -5
- package/lib/external/mcp/handlers/bootstrap/pipeline/EpisodicMemory.js +1 -1
- package/lib/external/mcp/handlers/bootstrap/pipeline/ToolResultCache.js +1 -1
- package/lib/external/mcp/handlers/bootstrap/pipeline/dimension-configs.js +2 -2
- package/lib/external/mcp/handlers/bootstrap/pipeline/noAiFallback.js +1 -1
- package/lib/external/mcp/handlers/bootstrap/shared/dimension-sop.js +670 -0
- package/lib/external/mcp/handlers/bootstrap/shared/dimension-text.js +2 -2
- package/lib/external/mcp/handlers/bootstrap/shared/skill-generator.js +86 -10
- package/lib/external/mcp/handlers/system.js +21 -2
- package/lib/external/mcp/tools.js +23 -10
- package/lib/platform/ios/xcode/XcodeIntegration.js +2 -2
- package/lib/service/candidate/CandidateAggregator.js +52 -0
- package/lib/service/chat/tools/ai-analysis.js +3 -3
- package/lib/service/chat/tools/ast-graph.js +2 -2
- package/lib/shared/RecipeReadinessChecker.js +13 -5
- package/package.json +1 -1
- package/scripts/migrate-md-to-knowledge.mjs +5 -2
- package/skills/autosnippet-analysis/SKILL.md +5 -5
- package/skills/autosnippet-coldstart/SKILL.md +75 -60
- package/skills/autosnippet-concepts/SKILL.md +2 -2
- package/skills/autosnippet-guard/SKILL.md +3 -3
- package/skills/autosnippet-intent/SKILL.md +1 -1
- package/templates/copilot-instructions.md +18 -2
|
@@ -20,24 +20,79 @@ const logger = Logger.getInstance();
|
|
|
20
20
|
/** Skill 分析文本最低字符数 */
|
|
21
21
|
const MIN_ANALYSIS_LENGTH = 100;
|
|
22
22
|
|
|
23
|
-
/**
|
|
24
|
-
const
|
|
23
|
+
/** 重复率硬拒绝阈值(全局唯一率低于此值 → 尝试去重挽救) */
|
|
24
|
+
const HARD_REJECT_RATIO = 0.10;
|
|
25
|
+
|
|
26
|
+
/** 连续重复块阈值(连续相同行 ≥ 此值 → AI 循环信号) */
|
|
27
|
+
const CONSECUTIVE_DUPE_THRESHOLD = 8;
|
|
25
28
|
|
|
26
29
|
/** 短文本结构化豁免阈值(低于此字符数的文本必须有结构化内容) */
|
|
27
30
|
const STRUCTURE_CHECK_THRESHOLD = 500;
|
|
28
31
|
|
|
29
32
|
// ── 质量门控 ────────────────────────────────────────────────
|
|
30
33
|
|
|
34
|
+
/**
|
|
35
|
+
* 规范化行文本 — 用于去重比较
|
|
36
|
+
* 去除列表标记、编号、代码围栏、引用标记等结构性前缀,
|
|
37
|
+
* 避免 category-scan 等维度中大量结构相似但内容不同的行被误判为重复。
|
|
38
|
+
*/
|
|
39
|
+
function normalizeLine(line) {
|
|
40
|
+
return line
|
|
41
|
+
.trim()
|
|
42
|
+
.replace(/^[-*•]\s+/, '') // strip list markers
|
|
43
|
+
.replace(/^\d+\.\s+/, '') // strip numbered list
|
|
44
|
+
.replace(/^[`>]+\s*/, '') // strip code/quote markers
|
|
45
|
+
.replace(/^#{1,3}\s+/, '') // strip Markdown headings
|
|
46
|
+
.replace(/\(来源[::].*?\)/g, '') // strip source annotations
|
|
47
|
+
.replace(/\s+/g, ' ') // collapse whitespace
|
|
48
|
+
.trim();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* 检测最长连续重复块长度 — AI 循环的核心特征
|
|
53
|
+
*/
|
|
54
|
+
function maxConsecutiveDuplicates(lines) {
|
|
55
|
+
let max = 0;
|
|
56
|
+
let current = 0;
|
|
57
|
+
for (let i = 1; i < lines.length; i++) {
|
|
58
|
+
if (lines[i] === lines[i - 1] && lines[i].length > 0) {
|
|
59
|
+
current++;
|
|
60
|
+
if (current > max) max = current;
|
|
61
|
+
} else {
|
|
62
|
+
current = 0;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return max;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* 去除连续重复行 — 将连续 N 行相同内容压缩为 1 行
|
|
70
|
+
*/
|
|
71
|
+
function deduplicateConsecutive(text) {
|
|
72
|
+
const lines = text.split('\n');
|
|
73
|
+
const result = [lines[0]];
|
|
74
|
+
for (let i = 1; i < lines.length; i++) {
|
|
75
|
+
if (lines[i].trim() !== lines[i - 1].trim() || lines[i].trim().length === 0) {
|
|
76
|
+
result.push(lines[i]);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return result.join('\n');
|
|
80
|
+
}
|
|
81
|
+
|
|
31
82
|
/**
|
|
32
83
|
* 验证分析文本是否满足 Skill 生成的质量门控
|
|
33
84
|
*
|
|
34
85
|
* 门控规则:
|
|
35
86
|
* 1. 文本长度 ≥ 100 字符
|
|
36
|
-
* 2.
|
|
87
|
+
* 2. 重复检测 (两层):
|
|
88
|
+
* a. 连续重复块 ≥ 8 行 → AI 循环信号 → 去重后挽救
|
|
89
|
+
* b. 规范化后全局唯一率 < 0.10 → 极端重复 → 去重后挽救
|
|
37
90
|
* 3. 短文本(<500 字符)必须包含结构化内容(标题/列表/代码块)
|
|
38
91
|
*
|
|
92
|
+
* 挽救机制: 当检测到重复时,先去除连续重复行,如果去重后文本 ≥ 100 字符则放行。
|
|
93
|
+
*
|
|
39
94
|
* @param {string} analysisText — Analyst 或外部 Agent 的分析文本
|
|
40
|
-
* @returns {{ pass: boolean, reason: string|null }}
|
|
95
|
+
* @returns {{ pass: boolean, reason: string|null, deduplicatedText?: string }}
|
|
41
96
|
*/
|
|
42
97
|
function validateSkillQuality(analysisText) {
|
|
43
98
|
// 1. 文本过短
|
|
@@ -48,14 +103,32 @@ function validateSkillQuality(analysisText) {
|
|
|
48
103
|
};
|
|
49
104
|
}
|
|
50
105
|
|
|
51
|
-
// 2.
|
|
106
|
+
// 2. 重复检测 — 规范化后比较,避免结构性前缀导致误判
|
|
52
107
|
const textLines = analysisText.split('\n').filter((l) => l.trim().length > 0);
|
|
53
|
-
const
|
|
54
|
-
const
|
|
55
|
-
|
|
108
|
+
const normalizedLines = textLines.map(normalizeLine).filter((l) => l.length > 0);
|
|
109
|
+
const uniqueNormalized = new Set(normalizedLines);
|
|
110
|
+
const uniqueRatio = normalizedLines.length > 0 ? uniqueNormalized.size / normalizedLines.length : 1;
|
|
111
|
+
|
|
112
|
+
// 连续重复块检测(AI 循环的核心特征)
|
|
113
|
+
const maxConsDupes = maxConsecutiveDuplicates(normalizedLines);
|
|
114
|
+
|
|
115
|
+
const isRepetitive = (normalizedLines.length > 30 && uniqueRatio < HARD_REJECT_RATIO)
|
|
116
|
+
|| maxConsDupes >= CONSECUTIVE_DUPE_THRESHOLD;
|
|
117
|
+
|
|
118
|
+
if (isRepetitive) {
|
|
119
|
+
// ── 挽救: 去除连续重复后重新评估 ──
|
|
120
|
+
const cleaned = deduplicateConsecutive(analysisText);
|
|
121
|
+
if (cleaned.trim().length >= MIN_ANALYSIS_LENGTH) {
|
|
122
|
+
logger.info(
|
|
123
|
+
`[SkillGenerator] Repetition detected (${uniqueNormalized.size}/${normalizedLines.length} unique, ` +
|
|
124
|
+
`ratio ${uniqueRatio.toFixed(2)}, maxConsec ${maxConsDupes}), salvaged via dedup ` +
|
|
125
|
+
`(${analysisText.length} → ${cleaned.length} chars)`
|
|
126
|
+
);
|
|
127
|
+
return { pass: true, reason: null, deduplicatedText: cleaned };
|
|
128
|
+
}
|
|
56
129
|
return {
|
|
57
130
|
pass: false,
|
|
58
|
-
reason: `repetitive content detected (${
|
|
131
|
+
reason: `repetitive content detected (${uniqueNormalized.size}/${normalizedLines.length} unique, ratio ${uniqueRatio.toFixed(2)}, maxConsec ${maxConsDupes}) — dedup salvage also too short (${cleaned.trim().length} chars)`,
|
|
59
132
|
};
|
|
60
133
|
}
|
|
61
134
|
|
|
@@ -156,8 +229,11 @@ export async function generateSkill(ctx, dim, analysisText, referencedFiles = []
|
|
|
156
229
|
return { success: false, skillName, error: validation.reason };
|
|
157
230
|
}
|
|
158
231
|
|
|
232
|
+
// 1.5. 如果触发了去重挽救,使用清理后的文本
|
|
233
|
+
const effectiveText = validation.deduplicatedText || analysisText;
|
|
234
|
+
|
|
159
235
|
// 2. 内容构建
|
|
160
|
-
const skillContent = buildSkillContent(dim,
|
|
236
|
+
const skillContent = buildSkillContent(dim, effectiveText, referencedFiles, keyFindings, source);
|
|
161
237
|
|
|
162
238
|
// 3. 创建 Skill
|
|
163
239
|
try {
|
|
@@ -106,6 +106,22 @@ export async function health(ctx) {
|
|
|
106
106
|
const allCritical = checks.database; // DB 是唯一硬性依赖
|
|
107
107
|
const status = allCritical ? 'ok' : 'degraded';
|
|
108
108
|
|
|
109
|
+
// 如果 DB 不可用但冷启动仍可执行,附加提示避免 Agent 浪费时间修复 DB
|
|
110
|
+
const actionHints = [];
|
|
111
|
+
if (!checks.database) {
|
|
112
|
+
actionHints.push(
|
|
113
|
+
'DB 不可用不影响冷启动:autosnippet_bootstrap 不依赖数据库(纯文件系统分析),可直接调用。DB 会在首次 submit_knowledge 时自动重试初始化。'
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
if (!knowledgeBase || knowledgeBase.recipes.total === 0) {
|
|
117
|
+
actionHints.push(
|
|
118
|
+
'知识库为空,建议执行冷启动:(1) 调用 autosnippet_bootstrap 获取 Mission Briefing → (2) 按维度分析代码并提交知识 → (3) 调用 autosnippet_dimension_complete 完成每个维度。'
|
|
119
|
+
);
|
|
120
|
+
actionHints.push(
|
|
121
|
+
'💡 冷启动前建议先加载 Skill 获取详细指引:autosnippet_skill({ operation: "load", name: "autosnippet-coldstart" })'
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
109
125
|
return envelope({
|
|
110
126
|
success: true,
|
|
111
127
|
data: {
|
|
@@ -118,6 +134,7 @@ export async function health(ctx) {
|
|
|
118
134
|
services: ctx.container.getServiceNames(),
|
|
119
135
|
knowledgeBase,
|
|
120
136
|
...(issues.length ? { issues } : {}),
|
|
137
|
+
...(actionHints.length ? { actionHints } : {}),
|
|
121
138
|
},
|
|
122
139
|
meta: { tool: 'autosnippet_health' },
|
|
123
140
|
});
|
|
@@ -224,8 +241,10 @@ export function capabilities() {
|
|
|
224
241
|
{
|
|
225
242
|
name: '冷启动(外部 Agent)',
|
|
226
243
|
steps: [
|
|
227
|
-
'
|
|
228
|
-
'
|
|
244
|
+
'⚠️ 先加载 Skill 获取详细指引: autosnippet_skill({ operation: "load", name: "autosnippet-coldstart" })',
|
|
245
|
+
'bootstrap(获取 Mission Briefing,无参数直接调用)',
|
|
246
|
+
'按 Briefing 中的 submissionSchema.example 格式提交知识(注意: content 和 reasoning 都是 JSON 对象)',
|
|
247
|
+
'Agent 分析代码 + submit_knowledge / submit_knowledge_batch × N',
|
|
229
248
|
'dimension_complete × N',
|
|
230
249
|
'wiki_plan → Agent 写文章 → wiki_finalize(可选)',
|
|
231
250
|
],
|
|
@@ -230,7 +230,9 @@ export const TOOLS = [
|
|
|
230
230
|
description:
|
|
231
231
|
'提交单条知识到知识库(V3 统一实体)。严格前置校验,缺少必要字段将被直接拒绝(不入库)。\n' +
|
|
232
232
|
'所有必填字段必须在单次调用中一次性提供,不要分步提交。\n' +
|
|
233
|
-
'
|
|
233
|
+
'⚠️ content 必须是对象: { "pattern": "代码片段", "markdown": "## 标题\\n正文...", "rationale": "设计原理" }(pattern/markdown 至少一个 + rationale 必填)\n' +
|
|
234
|
+
'⚠️ reasoning 必须是对象: { "whyStandard": "原因", "sources": ["file.ts"], "confidence": 0.85 }\n' +
|
|
235
|
+
'必填: title, language, content, kind, doClause, dontClause, whenClause, coreCode, category, trigger, description, headers, usageGuide, knowledgeType, reasoning',
|
|
234
236
|
inputSchema: {
|
|
235
237
|
type: 'object',
|
|
236
238
|
properties: {
|
|
@@ -239,15 +241,18 @@ export const TOOLS = [
|
|
|
239
241
|
language: { type: 'string', description: '编程语言(小写)' },
|
|
240
242
|
content: {
|
|
241
243
|
type: 'object',
|
|
242
|
-
description:
|
|
244
|
+
description:
|
|
245
|
+
'内容值对象(JSON 对象,不是字符串!)。必须包含: pattern(代码片段) 或 markdown(Markdown正文) 至少一个 + rationale(设计原理) 必填。' +
|
|
246
|
+
'示例: { "pattern": "func example() { ... }", "markdown": "## 标题\\n正文≥200字...", "rationale": "为什么这样设计" }',
|
|
243
247
|
properties: {
|
|
244
|
-
pattern: { type: 'string' },
|
|
245
|
-
markdown: { type: 'string' },
|
|
248
|
+
pattern: { type: 'string', description: '核心代码片段(与 markdown 至少提供一个)' },
|
|
249
|
+
markdown: { type: 'string', description: 'Markdown 正文(≥200字符,与 pattern 至少提供一个)' },
|
|
246
250
|
rationale: { type: 'string', description: '设计原理说明(必填)' },
|
|
247
251
|
steps: { type: 'array', items: { type: 'object' } },
|
|
248
252
|
codeChanges: { type: 'array', items: { type: 'object' } },
|
|
249
253
|
verification: { type: 'object' },
|
|
250
254
|
},
|
|
255
|
+
required: ['rationale'],
|
|
251
256
|
},
|
|
252
257
|
kind: {
|
|
253
258
|
type: 'string',
|
|
@@ -289,14 +294,16 @@ export const TOOLS = [
|
|
|
289
294
|
relations: { type: 'object', description: '关系' },
|
|
290
295
|
reasoning: {
|
|
291
296
|
type: 'object',
|
|
292
|
-
description:
|
|
297
|
+
description:
|
|
298
|
+
'推理依据(JSON 对象,必填)。示例: { "whyStandard": "28/30 files follow this pattern", "sources": ["src/UserService.ts"], "confidence": 0.85 }',
|
|
293
299
|
properties: {
|
|
294
|
-
whyStandard: { type: 'string' },
|
|
295
|
-
sources: { type: 'array', items: { type: 'string' } },
|
|
296
|
-
confidence: { type: 'number' },
|
|
300
|
+
whyStandard: { type: 'string', description: '为什么这是标准做法(必填)' },
|
|
301
|
+
sources: { type: 'array', items: { type: 'string' }, description: '参考的文件路径数组(必填,至少 1 个)' },
|
|
302
|
+
confidence: { type: 'number', description: '置信度 0.0-1.0(推荐 0.7-0.9)' },
|
|
297
303
|
qualitySignals: { type: 'object' },
|
|
298
304
|
alternatives: { type: 'array', items: { type: 'string' } },
|
|
299
305
|
},
|
|
306
|
+
required: ['whyStandard', 'sources', 'confidence'],
|
|
300
307
|
},
|
|
301
308
|
headerPaths: { type: 'array', items: { type: 'string' } },
|
|
302
309
|
moduleName: { type: 'string' },
|
|
@@ -330,7 +337,11 @@ export const TOOLS = [
|
|
|
330
337
|
{
|
|
331
338
|
name: 'autosnippet_submit_knowledge_batch',
|
|
332
339
|
tier: 'agent',
|
|
333
|
-
description:
|
|
340
|
+
description:
|
|
341
|
+
'批量提交知识条目(V3 统一实体)。每条字段要求同 submit_knowledge。支持去重。\n' +
|
|
342
|
+
'⚠️ items 数组中每条的 content 和 reasoning 都必须是 JSON 对象(不是字符串)。\n' +
|
|
343
|
+
'content 格式: { "pattern": "代码...", "markdown": "正文...", "rationale": "原理..." }\n' +
|
|
344
|
+
'reasoning 格式: { "whyStandard": "原因", "sources": ["file.ts"], "confidence": 0.85 }',
|
|
334
345
|
inputSchema: {
|
|
335
346
|
type: 'object',
|
|
336
347
|
properties: {
|
|
@@ -408,7 +419,9 @@ export const TOOLS = [
|
|
|
408
419
|
name: 'autosnippet_bootstrap',
|
|
409
420
|
tier: 'agent',
|
|
410
421
|
description:
|
|
411
|
-
'冷启动 Mission Briefing — 自动分析项目结构、AST、依赖图和 Guard
|
|
422
|
+
'冷启动 Mission Briefing — 自动分析项目结构、AST、依赖图和 Guard 审计,返回完整的执行计划和维度任务清单。无需任何参数,直接调用即可。不依赖数据库,DB 不可用时也能正常工作。\n' +
|
|
423
|
+
'💡 建议先加载 Skill 获取详细冷启动指引: autosnippet_skill({ operation: "load", name: "autosnippet-coldstart" })\n' +
|
|
424
|
+
'返回的 submissionSchema.example 包含完整的提交 JSON 示例,请严格按其格式提交知识。',
|
|
412
425
|
inputSchema: {
|
|
413
426
|
type: 'object',
|
|
414
427
|
properties: {},
|
|
@@ -101,7 +101,7 @@ const _SYSTEM_FRAMEWORKS = new Set([
|
|
|
101
101
|
* 3. Xcode osascript 自动插入,失败则文件写入回退
|
|
102
102
|
* 4. 附加 AutoSnippet 注释后缀
|
|
103
103
|
*
|
|
104
|
-
* @param {import('
|
|
104
|
+
* @param {import('../../../service/automation/FileWatcher.js').FileWatcher} watcher
|
|
105
105
|
* @param {string} fullPath 目标文件绝对路径
|
|
106
106
|
* @param {string[]} headers 待插入的 import 行数组
|
|
107
107
|
* @param {object} [opts]
|
|
@@ -296,7 +296,7 @@ export async function insertHeaders(watcher, fullPath, headers, opts = {}) {
|
|
|
296
296
|
* 7. Jump 到粘贴行 → 选中行内容 → Cmd+V 粘贴替换
|
|
297
297
|
* 8. 任一步失败 → 降级到纯文件写入
|
|
298
298
|
*
|
|
299
|
-
* @param {import('
|
|
299
|
+
* @param {import('../../../service/automation/FileWatcher.js').FileWatcher} watcher
|
|
300
300
|
*/
|
|
301
301
|
export async function insertCodeToXcode(watcher, fullPath, selected, triggerLine) {
|
|
302
302
|
const XA = await import('./XcodeAutomation.js');
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CandidateAggregator — 候选条目去重聚合
|
|
3
|
+
*
|
|
4
|
+
* 对候选列表按 title 进行模糊去重,保留最优条目。
|
|
5
|
+
* 被 knowledge handler 的 submitKnowledgeBatch 使用。
|
|
6
|
+
*
|
|
7
|
+
* @module service/candidate/CandidateAggregator
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { jaccardSimilarity, tokenizeForSimilarity } from '../../shared/similarity.js';
|
|
11
|
+
|
|
12
|
+
/** title 相似度阈值,超过此值视为重复 */
|
|
13
|
+
const TITLE_SIMILARITY_THRESHOLD = 0.85;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* 对候选条目列表进行去重聚合
|
|
17
|
+
*
|
|
18
|
+
* @param {Array<{title: string, code?: string, [key: string]: any}>} items
|
|
19
|
+
* @param {object} [opts]
|
|
20
|
+
* @param {number} [opts.threshold] 自定义相似度阈值 (0-1)
|
|
21
|
+
* @returns {{ items: Array, duplicates: Array<{item: any, duplicateOf: string}> }}
|
|
22
|
+
*/
|
|
23
|
+
export function aggregateCandidates(items, opts = {}) {
|
|
24
|
+
if (!Array.isArray(items) || items.length === 0) {
|
|
25
|
+
return { items: [], duplicates: [] };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const threshold = opts.threshold ?? TITLE_SIMILARITY_THRESHOLD;
|
|
29
|
+
const kept = [];
|
|
30
|
+
const duplicates = [];
|
|
31
|
+
|
|
32
|
+
for (const item of items) {
|
|
33
|
+
const titleTokens = tokenizeForSimilarity(item.title || '');
|
|
34
|
+
let isDuplicate = false;
|
|
35
|
+
|
|
36
|
+
for (const existing of kept) {
|
|
37
|
+
const existingTokens = tokenizeForSimilarity(existing.title || '');
|
|
38
|
+
const sim = jaccardSimilarity(titleTokens, existingTokens);
|
|
39
|
+
if (sim >= threshold) {
|
|
40
|
+
duplicates.push({ item, duplicateOf: existing.title });
|
|
41
|
+
isDuplicate = true;
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (!isDuplicate) {
|
|
47
|
+
kept.push(item);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return { items: kept, duplicates };
|
|
52
|
+
}
|
|
@@ -60,7 +60,7 @@ export const extractRecipes = {
|
|
|
60
60
|
// 加载语言参考 Skill(如有),注入到 AI 提取 prompt
|
|
61
61
|
let skillReference = null;
|
|
62
62
|
try {
|
|
63
|
-
const { loadBootstrapSkills } = await import('
|
|
63
|
+
const { loadBootstrapSkills } = await import('../../../external/mcp/handlers/bootstrap-internal.js');
|
|
64
64
|
const langProfile = ctx.aiProvider._detectLanguageProfile?.(files);
|
|
65
65
|
const primaryLang = langProfile?.primaryLanguage;
|
|
66
66
|
if (primaryLang) {
|
|
@@ -240,7 +240,7 @@ export const enrichCandidate = {
|
|
|
240
240
|
return { error: 'AI provider not available' };
|
|
241
241
|
}
|
|
242
242
|
// V3: 使用 MCP handler enrichCandidates 的逻辑
|
|
243
|
-
const { enrichCandidates: enrichFn } = await import('
|
|
243
|
+
const { enrichCandidates: enrichFn } = await import('../../../external/mcp/handlers/candidate.js');
|
|
244
244
|
const result = await enrichFn(ctx, { candidateIds: params.candidateIds });
|
|
245
245
|
return result?.data || result;
|
|
246
246
|
},
|
|
@@ -273,7 +273,7 @@ export const refineBootstrapCandidates = {
|
|
|
273
273
|
return { error: 'AI provider not available' };
|
|
274
274
|
}
|
|
275
275
|
// V3: 委托给 bootstrap handler 的 refine 逻辑
|
|
276
|
-
const { bootstrapRefine } = await import('
|
|
276
|
+
const { bootstrapRefine } = await import('../../../external/mcp/handlers/bootstrap-internal.js');
|
|
277
277
|
const result = await bootstrapRefine(ctx, {
|
|
278
278
|
candidateIds: params.candidateIds,
|
|
279
279
|
userPrompt: params.userPrompt,
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
/**
|
|
21
21
|
* 辅助: 安全获取 ProjectGraph 实例
|
|
22
22
|
* @param {object} ctx
|
|
23
|
-
* @returns {import('
|
|
23
|
+
* @returns {import('../../../core/ast/ProjectGraph.js').default|null}
|
|
24
24
|
*/
|
|
25
25
|
function _getProjectGraph(ctx) {
|
|
26
26
|
try {
|
|
@@ -558,7 +558,7 @@ export const queryCodeGraph = {
|
|
|
558
558
|
},
|
|
559
559
|
handler: async (params, ctx) => {
|
|
560
560
|
try {
|
|
561
|
-
const { CodeEntityGraph } = await import('
|
|
561
|
+
const { CodeEntityGraph } = await import('../../knowledge/CodeEntityGraph.js');
|
|
562
562
|
const db = ctx?.container?.get('database');
|
|
563
563
|
if (!db) {
|
|
564
564
|
return '代码实体图谱不可用: 数据库未初始化';
|
|
@@ -46,15 +46,21 @@ export function checkRecipeReadiness(item) {
|
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
// content 有效性(pattern/markdown/rationale 至少有实质内容)
|
|
49
|
-
|
|
50
|
-
if (
|
|
49
|
+
// 先检查 content 是否为对象(Agent 常犯错误:传字符串而非对象)
|
|
50
|
+
if (item.content && typeof item.content !== 'object') {
|
|
51
51
|
missing.push('content');
|
|
52
|
-
suggestions.push('content
|
|
52
|
+
suggestions.push('⚠️ content 必须是 JSON 对象(不是字符串!)。正确格式: { "pattern": "代码片段", "markdown": "## 标题\\n正文...", "rationale": "设计原理" }');
|
|
53
|
+
} else {
|
|
54
|
+
const hasCode = !!(item.code || item.content?.pattern || item.content?.markdown);
|
|
55
|
+
if (!hasCode) {
|
|
56
|
+
missing.push('content');
|
|
57
|
+
suggestions.push('content 必须是 JSON 对象且包含 pattern 或 markdown 至少一个。格式: { "pattern": "代码片段...", "rationale": "设计原理..." }');
|
|
58
|
+
}
|
|
53
59
|
}
|
|
54
60
|
|
|
55
61
|
if (!item.content?.rationale && !item.rationale) {
|
|
56
62
|
missing.push('rationale');
|
|
57
|
-
suggestions.push('content.rationale
|
|
63
|
+
suggestions.push('content.rationale 必须提供设计原理说明(在 content 对象中: { ..., "rationale": "为什么这样设计" })');
|
|
58
64
|
}
|
|
59
65
|
|
|
60
66
|
if (!item.language) {
|
|
@@ -130,13 +136,15 @@ export function checkRecipeReadiness(item) {
|
|
|
130
136
|
// reasoning 检查
|
|
131
137
|
if (!item.reasoning || typeof item.reasoning !== 'object') {
|
|
132
138
|
missing.push('reasoning');
|
|
133
|
-
suggestions.push('reasoning
|
|
139
|
+
suggestions.push('⚠️ reasoning 必须是 JSON 对象(不是字符串!)。正确格式: { "whyStandard": "为什么是标准做法", "sources": ["file1.ts", "file2.ts"], "confidence": 0.85 }');
|
|
134
140
|
} else {
|
|
135
141
|
if (!item.reasoning.whyStandard?.trim()) {
|
|
136
142
|
missing.push('reasoning.whyStandard');
|
|
143
|
+
suggestions.push('reasoning.whyStandard 必须说明为什么这是标准做法');
|
|
137
144
|
}
|
|
138
145
|
if (!Array.isArray(item.reasoning.sources) || item.reasoning.sources.length === 0) {
|
|
139
146
|
missing.push('reasoning.sources');
|
|
147
|
+
suggestions.push('reasoning.sources 必须是文件路径数组且至少包含 1 个来源');
|
|
140
148
|
}
|
|
141
149
|
}
|
|
142
150
|
|
package/package.json
CHANGED
|
@@ -27,8 +27,11 @@ const noBackup = args.includes('--no-backup');
|
|
|
27
27
|
const projectRoot = args.find((a) => !a.startsWith('--')) || process.cwd();
|
|
28
28
|
|
|
29
29
|
// ── 动态导入(兼容 ESM) ──
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
// NOTE: 旧的 RecipeFileWriter / CandidateFileWriter 已统一为 KnowledgeFileWriter
|
|
31
|
+
// parseKnowledgeMarkdown 兼容解析旧格式
|
|
32
|
+
const { parseKnowledgeMarkdown } = await import('../lib/service/knowledge/KnowledgeFileWriter.js');
|
|
33
|
+
const parseRecipeMarkdown = parseKnowledgeMarkdown;
|
|
34
|
+
const parseCandidateMarkdown = parseKnowledgeMarkdown;
|
|
32
35
|
const { KnowledgeEntry } = await import('../lib/domain/knowledge/KnowledgeEntry.js');
|
|
33
36
|
const { KnowledgeFileWriter } = await import('../lib/service/knowledge/KnowledgeFileWriter.js');
|
|
34
37
|
const { Lifecycle } = await import('../lib/domain/knowledge/Lifecycle.js');
|
|
@@ -26,7 +26,7 @@ description: Deep project analysis — full scan + semantic field enrichment + g
|
|
|
26
26
|
### Workflow
|
|
27
27
|
|
|
28
28
|
#### Phase 1: Collect & Baseline
|
|
29
|
-
1. 调用 `autosnippet_bootstrap
|
|
29
|
+
1. 调用 `autosnippet_bootstrap`(无参数)获取 Mission Briefing(文件列表 + Guard 审计基线)
|
|
30
30
|
2. 审视 Guard 违规 — 静态规则问题 (命名/API 使用/废弃 API)
|
|
31
31
|
3. 确定高优先级文件 (核心模块/共享工具/Service 层)
|
|
32
32
|
|
|
@@ -116,11 +116,11 @@ Use `autosnippet_submit_knowledge_batch` for batch submission.
|
|
|
116
116
|
**方式 B: 对已有候选调用 AI 补全**
|
|
117
117
|
> 注:`autosnippet_enrich_candidates` 已移入 admin 层级,Agent 推荐使用方式 A 直接填写完整字段。
|
|
118
118
|
|
|
119
|
-
**方式 C:
|
|
119
|
+
**方式 C: 候选字段诊断**
|
|
120
120
|
```
|
|
121
|
-
|
|
121
|
+
autosnippet_enrich_candidates({ candidateIds: ["id1", "id2", ...] })
|
|
122
122
|
```
|
|
123
|
-
|
|
123
|
+
诊断候选缺失字段(不使用 AI),Agent 根据诊断结果自行补全。
|
|
124
124
|
|
|
125
125
|
---
|
|
126
126
|
|
|
@@ -133,7 +133,7 @@ autosnippet_bootstrap(operation=refine, candidateIds: ["id1", "id2", ...])
|
|
|
133
133
|
### Workflow
|
|
134
134
|
|
|
135
135
|
1. 对目标候选做结构补齐(确保所有字段完整)
|
|
136
|
-
2. 调用 `
|
|
136
|
+
2. 调用 `autosnippet_enrich_candidates` 诊断候选缺失字段,根据结果自行补全
|
|
137
137
|
3. 提交时使用 `autosnippet_submit_knowledge`(内置自动校验 + 去重检查)
|
|
138
138
|
4. 报告补全情况 + 缺失字段 + 重复风险
|
|
139
139
|
5. 如缺失字段 AI 无法填充,提示用户手动补充
|