autosnippet 2.8.3 → 2.10.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 (110) hide show
  1. package/README.md +5 -5
  2. package/bin/cli.js +5 -33
  3. package/config/constitution.yaml +9 -2
  4. package/dashboard/dist/assets/{icons-B_Xg4B-s.js → icons-BkT3XrKf.js} +105 -100
  5. package/dashboard/dist/assets/index-BsB7DzW4.css +1 -0
  6. package/dashboard/dist/assets/index-DdmQMrJJ.js +155 -0
  7. package/dashboard/dist/index.html +3 -3
  8. package/lib/cli/AiScanService.js +13 -11
  9. package/lib/cli/KnowledgeSyncService.js +343 -0
  10. package/lib/cli/SetupService.js +9 -27
  11. package/lib/core/ast/ProjectGraph.js +160 -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 +351 -0
  15. package/lib/domain/knowledge/KnowledgeRepository.js +123 -0
  16. package/lib/domain/knowledge/Lifecycle.js +109 -0
  17. package/lib/domain/knowledge/index.js +27 -0
  18. package/lib/domain/knowledge/values/Constraints.js +125 -0
  19. package/lib/domain/knowledge/values/Content.js +86 -0
  20. package/lib/domain/knowledge/values/Quality.js +93 -0
  21. package/lib/domain/knowledge/values/Reasoning.js +69 -0
  22. package/lib/domain/knowledge/values/Relations.js +168 -0
  23. package/lib/domain/knowledge/values/Stats.js +87 -0
  24. package/lib/domain/knowledge/values/index.js +9 -0
  25. package/lib/external/ai/AiProvider.js +48 -0
  26. package/lib/external/ai/providers/GoogleGeminiProvider.js +12 -3
  27. package/lib/external/mcp/McpServer.js +7 -5
  28. package/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +3 -2
  29. package/lib/external/mcp/handlers/bootstrap.js +121 -12
  30. package/lib/external/mcp/handlers/browse.js +77 -73
  31. package/lib/external/mcp/handlers/candidate.js +29 -276
  32. package/lib/external/mcp/handlers/guard.js +2 -0
  33. package/lib/external/mcp/handlers/knowledge.js +205 -0
  34. package/lib/external/mcp/handlers/skill.js +4 -2
  35. package/lib/external/mcp/handlers/structure.js +25 -23
  36. package/lib/external/mcp/handlers/system.js +10 -12
  37. package/lib/external/mcp/tools.js +125 -138
  38. package/lib/http/HttpServer.js +4 -8
  39. package/lib/http/middleware/requestLogger.js +3 -3
  40. package/lib/http/routes/ai.js +17 -1
  41. package/lib/http/routes/extract.js +48 -4
  42. package/lib/http/routes/knowledge.js +246 -0
  43. package/lib/http/routes/search.js +12 -17
  44. package/lib/http/routes/skills.js +44 -1
  45. package/lib/infrastructure/cache/GraphCache.js +143 -0
  46. package/lib/infrastructure/database/migrations/015_create_token_usage.js +27 -0
  47. package/lib/infrastructure/database/migrations/016_unified_knowledge_entries.js +395 -0
  48. package/lib/infrastructure/external/XcodeAutomation.js +187 -103
  49. package/lib/infrastructure/realtime/RealtimeService.js +14 -2
  50. package/lib/injection/ServiceContainer.js +164 -63
  51. package/lib/repository/knowledge/KnowledgeRepository.impl.js +373 -0
  52. package/lib/repository/token/TokenUsageStore.js +162 -0
  53. package/lib/service/automation/DirectiveDetector.js +2 -3
  54. package/lib/service/automation/FileWatcher.js +67 -28
  55. package/lib/service/automation/XcodeIntegration.js +931 -156
  56. package/lib/service/automation/handlers/AlinkHandler.js +6 -4
  57. package/lib/service/automation/handlers/CreateHandler.js +53 -18
  58. package/lib/service/automation/handlers/GuardHandler.js +183 -20
  59. package/lib/service/automation/handlers/SearchHandler.js +35 -17
  60. package/lib/service/chat/AnalystAgent.js +25 -14
  61. package/lib/service/chat/CandidateGuardrail.js +1 -1
  62. package/lib/service/chat/ChatAgent.js +280 -48
  63. package/lib/service/chat/ContextWindow.js +92 -8
  64. package/lib/service/chat/HandoffProtocol.js +26 -1
  65. package/lib/service/chat/ProducerAgent.js +11 -9
  66. package/lib/service/chat/tools.js +298 -194
  67. package/lib/service/guard/GuardCheckEngine.js +114 -10
  68. package/lib/service/guard/GuardService.js +59 -48
  69. package/lib/service/knowledge/ConfidenceRouter.js +159 -0
  70. package/lib/service/knowledge/KnowledgeFileWriter.js +602 -0
  71. package/lib/service/knowledge/KnowledgeService.js +725 -0
  72. package/lib/service/search/SearchEngine.js +92 -19
  73. package/lib/service/skills/SignalCollector.js +15 -9
  74. package/lib/service/skills/SkillAdvisor.js +13 -11
  75. package/lib/service/snippet/SnippetFactory.js +5 -5
  76. package/lib/service/spm/SpmService.js +119 -18
  77. package/package.json +1 -1
  78. package/scripts/install-cursor-skill.js +0 -6
  79. package/scripts/migrate-md-to-knowledge.mjs +364 -0
  80. package/skills/autosnippet-analysis/SKILL.md +15 -7
  81. package/skills/autosnippet-candidates/SKILL.md +6 -6
  82. package/skills/autosnippet-coldstart/SKILL.md +7 -3
  83. package/skills/autosnippet-concepts/SKILL.md +7 -6
  84. package/skills/autosnippet-create/SKILL.md +13 -13
  85. package/skills/autosnippet-intent/SKILL.md +3 -2
  86. package/skills/autosnippet-lifecycle/SKILL.md +5 -5
  87. package/skills/autosnippet-recipes/SKILL.md +16 -4
  88. package/templates/constitution.yaml +1 -1
  89. package/templates/copilot-instructions.md +6 -6
  90. package/templates/recipes-setup/README.md +3 -3
  91. package/dashboard/dist/assets/index-CkIih2CC.css +0 -1
  92. package/dashboard/dist/assets/index-Duc8Qk-c.js +0 -197
  93. package/lib/cli/CandidateSyncService.js +0 -261
  94. package/lib/cli/SyncService.js +0 -356
  95. package/lib/domain/candidate/Candidate.js +0 -196
  96. package/lib/domain/candidate/CandidateRepository.js +0 -107
  97. package/lib/domain/candidate/Reasoning.js +0 -52
  98. package/lib/domain/recipe/Recipe.js +0 -421
  99. package/lib/domain/recipe/RecipeRepository.js +0 -54
  100. package/lib/domain/types/CandidateStatus.js +0 -52
  101. package/lib/http/routes/candidates.js +0 -559
  102. package/lib/http/routes/recipes.js +0 -397
  103. package/lib/repository/candidate/CandidateRepository.impl.js +0 -230
  104. package/lib/repository/recipe/RecipeRepository.impl.js +0 -498
  105. package/lib/service/candidate/CandidateAggregator.js +0 -52
  106. package/lib/service/candidate/CandidateFileWriter.js +0 -383
  107. package/lib/service/candidate/CandidateService.js +0 -973
  108. package/lib/service/recipe/RecipeFileWriter.js +0 -514
  109. package/lib/service/recipe/RecipeService.js +0 -786
  110. package/lib/service/recipe/RecipeStatsTracker.js +0 -148
