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.
Files changed (26) hide show
  1. package/dashboard/dist/assets/{index-D9fV5GGQ.js → index-Ciz4nd_4.js} +2 -2
  2. package/dashboard/dist/index.html +1 -1
  3. package/lib/external/mcp/handlers/bootstrap/MissionBriefingBuilder.js +36 -5
  4. package/lib/external/mcp/handlers/bootstrap/base-dimensions.js +5 -5
  5. package/lib/external/mcp/handlers/bootstrap/pipeline/EpisodicMemory.js +1 -1
  6. package/lib/external/mcp/handlers/bootstrap/pipeline/ToolResultCache.js +1 -1
  7. package/lib/external/mcp/handlers/bootstrap/pipeline/dimension-configs.js +2 -2
  8. package/lib/external/mcp/handlers/bootstrap/pipeline/noAiFallback.js +1 -1
  9. package/lib/external/mcp/handlers/bootstrap/shared/dimension-sop.js +670 -0
  10. package/lib/external/mcp/handlers/bootstrap/shared/dimension-text.js +2 -2
  11. package/lib/external/mcp/handlers/bootstrap/shared/skill-generator.js +86 -10
  12. package/lib/external/mcp/handlers/system.js +21 -2
  13. package/lib/external/mcp/tools.js +23 -10
  14. package/lib/platform/ios/xcode/XcodeIntegration.js +2 -2
  15. package/lib/service/candidate/CandidateAggregator.js +52 -0
  16. package/lib/service/chat/tools/ai-analysis.js +3 -3
  17. package/lib/service/chat/tools/ast-graph.js +2 -2
  18. package/lib/shared/RecipeReadinessChecker.js +13 -5
  19. package/package.json +1 -1
  20. package/scripts/migrate-md-to-knowledge.mjs +5 -2
  21. package/skills/autosnippet-analysis/SKILL.md +5 -5
  22. package/skills/autosnippet-coldstart/SKILL.md +75 -60
  23. package/skills/autosnippet-concepts/SKILL.md +2 -2
  24. package/skills/autosnippet-guard/SKILL.md +3 -3
  25. package/skills/autosnippet-intent/SKILL.md +1 -1
  26. 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 MAX_DUPLICATE_RATIO = 0.3;
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. 重复行比率 < 0.3(防止 AI 循环输出)
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. 重复行检测(AI 陷入循环输出工具提示等)
106
+ // 2. 重复检测 — 规范化后比较,避免结构性前缀导致误判
52
107
  const textLines = analysisText.split('\n').filter((l) => l.trim().length > 0);
53
- const uniqueLines = new Set(textLines.map((l) => l.trim()));
54
- const uniqueRatio = textLines.length > 0 ? uniqueLines.size / textLines.length : 1;
55
- if (textLines.length > 20 && uniqueRatio < MAX_DUPLICATE_RATIO) {
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 (${uniqueLines.size}/${textLines.length} unique, ratio ${uniqueRatio.toFixed(2)})`,
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, analysisText, referencedFiles, keyFindings, source);
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
- 'bootstrap(获取 Mission Briefing)',
228
- 'Agent 分析代码 + submit_knowledge × N',
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
- '必填: title, language, content(pattern或markdown + rationale), kind, doClause, dontClause, whenClause, coreCode, category, trigger, description, headers, usageGuide, knowledgeType, reasoning',
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: '内容值对象(必须有 pattern 或 markdown + rationale)',
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: '推理依据(必填){whyStandard, sources[], confidence}',
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: '批量提交知识条目(V3 统一实体)。每条须含 kind/doClause/dontClause/whenClause/coreCode。支持去重。',
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('./FileWatcher.js').FileWatcher} watcher
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('./FileWatcher.js').FileWatcher} watcher
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('../../external/mcp/handlers/bootstrap-internal.js');
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('../../external/mcp/handlers/candidate.js');
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('../../external/mcp/handlers/bootstrap-internal.js');
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('../../core/ast/ProjectGraph.js').default|null}
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('./../../service/knowledge/CodeEntityGraph.js');
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
- const hasCode = !!(item.code || item.content?.pattern || item.content?.markdown);
50
- if (!hasCode) {
49
+ // 先检查 content 是否为对象(Agent 常犯错误:传字符串而非对象)
50
+ if (item.content && typeof item.content !== 'object') {
51
51
  missing.push('content');
52
- suggestions.push('content 需包含 pattern markdown(代码片段或正文)');
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 必须包含 whyStandard + sources + confidence');
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "autosnippet",
3
- "version": "3.1.5",
3
+ "version": "3.1.7",
4
4
  "description": "Extract code patterns into a knowledge base for AI coding assistants",
5
5
  "type": "module",
6
6
  "main": "lib/bootstrap.js",
@@ -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
- const { parseRecipeMarkdown } = await import('../lib/service/recipe/RecipeFileWriter.js');
31
- const { parseCandidateMarkdown } = await import('../lib/service/candidate/CandidateFileWriter.js');
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(operation=scan)` 获取文件列表 + Guard 审计基线
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: Bootstrap 候选 AI 润色**
119
+ **方式 C: 候选字段诊断**
120
120
  ```
121
- autosnippet_bootstrap(operation=refine, candidateIds: ["id1", "id2", ...])
121
+ autosnippet_enrich_candidates({ candidateIds: ["id1", "id2", ...] })
122
122
  ```
123
- 使用 AI 对 Bootstrap 候选做内容润色(改善 summary、补充 insight、推断 relations、调整 confidence)。
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. 调用 `autosnippet_bootstrap(operation=refine)` 对候选做 AI 润色(summary/insight/relations)
136
+ 2. 调用 `autosnippet_enrich_candidates` 诊断候选缺失字段,根据结果自行补全
137
137
  3. 提交时使用 `autosnippet_submit_knowledge`(内置自动校验 + 去重检查)
138
138
  4. 报告补全情况 + 缺失字段 + 重复风险
139
139
  5. 如缺失字段 AI 无法填充,提示用户手动补充