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.
- package/README.md +5 -5
- package/bin/cli.js +5 -33
- package/config/constitution.yaml +9 -2
- package/dashboard/dist/assets/{icons-B_Xg4B-s.js → icons-BkT3XrKf.js} +105 -100
- package/dashboard/dist/assets/index-BsB7DzW4.css +1 -0
- package/dashboard/dist/assets/index-DdmQMrJJ.js +155 -0
- package/dashboard/dist/index.html +3 -3
- package/lib/cli/AiScanService.js +13 -11
- package/lib/cli/KnowledgeSyncService.js +343 -0
- package/lib/cli/SetupService.js +9 -27
- package/lib/core/ast/ProjectGraph.js +160 -0
- package/lib/core/gateway/GatewayActionRegistry.js +48 -58
- package/lib/domain/index.js +16 -11
- package/lib/domain/knowledge/KnowledgeEntry.js +351 -0
- package/lib/domain/knowledge/KnowledgeRepository.js +123 -0
- package/lib/domain/knowledge/Lifecycle.js +109 -0
- package/lib/domain/knowledge/index.js +27 -0
- package/lib/domain/knowledge/values/Constraints.js +125 -0
- package/lib/domain/knowledge/values/Content.js +86 -0
- package/lib/domain/knowledge/values/Quality.js +93 -0
- package/lib/domain/knowledge/values/Reasoning.js +69 -0
- package/lib/domain/knowledge/values/Relations.js +168 -0
- package/lib/domain/knowledge/values/Stats.js +87 -0
- package/lib/domain/knowledge/values/index.js +9 -0
- package/lib/external/ai/AiProvider.js +48 -0
- package/lib/external/ai/providers/GoogleGeminiProvider.js +12 -3
- package/lib/external/mcp/McpServer.js +7 -5
- package/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +3 -2
- package/lib/external/mcp/handlers/bootstrap.js +121 -12
- package/lib/external/mcp/handlers/browse.js +77 -73
- package/lib/external/mcp/handlers/candidate.js +29 -276
- package/lib/external/mcp/handlers/guard.js +2 -0
- package/lib/external/mcp/handlers/knowledge.js +205 -0
- package/lib/external/mcp/handlers/skill.js +4 -2
- package/lib/external/mcp/handlers/structure.js +25 -23
- package/lib/external/mcp/handlers/system.js +10 -12
- package/lib/external/mcp/tools.js +125 -138
- package/lib/http/HttpServer.js +4 -8
- package/lib/http/middleware/requestLogger.js +3 -3
- package/lib/http/routes/ai.js +17 -1
- 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/http/routes/skills.js +44 -1
- package/lib/infrastructure/cache/GraphCache.js +143 -0
- package/lib/infrastructure/database/migrations/015_create_token_usage.js +27 -0
- package/lib/infrastructure/database/migrations/016_unified_knowledge_entries.js +395 -0
- package/lib/infrastructure/external/XcodeAutomation.js +187 -103
- package/lib/infrastructure/realtime/RealtimeService.js +14 -2
- package/lib/injection/ServiceContainer.js +164 -63
- package/lib/repository/knowledge/KnowledgeRepository.impl.js +373 -0
- package/lib/repository/token/TokenUsageStore.js +162 -0
- package/lib/service/automation/DirectiveDetector.js +2 -3
- package/lib/service/automation/FileWatcher.js +67 -28
- package/lib/service/automation/XcodeIntegration.js +931 -156
- package/lib/service/automation/handlers/AlinkHandler.js +6 -4
- package/lib/service/automation/handlers/CreateHandler.js +53 -18
- package/lib/service/automation/handlers/GuardHandler.js +183 -20
- package/lib/service/automation/handlers/SearchHandler.js +35 -17
- package/lib/service/chat/AnalystAgent.js +25 -14
- package/lib/service/chat/CandidateGuardrail.js +1 -1
- package/lib/service/chat/ChatAgent.js +280 -48
- package/lib/service/chat/ContextWindow.js +92 -8
- package/lib/service/chat/HandoffProtocol.js +26 -1
- package/lib/service/chat/ProducerAgent.js +11 -9
- package/lib/service/chat/tools.js +298 -194
- package/lib/service/guard/GuardCheckEngine.js +114 -10
- package/lib/service/guard/GuardService.js +59 -48
- package/lib/service/knowledge/ConfidenceRouter.js +159 -0
- package/lib/service/knowledge/KnowledgeFileWriter.js +602 -0
- package/lib/service/knowledge/KnowledgeService.js +725 -0
- package/lib/service/search/SearchEngine.js +92 -19
- package/lib/service/skills/SignalCollector.js +15 -9
- package/lib/service/skills/SkillAdvisor.js +13 -11
- package/lib/service/snippet/SnippetFactory.js +5 -5
- package/lib/service/spm/SpmService.js +119 -18
- 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 +6 -6
- package/skills/autosnippet-coldstart/SKILL.md +7 -3
- 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 +16 -4
- 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-CkIih2CC.css +0 -1
- package/dashboard/dist/assets/index-Duc8Qk-c.js +0 -197
- 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 -973
- package/lib/service/recipe/RecipeFileWriter.js +0 -514
- package/lib/service/recipe/RecipeService.js +0 -786
- 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-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
-
// 候选校验 &
|
|
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 格式化输出 (
|
|
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 === '
|
|
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: '
|
|
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. 使用
|
|
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
|
|
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
|
|
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
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
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
|
-
|
|
554
|
-
|
|
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
|
});
|