@@ -0,0 +1,168 @@
1
+ /**
2
+ * Relations — 关系图值对象
3
+ *
4
+ * 统一为分桶结构(非扁平数组)。
5
+ * 每个桶存储 [{ target, description }] 格式的关系列表。
6
+ */
7
+
8
+ /** 所有合法的关系桶名 (snake_case) */
9
+ export const RELATION_BUCKETS = [
10
+ 'inherits', // 继承
11
+ 'implements', // 实现接口/协议
12
+ 'calls', // 调用
13
+ 'depends_on', // 依赖
14
+ 'data_flow', // 数据流向
15
+ 'conflicts', // 冲突
16
+ 'extends', // 扩展
17
+ 'related', // 弱关联
18
+ 'alternative', // 替代方案
19
+ 'prerequisite', // 前置条件
20
+ 'deprecated_by', // 被取代
21
+ 'solves', // 解决问题
22
+ 'enforces', // 强制约束
23
+ 'references', // 引用
24
+ ];
25
+
26
+ /** camelCase → snake_case 桶名映射(兼容旧数据) */
27
+ const LEGACY_BUCKET_MAP = {
28
+ dependsOn: 'depends_on',
29
+ dataFlow: 'data_flow',
30
+ deprecatedBy: 'deprecated_by',
31
+ dataFlowTo: 'data_flow',
32
+ };
33
+
34
+ export class Relations {
35
+ constructor(buckets = {}) {
36
+ /** @type {Object.<string, Array<{target:string, description:string}>>} */
37
+ this._b = {};
38
+ for (const k of RELATION_BUCKETS) {
39
+ // 同时尝试 snake_case 和 camelCase 的 key
40
+ const vals = buckets[k] || [];
41
+ this._b[k] = vals.map(r => ({
42
+ target: r.target || '',
43
+ description: r.description || '',
44
+ }));
45
+ }
46
+ // 处理旧 camelCase key
47
+ for (const [legacy, canonical] of Object.entries(LEGACY_BUCKET_MAP)) {
48
+ if (buckets[legacy]?.length > 0) {
49
+ for (const r of buckets[legacy]) {
50
+ if (!this._b[canonical].some(x => x.target === r.target)) {
51
+ this._b[canonical].push({
52
+ target: r.target || '',
53
+ description: r.description || '',
54
+ });
55
+ }
56
+ }
57
+ }
58
+ }
59
+ }
60
+
61
+ /**
62
+ * 从任意输入构造 Relations
63
+ * @param {Relations|Array|Object|null} input
64
+ * @returns {Relations}
65
+ */
66
+ static from(input) {
67
+ if (input instanceof Relations) return input;
68
+ if (!input) return new Relations();
69
+ // 扁平数组 → 自动分桶(兼容旧 Candidate relations)
70
+ if (Array.isArray(input)) return Relations.fromFlat(input);
71
+ return new Relations(input);
72
+ }
73
+
74
+ /**
75
+ * 从扁平数组 [{type, target, description}] 构建分桶
76
+ * @param {Array<{type:string, target:string, description?:string}>} arr
77
+ * @returns {Relations}
78
+ */
79
+ static fromFlat(arr) {
80
+ const buckets = {};
81
+ for (const rel of arr) {
82
+ const bucket = LEGACY_BUCKET_MAP[rel.type] || rel.type || 'related';
83
+ if (!buckets[bucket]) buckets[bucket] = [];
84
+ buckets[bucket].push({
85
+ target: rel.target || '',
86
+ description: rel.description || '',
87
+ });
88
+ }
89
+ return new Relations(buckets);
90
+ }
91
+
92
+ /**
93
+ * 扁平视图(仅 Dashboard 渲染用)
94
+ * @returns {Array<{type:string, target:string, description:string}>}
95
+ */
96
+ toFlatArray() {
97
+ const result = [];
98
+ for (const [type, list] of Object.entries(this._b)) {
99
+ for (const r of list) {
100
+ result.push({ type, ...r });
101
+ }
102
+ }
103
+ return result;
104
+ }
105
+
106
+ /**
107
+ * 获取指定桶
108
+ * @param {string} type
109
+ * @returns {Array<{target:string, description:string}>}
110
+ */
111
+ getByType(type) {
112
+ return this._b[type] || [];
113
+ }
114
+
115
+ /**
116
+ * 是否为空
117
+ * @returns {boolean}
118
+ */
119
+ isEmpty() {
120
+ return Object.values(this._b).every(l => l.length === 0);
121
+ }
122
+
123
+ /**
124
+ * 添加关系
125
+ * @param {string} type 桶名
126
+ * @param {string} target 目标
127
+ * @param {string} description
128
+ * @returns {Relations}
129
+ */
130
+ add(type, target, description = '') {
131
+ if (!this._b[type]) this._b[type] = [];
132
+ if (!this._b[type].some(r => r.target === target)) {
133
+ this._b[type].push({ target, description });
134
+ }
135
+ return this;
136
+ }
137
+
138
+ /**
139
+ * 移除关系
140
+ * @param {string} type 桶名
141
+ * @param {string} target 目标
142
+ * @returns {Relations}
143
+ */
144
+ remove(type, target) {
145
+ if (this._b[type]) {
146
+ this._b[type] = this._b[type].filter(r => r.target !== target);
147
+ }
148
+ return this;
149
+ }
150
+
151
+ /**
152
+ * 转换为 wire format JSON (分桶)
153
+ */
154
+ toJSON() {
155
+ return { ...this._b };
156
+ }
157
+
158
+ /**
159
+ * 从 wire format 创建
160
+ * @param {Object|Array} data
161
+ * @returns {Relations}
162
+ */
163
+ static fromJSON(data) {
164
+ return Relations.from(data);
165
+ }
166
+ }
167
+
168
+ export default Relations;
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Stats — 统计值对象
3
+ *
4
+ * 记录知识条目的使用统计:浏览、采用、应用、Guard 命中、搜索命中、权威分。
5
+ */
6
+ export class Stats {
7
+ constructor(props = {}) {
8
+ /** @type {number} 浏览次数 */
9
+ this.views = props.views ?? 0;
10
+ /** @type {number} 采用次数 */
11
+ this.adoptions = props.adoptions ?? 0;
12
+ /** @type {number} 应用次数 */
13
+ this.applications = props.applications ?? 0;
14
+ /** @type {number} Guard 命中次数 */
15
+ this.guardHits = props.guard_hits ?? props.guardHits ?? 0;
16
+ /** @type {number} 搜索命中次数 */
17
+ this.searchHits = props.search_hits ?? props.searchHits ?? 0;
18
+ /** @type {number} 权威分 0-5 */
19
+ this.authority = props.authority ?? 0;
20
+ }
21
+
22
+ /**
23
+ * 从任意输入构造 Stats
24
+ * @param {Stats|Object|null} input
25
+ * @returns {Stats}
26
+ */
27
+ static from(input) {
28
+ if (input instanceof Stats) return input;
29
+ return new Stats(input || {});
30
+ }
31
+
32
+ /**
33
+ * 从旧 Recipe statistics 字段映射
34
+ * @param {Object} old
35
+ * @param {number} qualityOverall 用于初始化 authority
36
+ * @returns {Stats}
37
+ */
38
+ static fromLegacyRecipe(old, qualityOverall = 0) {
39
+ if (!old) return new Stats();
40
+ return new Stats({
41
+ views: old.viewCount ?? old.views ?? 0,
42
+ adoptions: old.adoptionCount ?? old.adoptions ?? 0,
43
+ applications: old.applicationCount ?? old.applications ?? 0,
44
+ guard_hits: old.guardHitCount ?? old.guardHits ?? 0,
45
+ search_hits: old.searchHits ?? 0,
46
+ authority: qualityOverall * 5,
47
+ });
48
+ }
49
+
50
+ /**
51
+ * 增加计数
52
+ * @param {'views'|'adoptions'|'applications'|'guardHits'|'searchHits'} counter
53
+ * @param {number} delta
54
+ * @returns {Stats}
55
+ */
56
+ increment(counter, delta = 1) {
57
+ if (counter in this && typeof this[counter] === 'number') {
58
+ this[counter] += delta;
59
+ }
60
+ return this;
61
+ }
62
+
63
+ /**
64
+ * 转换为 wire format JSON (snake_case)
65
+ */
66
+ toJSON() {
67
+ return {
68
+ views: this.views,
69
+ adoptions: this.adoptions,
70
+ applications: this.applications,
71
+ guard_hits: this.guardHits,
72
+ search_hits: this.searchHits,
73
+ authority: this.authority,
74
+ };
75
+ }
76
+
77
+ /**
78
+ * 从 wire format 创建
79
+ * @param {Object} data
80
+ * @returns {Stats}
81
+ */
82
+ static fromJSON(data) {
83
+ return Stats.from(data);
84
+ }
85
+ }
86
+
87
+ export default Stats;
@@ -0,0 +1,9 @@
1
+ /**
2
+ * 值对象统一导出
3
+ */
4
+ export { Content } from './Content.js';
5
+ export { Relations, RELATION_BUCKETS } from './Relations.js';
6
+ export { Constraints } from './Constraints.js';
7
+ export { Reasoning } from './Reasoning.js';
8
+ export { Quality } from './Quality.js';
9
+ export { Stats } from './Stats.js';
@@ -174,6 +174,54 @@ export class AiProvider {
174
174
  ? `\n# Code Structure Analysis (AST)\nThe following is a Tree-sitter AST analysis of the project. Use this structural context to better understand class hierarchies, design patterns, and code quality when extracting recipes:\n\n${options.astContext.substring(0, 3000)}\n`
175
175
  : '';
176
176
 
177
+ // comprehensive 模式:全量分析整个文件,不跳过任何有意义的方法
178
+ if (options.comprehensive) {
179
+ return `# Role
180
+ You are a ${langProfile.role} performing a **comprehensive full-file analysis**.
181
+
182
+ # Goal
183
+ Thoroughly analyze ALL code in "${targetName}" and create Recipe entries for **every significant method, function, or code block**.
184
+ This is a full-file analysis — do NOT skip methods just because they seem "simple" or "standard".
185
+ ${skillSection}${astSection}
186
+
187
+ # What to extract
188
+ - **Every** complete method/function with 5+ lines of implementation
189
+ - Initialization and configuration methods (init, viewDidLoad, setup, configure)
190
+ - Event handlers and action methods
191
+ - Data processing and business logic
192
+ - Protocol/delegate implementations
193
+ - ANY code block that a developer might reference, learn from, or reuse
194
+
195
+ # Extraction Rules
196
+ - Extract **BROADLY** — include all meaningful code units, not just "clever" or "novel" patterns
197
+ - Each recipe must be a **complete, standalone** code unit with full signature and body
198
+ - Preserve the file's actual code. Use \`<#placeholder#>\` ONLY for literal strings/values a developer would customize
199
+ - Every recipe must be traceable to real code in the file. Do NOT invent code
200
+ - Include relevant \`headers\` (import/require lines) that the code depends on
201
+ - You **MUST** extract at least ONE recipe — every source file has something worth capturing
202
+
203
+ ${langProfile.extractionExamples}
204
+
205
+ # Output (JSON Array)
206
+ Each item:
207
+ - title (string): Descriptive English name
208
+ - summary_cn (string): Chinese description (2-3 sentences explaining what this code does)
209
+ - summary_en (string): English description (2-3 sentences)
210
+ - trigger (string): @shortcut
211
+ - category: ${langProfile.categories}
212
+ - language: "${langProfile.primaryLanguage}"
213
+ - code (string): Complete function/method/class from the file
214
+ - headers (string[]): Required import/require lines
215
+ - tags (string[]): Search keywords
216
+ - usageGuide_cn (string): "何时使用" + "关键要点" (2-3 lines)
217
+ - usageGuide_en (string): "When to use" + "Key points" (2-3 lines)
218
+
219
+ Return ONLY a JSON array. Do NOT return an empty array.
220
+
221
+ Files Content:
222
+ ${files}`;
223
+ }
224
+
177
225
  return `# Role
178
226
  You are a ${langProfile.role} extracting production-quality reusable code patterns.
179
227
 
@@ -17,7 +17,7 @@ export class GoogleGeminiProvider extends AiProvider {
17
17
  constructor(config = {}) {
18
18
  super(config);
19
19
  this.name = 'google-gemini';
20
- this.model = config.model || 'gemini-2.0-flash';
20
+ this.model = config.model || 'gemini-3-flash-preview';
21
21
  this.apiKey = config.apiKey || process.env.ASD_GOOGLE_API_KEY || '';
22
22
  this.logger = Logger.getInstance();
23
23
  }
@@ -173,7 +173,12 @@ export class GoogleGeminiProvider extends AiProvider {
173
173
  if (msg.content) parts.push({ text: msg.content });
174
174
  if (msg.toolCalls?.length > 0) {
175
175
  for (const tc of msg.toolCalls) {
176
- parts.push({ functionCall: { name: tc.name, args: tc.args || {} } });
176
+ const fcPart = { functionCall: { name: tc.name, args: tc.args || {} } };
177
+ // Gemini 3+: 回填 thoughtSignature(首个 functionCall 必须携带)
178
+ if (tc.thoughtSignature) {
179
+ fcPart.thoughtSignature = tc.thoughtSignature;
180
+ }
181
+ parts.push(fcPart);
177
182
  }
178
183
  }
179
184
  if (parts.length > 0) pushOrMerge({ role: 'model', parts });
@@ -266,6 +271,8 @@ export class GoogleGeminiProvider extends AiProvider {
266
271
  id: `gemini_fc_${Date.now()}_${fcIndex++}`,
267
272
  name: part.functionCall.name,
268
273
  args: part.functionCall.args || {},
274
+ // Gemini 3+: thoughtSignature 必须原样回传,否则后续请求 400
275
+ thoughtSignature: part.thoughtSignature || undefined,
269
276
  });
270
277
  } else if (part.text) {
271
278
  textParts.push(part.text);
@@ -327,7 +334,9 @@ export class GoogleGeminiProvider extends AiProvider {
327
334
  signal: controller.signal,
328
335
  });
329
336
  if (!res.ok) {
330
- const err = new Error(`Gemini API error: ${res.status}`);
337
+ let detail = '';
338
+ try { const j = await res.json(); detail = j?.error?.message || JSON.stringify(j).slice(0, 300); } catch { /* ignore */ }
339
+ const err = new Error(`Gemini API error: ${res.status}${detail ? ' — ' + detail : ''}`);
331
340
  err.status = res.status;
332
341
  throw err;
333
342
  }
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Model Context Protocol (stdio transport)
5
5
  * 提供给 IDE AI Agent (Cursor/VSCode Copilot) 的工具集
6
- * 34 工具,全部基于 V2 服务层,不依赖 V1
6
+ * 38 工具,全部基于 V2 服务层,不依赖 V1
7
7
  * Gateway 权限 gating: 写操作经过 Gateway 权限/宪法/审计检查
8
8
  *
9
9
  * 本文件仅包含服务编排层(初始化、路由、Gateway gating、生命周期)。
@@ -31,6 +31,7 @@ import * as candidateHandlers from './handlers/candidate.js';
31
31
  import * as guardHandlers from './handlers/guard.js';
32
32
  import * as bootstrapHandlers from './handlers/bootstrap.js';
33
33
  import * as skillHandlers from './handlers/skill.js';
34
+ import * as knowledgeHandlers from './handlers/knowledge.js';
34
35
 
35
36
  // ─── McpServer 类 ─────────────────────────────────────────────
36
37
 
@@ -140,12 +141,9 @@ export class McpServer {
140
141
  case 'autosnippet_graph_impact': return structureHandlers.graphImpact(ctx, args);
141
142
  case 'autosnippet_graph_path': return structureHandlers.graphPath(ctx, args);
142
143
  case 'autosnippet_graph_stats': return structureHandlers.graphStats(ctx);
143
- // 候选校验 & 提交 & AI 补全
144
+ // 候选校验 & AI 补全(提交已移至 V3 knowledge handlers)
144
145
  case 'autosnippet_validate_candidate': return candidateHandlers.validateCandidate(ctx, args);
145
146
  case 'autosnippet_check_duplicate': return candidateHandlers.checkDuplicate(ctx, args);
146
- case 'autosnippet_submit_candidate': return candidateHandlers.submitSingle(ctx, args);
147
- case 'autosnippet_submit_candidates': return candidateHandlers.submitBatch(ctx, args);
148
- case 'autosnippet_submit_draft_recipes': return candidateHandlers.submitDrafts(ctx, args);
149
147
  case 'autosnippet_enrich_candidates': return candidateHandlers.enrichCandidates(ctx, args);
150
148
  // Guard & 扫描
151
149
  case 'autosnippet_guard_check': return guardHandlers.guardCheck(ctx, args);
@@ -161,6 +159,10 @@ export class McpServer {
161
159
  case 'autosnippet_delete_skill': return skillHandlers.deleteSkill(ctx, args);
162
160
  case 'autosnippet_update_skill': return skillHandlers.updateSkill(ctx, args);
163
161
  case 'autosnippet_suggest_skills': return skillHandlers.suggestSkills(ctx);
162
+ // V3 知识条目
163
+ case 'autosnippet_submit_knowledge': return knowledgeHandlers.submitKnowledge(ctx, args);
164
+ case 'autosnippet_submit_knowledge_batch': return knowledgeHandlers.submitKnowledgeBatch(ctx, args);
165
+ case 'autosnippet_knowledge_lifecycle': return knowledgeHandlers.knowledgeLifecycle(ctx, args);
164
166
  default: throw new Error(`Unknown tool: ${name}`);
165
167
  }
166
168
  }
@@ -5,7 +5,7 @@
5
5
  *
6
6
  * 1. Analyst Agent 自由探索代码 (AST 工具 + 文件搜索)
7
7
  * 2. HandoffProtocol 质量门控
8
- * 3. Producer Agent 格式化输出 (submit_candidate)
8
+ * 3. Producer Agent 格式化输出 (submit_knowledge)
9
9
  * 4. TierScheduler 分层并行执行
10
10
  *
11
11
  * @module pipeline/orchestrator
@@ -412,7 +412,7 @@ export async function fillDimensionsV3(fillContext) {
412
412
  // 记录到 DimensionContext
413
413
  for (const tc of (producerResult.toolCalls || [])) {
414
414
  const tool = tc.tool || tc.name;
415
- if (tool === 'submit_candidate' || tool === 'submit_with_check') {
415
+ if (tool === 'submit_knowledge' || tool === 'submit_with_check') {
416
416
  dimContext.addSubmittedCandidate(dimId, {
417
417
  title: tc.params?.title || '',
418
418
  subTopic: tc.params?.category || '',
@@ -654,4 +654,5 @@ export async function fillDimensionsV3(fillContext) {
654
654
  chatAgent.setFileCache(null);
655
655
  }
656
656
 
657
+ export { clearCheckpoints };
657
658
  export default fillDimensionsV3;
@@ -45,7 +45,7 @@ import pathGuard from '../../../shared/PathGuard.js';
45
45
 
46
46
  // ── Sub-modules ──
47
47
  import { loadBootstrapSkills, extractSkillDimensionGuides, enhanceDimensions } from './bootstrap/skills.js';
48
- import { fillDimensionsV3 } from './bootstrap/pipeline/orchestrator.js';
48
+ import { fillDimensionsV3, clearCheckpoints } from './bootstrap/pipeline/orchestrator.js';
49
49
 
50
50
  // Re-export for external consumers
51
51
  export { loadBootstrapSkills };
@@ -69,6 +69,10 @@ export async function bootstrapKnowledge(ctx, args) {
69
69
  const t0 = Date.now();
70
70
  const projectRoot = process.env.ASD_PROJECT_DIR || process.cwd();
71
71
 
72
+ // ── 清除旧 checkpoint: 每次手动触发冷启动时重新开始 ──
73
+ await clearCheckpoints(projectRoot);
74
+ ctx.logger.info('[Bootstrap] Cleared old checkpoints — starting fresh');
75
+
72
76
  // 路径安全守卫 — 确保所有写操作限制在项目目录内
73
77
  if (!pathGuard.configured) {
74
78
  const { default: Bootstrap } = await import('../../../bootstrap.js');
@@ -388,7 +392,7 @@ export async function bootstrapKnowledge(ctx, args) {
388
392
  skillWorthyDimensions: dimensions.filter(d => d.skillWorthy).map(d => d.id),
389
393
  candidateOnlyDimensions: dimensions.filter(d => !d.skillWorthy).map(d => d.id),
390
394
  candidateRequiredFields: ['title', 'code', 'language', 'category', 'knowledgeType', 'reasoning'],
391
- submissionTool: 'autosnippet_submit_candidates',
395
+ submissionTool: 'autosnippet_submit_knowledge_batch',
392
396
  expectedOutput: '候选知识(微观代码维度:code-pattern/best-practice/event-and-data-flow + 深度扫描:objc-deep-scan/category-scan)+ 6 个 Project Skills(宏观叙事维度:code-standard/architecture/project-profile/agent-guidelines + 深度扫描:objc-deep-scan/category-scan)',
393
397
  },
394
398
 
@@ -419,7 +423,7 @@ export async function bootstrapKnowledge(ctx, args) {
419
423
  '== 完成后可执行的后续操作 ==',
420
424
  '1. 调用 autosnippet_enrich_candidates(candidateIds) 补全候选缺失字段',
421
425
  '2. 调用 autosnippet_bootstrap_refine() 对候选进行 AI 精炼',
422
- '3. 使用 autosnippet_submit_candidates 手动提交更多候选',
426
+ '3. 使用 autosnippet_submit_knowledge_batch 手动提交更多知识条目',
423
427
  '4. 使用 autosnippet_load_skill 加载自动生成的 Project Skills',
424
428
  '',
425
429
  '== 宏观维度 → Project Skills ==',
@@ -516,7 +520,7 @@ export async function bootstrapKnowledge(ctx, args) {
516
520
  /**
517
521
  * bootstrapRefine — Phase 6 AI 润色
518
522
  *
519
- * 对 Bootstrap Phase 5 产出的候选进行 AI 二次精炼:
523
+ * 对 Bootstrap Phase 5 产出的知识条目进行 AI 二次精炼:
520
524
  * - 改善模板化描述 → 更自然精准
521
525
  * - 补充高阶架构洞察
522
526
  * - 推断并填充 relations 关联
@@ -527,7 +531,7 @@ export async function bootstrapKnowledge(ctx, args) {
527
531
  */
528
532
  export async function bootstrapRefine(ctx, args) {
529
533
  const t0 = Date.now();
530
- const candidateService = ctx.container.get('candidateService');
534
+ const knowledgeService = ctx.container.get('knowledgeService');
531
535
  const aiProvider = ctx.container.get('aiProvider');
532
536
 
533
537
  if (!aiProvider) {
@@ -541,17 +545,122 @@ export async function bootstrapRefine(ctx, args) {
541
545
  onProgress = (eventName, data) => taskManager.emitProgress(eventName, data);
542
546
  } catch { /* optional */ }
543
547
 
544
- const result = await candidateService.refineBootstrapCandidates(
545
- aiProvider,
546
- { candidateIds: args.candidateIds, userPrompt: args.userPrompt, dryRun: args.dryRun, onProgress },
547
- { userId: 'external_agent' },
548
- );
548
+ // 1. 收集待润色条目
549
+ let entries;
550
+ if (args.candidateIds?.length) {
551
+ entries = [];
552
+ for (const id of args.candidateIds) {
553
+ const e = await knowledgeService.get(id);
554
+ if (e) entries.push(typeof e.toJSON === 'function' ? e.toJSON() : e);
555
+ }
556
+ } else {
557
+ const result = await knowledgeService.list({ lifecycle: 'pending', source: 'bootstrap' }, { page: 1, pageSize: 200 });
558
+ entries = (result.items || []).map(e => typeof e.toJSON === 'function' ? e.toJSON() : e);
559
+ }
560
+
561
+ if (entries.length === 0) {
562
+ return envelope({ success: true, data: { refined: 0, total: 0, errors: [], results: [] }, meta: { tool: 'autosnippet_bootstrap_refine', responseTimeMs: Date.now() - t0 } });
563
+ }
564
+
565
+ onProgress?.('refine:started', { total: entries.length, candidateIds: entries.map(e => e.id) });
566
+
567
+ // 2. 逐条 AI 润色
568
+ const allTitles = entries.map(e => e.title).filter(Boolean);
569
+ const results = [];
570
+ const errors = [];
571
+ let refined = 0;
572
+ let processed = 0;
573
+
574
+ for (const entry of entries) {
575
+ processed++;
576
+ onProgress?.('refine:item-started', { candidateId: entry.id, title: entry.title, current: processed, total: entries.length, progress: Math.round(((processed - 1) / entries.length) * 100) });
577
+
578
+ try {
579
+ const prompt = `你是一位高级代码知识管理专家。请改进以下知识条目:
580
+
581
+ 标题: ${entry.title}
582
+ 类型: ${entry.knowledge_type}
583
+ 语言: ${entry.language}
584
+ 描述: ${entry.description || ''}
585
+ 代码:
586
+ \`\`\`
587
+ ${(entry.content?.pattern || '').substring(0, 2000)}
588
+ \`\`\`
589
+
590
+ 同批次知识: ${allTitles.slice(0, 20).join(', ')}
591
+ ${args.userPrompt ? `用户补充: ${args.userPrompt}` : ''}
592
+
593
+ 请返回 JSON:
594
+ {
595
+ "summary": "改进后的中文摘要",
596
+ "tags": ["建议标签"],
597
+ "relations": [{"target":"相关条目标题","relation":"depends_on|extends|related_to"}],
598
+ "confidence": 0.0-1.0,
599
+ "insight": "架构洞察(一句话)"
600
+ }`;
601
+
602
+ const response = await aiProvider.chat(prompt, { temperature: 0.3 });
603
+ const parsed = aiProvider.extractJSON(response, '{', '}');
604
+
605
+ if (!parsed) {
606
+ errors.push({ id: entry.id, title: entry.title, error: 'AI returned no valid JSON' });
607
+ onProgress?.('refine:item-failed', { candidateId: entry.id, title: entry.title, error: 'No valid JSON', current: processed, total: entries.length, progress: Math.round((processed / entries.length) * 100) });
608
+ continue;
609
+ }
610
+
611
+ if (args.dryRun) {
612
+ results.push({ id: entry.id, title: entry.title, preview: parsed });
613
+ onProgress?.('refine:item-completed', { candidateId: entry.id, title: entry.title, refined: false, current: processed, total: entries.length, progress: Math.round((processed / entries.length) * 100) });
614
+ continue;
615
+ }
616
+
617
+ // 构建更新数据
618
+ const updateData = {};
619
+ let changed = false;
620
+
621
+ if (parsed.summary && parsed.summary !== entry.summary_cn) {
622
+ updateData.summary_cn = parsed.summary;
623
+ changed = true;
624
+ }
625
+ if (parsed.tags && Array.isArray(parsed.tags)) {
626
+ const merged = [...new Set([...(entry.tags || []), ...parsed.tags])];
627
+ if (merged.length > (entry.tags || []).length) {
628
+ updateData.tags = merged;
629
+ changed = true;
630
+ }
631
+ }
632
+ if (typeof parsed.confidence === 'number' && parsed.confidence !== 0.6) {
633
+ updateData.reasoning = { ...(entry.reasoning || {}), confidence: parsed.confidence };
634
+ changed = true;
635
+ }
636
+ if (parsed.insight) {
637
+ updateData.description = `${entry.description || ''}\n\n**AI Insight**: ${parsed.insight}`.trim();
638
+ changed = true;
639
+ }
640
+
641
+ if (changed) {
642
+ await knowledgeService.update(entry.id, updateData);
643
+ refined++;
644
+ }
645
+
646
+ results.push({ id: entry.id, title: entry.title, refined: changed, fields: Object.keys(parsed) });
647
+ onProgress?.('refine:item-completed', { candidateId: entry.id, title: entry.title, refined: changed, current: processed, total: entries.length, progress: Math.round((processed / entries.length) * 100), refinedSoFar: refined });
648
+ } catch (err) {
649
+ errors.push({ id: entry.id, title: entry.title, error: err.message });
650
+ onProgress?.('refine:item-failed', { candidateId: entry.id, title: entry.title, error: err.message, current: processed, total: entries.length, progress: Math.round((processed / entries.length) * 100) });
651
+ }
652
+ }
653
+
654
+ onProgress?.('refine:completed', { total: entries.length, refined, failed: errors.length });
549
655
 
550
656
  return envelope({
551
657
  success: true,
552
658
  data: {
553
- ...result,
554
- message: `Phase 6 AI 润色完成: ${result.refined}/${result.total} 条候选已更新${args.dryRun ? '(预览模式)' : ''}`,
659
+ refined,
660
+ total: entries.length,
661
+ errors,
662
+ results,
663
+ message: `Phase 6 AI 润色完成: ${refined}/${entries.length} 条知识条目已更新${args.dryRun ? '(预览模式)' : ''}`,
555
664
  },
556
665
  meta: { tool: 'autosnippet_bootstrap_refine', responseTimeMs: Date.now() - t0 },
557
666
  });