autosnippet 3.0.10 → 3.0.13

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 (56) hide show
  1. package/bin/cli.js +64 -1
  2. package/config/default.json +9 -0
  3. package/dashboard/dist/assets/{index-I2ySoCmF.js → index-Bnm26ulL.js} +47 -47
  4. package/dashboard/dist/index.html +1 -1
  5. package/lib/cli/SetupService.js +92 -5
  6. package/lib/cli/UpgradeService.js +14 -5
  7. package/lib/core/discovery/GenericDiscoverer.js +4 -28
  8. package/lib/external/mcp/handlers/bootstrap/base-dimensions.js +246 -0
  9. package/lib/external/mcp/handlers/bootstrap/pipeline/checkpoint.js +80 -0
  10. package/lib/external/mcp/handlers/bootstrap/pipeline/dimension-configs.js +275 -0
  11. package/lib/external/mcp/handlers/bootstrap/pipeline/noAiFallback.js +600 -0
  12. package/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +125 -342
  13. package/lib/external/mcp/handlers/bootstrap/refine.js +362 -0
  14. package/lib/external/mcp/handlers/bootstrap.js +6 -590
  15. package/lib/external/mcp/handlers/browse.js +119 -9
  16. package/lib/external/mcp/handlers/guard.js +25 -6
  17. package/lib/external/mcp/handlers/search.js +56 -24
  18. package/lib/http/routes/guardRules.js +9 -17
  19. package/lib/injection/ServiceContainer.js +12 -3
  20. package/lib/platform/ios/xcode/XcodeImportResolver.js +434 -0
  21. package/lib/platform/ios/xcode/XcodeIntegration.js +40 -659
  22. package/lib/platform/ios/xcode/XcodeWriteUtils.js +220 -0
  23. package/lib/service/chat/ChatAgent.js +39 -418
  24. package/lib/service/chat/ChatAgentPrompts.js +149 -0
  25. package/lib/service/chat/ChatAgentTasks.js +297 -0
  26. package/lib/service/chat/tools/_shared.js +61 -0
  27. package/lib/service/chat/tools/ai-analysis.js +284 -0
  28. package/lib/service/chat/tools/ast-graph.js +681 -0
  29. package/lib/service/chat/tools/composite.js +496 -0
  30. package/lib/service/chat/tools/guard.js +265 -0
  31. package/lib/service/chat/tools/index.js +250 -0
  32. package/lib/service/chat/tools/infrastructure.js +222 -0
  33. package/lib/service/chat/tools/knowledge-graph.js +234 -0
  34. package/lib/service/chat/tools/lifecycle.js +469 -0
  35. package/lib/service/chat/tools/project-access.js +923 -0
  36. package/lib/service/chat/tools/query.js +264 -0
  37. package/lib/service/chat/tools.js +14 -3994
  38. package/lib/service/cursor/AgentInstructionsGenerator.js +395 -0
  39. package/lib/service/cursor/CursorDeliveryPipeline.js +70 -11
  40. package/lib/service/cursor/FileProtection.js +116 -0
  41. package/lib/service/cursor/KnowledgeCompressor.js +61 -11
  42. package/lib/service/cursor/SkillsSyncer.js +5 -3
  43. package/lib/service/cursor/TopicClassifier.js +19 -3
  44. package/lib/service/guard/ExclusionManager.js +26 -2
  45. package/lib/service/guard/GuardCheckEngine.js +38 -370
  46. package/lib/service/guard/GuardCodeChecks.js +362 -0
  47. package/lib/service/guard/GuardCrossFileChecks.js +307 -0
  48. package/lib/service/guard/GuardPatternUtils.js +180 -0
  49. package/lib/service/guard/GuardService.js +80 -38
  50. package/lib/service/module/ModuleService.js +1 -0
  51. package/lib/service/search/SearchEngine.js +10 -2
  52. package/lib/service/wiki/WikiGenerator.js +226 -1532
  53. package/lib/service/wiki/WikiRenderers.js +1878 -0
  54. package/lib/service/wiki/WikiUtils.js +907 -0
  55. package/lib/shared/LanguageService.js +299 -0
  56. package/package.json +1 -1
@@ -1,3999 +1,19 @@
1
1
  /**
2
- * tools.js — ChatAgent 全部工具定义
2
+ * tools.js — ChatAgent 工具定义统一入口 (Barrel Re-export)
3
3
  *
4
- * 54 个工具覆盖项目全部 AI 能力:
4
+ * 实际工具定义已拆分到 ./tools/ 子目录:
5
+ * - project-access.js 项目数据访问 (5)
6
+ * - query.js 查询类 (6)
7
+ * - ai-analysis.js AI 分析类 (4)
8
+ * - knowledge-graph.js 知识图谱 (3)
9
+ * - guard.js Guard 安全类 (6)
10
+ * - lifecycle.js 生命周期操作类 (11)
11
+ * - infrastructure.js 基础设施 + Skills (7)
12
+ * - composite.js 组合工具 + 元工具 (6)
13
+ * - ast-graph.js AST 分析 + Agent Memory (10)
5
14
  *
6
- * ┌─── 项目数据访问 (5) ────────────────────────────────────┐
7
- * │ 1. search_project_code 搜索项目源码 │
8
- * │ 2. read_project_file 读取项目文件 │
9
- * │ 2b. list_project_structure 列出项目目录结构 (v10) │
10
- * │ 2c. get_file_summary 文件结构摘要 (v10) │
11
- * │ 2d. semantic_search_code 语义知识搜索 (v10) │
12
- * └────────────────────────────────────────────────────────┘
13
- * ┌─── 查询类 (8) ─────────────────────────────────┐
14
- * │ 3. search_recipes 搜索 Recipe │
15
- * │ 4. search_candidates 搜索候选项 │
16
- * │ 5. get_recipe_detail 获取 Recipe 详情 │
17
- * │ 6. get_project_stats 获取项目统计 │
18
- * │ 7. search_knowledge RAG 知识库搜索 │
19
- * │ 8. get_related_recipes 知识图谱关联查询 │
20
- * │ 9. list_guard_rules 列出 Guard 规则 │
21
- * │ 10. get_recommendations 获取推荐 Recipe │
22
- * └─────────────────────────────────────────────────┘
23
- * ┌─── AI 分析类 (5) ──────────────────────────────────┐
24
- * │ 11. summarize_code 代码摘要 │
25
- * │ 12. extract_recipes 从源码提取 Recipe │
26
- * │ 13. enrich_candidate ① 结构补齐 │
27
- * │ 13b. refine_bootstrap_candidates ② 内容润色 │
28
- * │ 14. ai_translate AI 翻译 (中→英) │
29
- * └─────────────────────────────────────────────────────┘
30
- * ┌─── Guard 安全类 (3) ───────────────────────────────┐
31
- * │ 15. guard_check_code Guard 规则代码检查 │
32
- * │ 16. query_violations 查询 Guard 违规记录 │
33
- * │ 17. generate_guard_rule AI 生成 Guard 规则 │
34
- * └─────────────────────────────────────────────────────┘
35
- * ┌─── 生命周期操作类 (7) ─────────────────────────────┐
36
- * │ 18. submit_knowledge 提交候选 │
37
- * │ 19. approve_candidate 批准候选 │
38
- * │ 20. reject_candidate 驳回候选 │
39
- * │ 21. publish_recipe 发布 Recipe │
40
- * │ 22. deprecate_recipe 弃用 Recipe │
41
- * │ 23. update_recipe 更新 Recipe 字段 │
42
- * │ 24. record_usage 记录 Recipe 使用 │
43
- * └─────────────────────────────────────────────────────┘
44
- * ┌─── 质量与反馈类 (3) ───────────────────────────────┐
45
- * │ 25. quality_score Recipe 质量评分 │
46
- * │ 26. validate_candidate 候选校验 │
47
- * │ 27. get_feedback_stats 获取反馈统计 │
48
- * └─────────────────────────────────────────────────────┘
49
- * ┌─── 知识图谱类 (3) ─────────────────────────────────┐
50
- * │ 28. check_duplicate 候选查重 │
51
- * │ 29. discover_relations 知识图谱关系发现 │
52
- * │ 30. add_graph_edge 添加知识图谱关系 │
53
- * └─────────────────────────────────────────────────────┘
54
- * ┌─── 基础设施类 (3) ─────────────────────────────────┐
55
- * │ 31. graph_impact_analysis 影响范围分析 │
56
- * │ 32. rebuild_index 向量索引重建 │
57
- * │ 33. query_audit_log 审计日志查询 │
58
- * └─────────────────────────────────────────────────────┘
59
- * ┌─── Skills & Bootstrap (4) ─────────────────────────┐
60
- * │ 34. load_skill 加载 Agent Skill 文档 │
61
- * │ 35. create_skill 创建项目级 Skill │
62
- * │ 36. suggest_skills 推荐创建 Skill │
63
- * │ 37. bootstrap_knowledge 冷启动知识库初始化 │
64
- * └─────────────────────────────────────────────────────┘
65
- * ┌─── 组合工具 (3) ───────────────────────────────────┐
66
- * │ 38. analyze_code Guard + Recipe 搜索 │
67
- * │ 39. knowledge_overview 全局知识库概览 │
68
- * │ 40. submit_with_check 查重 + 提交 │
69
- * └─────────────────────────────────────────────────────┘
70
- * ┌─── 元工具 (3) — Agent 自主能力增强 ───────────────┐
71
- * │ 41. get_tool_details 工具参数查询 │
72
- * │ 42. plan_task 任务规划 (结构化计划) │
73
- * │ 43. review_my_output 自我质量审查 │
74
- * └─────────────────────────────────────────────────────┘
75
- *
76
- * v10 新增工具 (领域大脑 Agent-Pull):
77
- * 2b. list_project_structure — 项目目录树 + 文件统计
78
- * 2c. get_file_summary — 文件导入/声明/方法签名摘要
79
- * 2d. semantic_search_code — 语义相似度知识搜索
15
+ * 本文件仅做 re-export,保持向后兼容。
80
16
  */
81
17
 
82
- import fs from 'node:fs';
83
- import path from 'node:path';
84
- import { fileURLToPath } from 'node:url';
85
- import Logger from '../../infrastructure/logging/Logger.js';
86
- import { LanguageService } from '../../shared/LanguageService.js';
87
- import { findSimilarRecipes } from '../candidate/SimilarityService.js';
88
- import { CandidateGuardrail } from './CandidateGuardrail.js';
89
-
90
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
91
-
92
- const PROJECT_ROOT = path.resolve(__dirname, '../../..');
93
- /** skills/ 目录绝对路径 */
94
- const SKILLS_DIR = path.resolve(PROJECT_ROOT, 'skills');
95
- /** 项目级 skills 目录 */
96
- const PROJECT_SKILLS_DIR = path.resolve(PROJECT_ROOT, '.autosnippet', 'skills');
97
-
98
- // ════════════════════════════════════════════════════════════
99
- // 项目数据访问 (5) — 搜索/读取用户项目源码 + v10 Agent-Pull
100
- // ════════════════════════════════════════════════════════════
101
-
102
- // ────────────────────────────────────────────────────────────
103
- // 1. search_project_code — 搜索项目源码
104
- // ────────────────────────────────────────────────────────────
105
-
106
- /** 三方库路径识别(与 bootstrap/shared/third-party-filter.js 对齐) */
107
- const THIRD_PARTY_RE =
108
- /(?:^|\/)(?:Pods|Carthage|\.build\/checkouts|vendor|ThirdParty|External|Submodules|DerivedData|include|node_modules|build)\/|(?:^|\/)(?:Masonry|AFNetworking|SDWebImage|MJRefresh|MJExtension|YYKit|YYModel|Lottie|FLEX|IQKeyboardManager|MBProgressHUD|SVProgressHUD|SnapKit|Kingfisher|Alamofire|Moya|ReactiveObjC|ReactiveCocoa|RxSwift|RxCocoa|FMDB|Realm|Mantle|JSONModel|CocoaLumberjack|CocoaAsyncSocket|SocketRocket|GPUImage|FBSDKCore|FBSDKLogin|FlatBuffers|Protobuf|PromiseKit|Charts|Hero)\//i;
109
-
110
- /** 源码文件扩展名 */
111
- const SOURCE_EXT_RE = /\.(m|mm|swift|h|c|cpp|js|ts|jsx|tsx|py|rb|java|kt|go|rs)$/i;
112
-
113
- /** 声明行识别 — 用于对匹配行打分(与 bootstrap/shared/scanner.js 对齐) */
114
- const DECL_RE =
115
- /^\s*(@property\b|@interface\b|@protocol\b|@class\b|@synthesize\b|@dynamic\b|@end\b|NS_ASSUME_NONNULL|#import\b|#include\b|#define\b)/;
116
- const TYPE_DECL_RE = /^\s*\w[\w<>*\s]+[\s*]+_?\w+\s*;$/;
117
-
118
- function _scoreSearchLine(line) {
119
- const t = line.trim();
120
- if (DECL_RE.test(t)) {
121
- return -2;
122
- }
123
- if (TYPE_DECL_RE.test(t)) {
124
- return -1;
125
- }
126
- if (/^[-+]\s*\([^)]+\)\s*\w+[^{]*;\s*$/.test(t)) {
127
- return -1;
128
- }
129
- if (/\[.*\w+.*\]/.test(t)) {
130
- return 2; // ObjC message send
131
- }
132
- if (/\w+\s*\(/.test(t)) {
133
- return 2; // function call
134
- }
135
- if (/\^\s*[{(]/.test(t)) {
136
- return 1; // block literal
137
- }
138
- return 0;
139
- }
140
-
141
- /**
142
- * 收集项目文件列表 — 抽取为公用函数,供单次和批量搜索复用。
143
- * 优先使用内存缓存(bootstrap 场景),否则从磁盘递归读取。
144
- */
145
- async function _getProjectFiles(params, ctx) {
146
- const { fileFilter } = params;
147
- const projectRoot = ctx.projectRoot || process.cwd();
148
-
149
- let extFilter = null;
150
- if (fileFilter) {
151
- const exts = fileFilter.split(',').map((e) => e.trim().replace(/^\./, ''));
152
- extFilter = new RegExp(`\\.(${exts.join('|')})$`, 'i');
153
- }
154
-
155
- const fileCache = ctx.fileCache || null;
156
- let files;
157
- let skippedThirdParty = 0;
158
-
159
- if (fileCache && Array.isArray(fileCache)) {
160
- files = fileCache.filter((f) => {
161
- const p = f.relativePath || f.path || '';
162
- if (THIRD_PARTY_RE.test(p)) {
163
- skippedThirdParty++;
164
- return false;
165
- }
166
- if (extFilter && !extFilter.test(p)) {
167
- return false;
168
- }
169
- if (!SOURCE_EXT_RE.test(p)) {
170
- return false;
171
- }
172
- return true;
173
- });
174
- } else {
175
- files = [];
176
- const MAX_FILE_SIZE = 512 * 1024;
177
- const walk = (dir, relBase = '') => {
178
- try {
179
- const entries = fs.readdirSync(dir, { withFileTypes: true });
180
- for (const entry of entries) {
181
- const relPath = relBase ? `${relBase}/${entry.name}` : entry.name;
182
- const fullPath = path.join(dir, entry.name);
183
- const isDir =
184
- entry.isDirectory() ||
185
- (entry.isSymbolicLink() &&
186
- (() => {
187
- try {
188
- return fs.statSync(fullPath).isDirectory();
189
- } catch {
190
- return false;
191
- }
192
- })());
193
- const isFile =
194
- entry.isFile() ||
195
- (entry.isSymbolicLink() &&
196
- (() => {
197
- try {
198
- return fs.statSync(fullPath).isFile();
199
- } catch {
200
- return false;
201
- }
202
- })());
203
- if (isDir) {
204
- if (
205
- entry.name.startsWith('.') ||
206
- entry.name === 'node_modules' ||
207
- entry.name === 'build'
208
- ) {
209
- continue;
210
- }
211
- if (THIRD_PARTY_RE.test(`${relPath}/`)) {
212
- skippedThirdParty++;
213
- continue;
214
- }
215
- walk(fullPath, relPath);
216
- } else if (isFile) {
217
- if (THIRD_PARTY_RE.test(relPath)) {
218
- skippedThirdParty++;
219
- continue;
220
- }
221
- if (!SOURCE_EXT_RE.test(entry.name)) {
222
- continue;
223
- }
224
- if (extFilter && !extFilter.test(entry.name)) {
225
- continue;
226
- }
227
- try {
228
- const stat = fs.statSync(fullPath);
229
- if (stat.size > MAX_FILE_SIZE) {
230
- continue;
231
- }
232
- const content = fs.readFileSync(fullPath, 'utf-8');
233
- files.push({ relativePath: relPath, content, name: entry.name });
234
- } catch {
235
- /* skip unreadable files */
236
- }
237
- }
238
- }
239
- } catch {
240
- /* skip inaccessible dirs */
241
- }
242
- };
243
- walk(projectRoot);
244
- }
245
-
246
- return { files, skippedThirdParty };
247
- }
248
-
249
- const searchProjectCode = {
250
- name: 'search_project_code',
251
- description:
252
- '在用户项目源码中搜索指定模式。返回匹配的代码片段及上下文。' +
253
- '自动过滤三方库代码(Pods/Carthage/node_modules),优先返回实际使用行而非声明行。' +
254
- '适用场景:验证代码模式存在性、查找更多项目示例、理解项目中某个 API 的用法。' +
255
- '批量搜索:传入 patterns 数组可一次搜索多个关键词(每个关键词独立返回结果),减少工具调用次数。',
256
- parameters: {
257
- type: 'object',
258
- properties: {
259
- pattern: { type: 'string', description: '搜索词或正则表达式(单个搜索时使用)' },
260
- patterns: {
261
- type: 'array',
262
- items: { type: 'string' },
263
- description:
264
- '批量搜索:多个搜索词数组,如 ["methodA", "methodB", "classC"]。与 pattern 互斥,优先使用 patterns。',
265
- },
266
- isRegex: { type: 'boolean', description: '是否为正则表达式,默认 false' },
267
- fileFilter: { type: 'string', description: '文件扩展名过滤,如 ".m,.swift"' },
268
- contextLines: { type: 'number', description: '匹配行前后的上下文行数,默认 3' },
269
- maxResults: { type: 'number', description: '每个 pattern 的最大返回结果数,默认 5' },
270
- },
271
- required: [],
272
- },
273
- handler: async (params, ctx) => {
274
- // ── 去重缓存初始化 ──
275
- const state = ctx._sharedState || ctx;
276
- if (!state._searchCache) {
277
- state._searchCache = new Map();
278
- }
279
-
280
- // ── 批量模式:patterns 数组 ──
281
- if (Array.isArray(params.patterns) && params.patterns.length > 0) {
282
- const batchPatterns = params.patterns.slice(0, 10); // 最多 10 个
283
- const batchResults = {};
284
- let dedupCount = 0;
285
- for (const p of batchPatterns) {
286
- // 去重:已搜索过的 pattern 直接返回缓存
287
- const cacheKey = `${p}|${params.isRegex || false}|${params.fileFilter || ''}`;
288
- if (state._searchCache.has(cacheKey)) {
289
- batchResults[p] = { ...state._searchCache.get(cacheKey), _cached: true };
290
- dedupCount++;
291
- continue;
292
- }
293
- const sub = await searchProjectCode.handler(
294
- { ...params, pattern: p, patterns: undefined },
295
- ctx
296
- );
297
- const entry = { matches: sub.matches || [], total: sub.total || 0 };
298
- state._searchCache.set(cacheKey, entry);
299
- batchResults[p] = entry;
300
- }
301
- return {
302
- batchResults,
303
- patternsSearched: batchPatterns.length,
304
- searchedFiles: (await _getProjectFiles(params, ctx)).files.length,
305
- ...(dedupCount > 0
306
- ? {
307
- _deduped: dedupCount,
308
- hint: `${dedupCount} 个 pattern 命中缓存,请避免重复搜索相同关键词。`,
309
- }
310
- : {}),
311
- };
312
- }
313
-
314
- // 兼容 AI 传 "query" / "search" / "keyword" 替代 "pattern"
315
- const pattern =
316
- params.pattern || params.query || params.search || params.keyword || params.search_query;
317
- const { isRegex = false, contextLines = 3, maxResults = 5 } = params;
318
- const _projectRoot = ctx.projectRoot || process.cwd();
319
-
320
- if (!pattern || typeof pattern !== 'string') {
321
- return {
322
- error: '参数错误: 请提供 pattern(搜索关键词或正则表达式)或 patterns 数组',
323
- matches: [],
324
- total: 0,
325
- };
326
- }
327
-
328
- // ── 单 pattern 去重检查 ──
329
- const cacheKey = `${pattern}|${params.isRegex || false}|${params.fileFilter || ''}`;
330
- if (state._searchCache.has(cacheKey)) {
331
- const cached = state._searchCache.get(cacheKey);
332
- return {
333
- ...cached,
334
- _cached: true,
335
- hint: `⚠ 已搜索过 "${pattern}",返回缓存结果。请搜索不同的关键词以获取新信息。`,
336
- };
337
- }
338
-
339
- // 构建搜索正则
340
- let searchRe;
341
- try {
342
- searchRe = isRegex
343
- ? new RegExp(pattern, 'gi')
344
- : new RegExp(pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'gi');
345
- } catch (err) {
346
- return { error: `Invalid pattern: ${err.message}`, matches: [], total: 0 };
347
- }
348
-
349
- const { files, skippedThirdParty } = await _getProjectFiles(params, ctx);
350
-
351
- // 搜索匹配
352
- const matches = [];
353
- let total = 0;
354
-
355
- for (const f of files) {
356
- if (!f.content) {
357
- continue;
358
- }
359
- // 快速预过滤
360
- searchRe.lastIndex = 0;
361
- if (!searchRe.test(f.content)) {
362
- continue;
363
- }
364
-
365
- const lines = f.content.split('\n');
366
- searchRe.lastIndex = 0;
367
-
368
- for (let i = 0; i < lines.length; i++) {
369
- searchRe.lastIndex = 0;
370
- if (!searchRe.test(lines[i])) {
371
- continue;
372
- }
373
- total++;
374
-
375
- if (matches.length < maxResults) {
376
- const start = Math.max(0, i - contextLines);
377
- const end = Math.min(lines.length - 1, i + contextLines);
378
- const contextArr = [];
379
- for (let j = start; j <= end; j++) {
380
- contextArr.push(lines[j]);
381
- }
382
-
383
- matches.push({
384
- file: f.relativePath || f.path || f.name,
385
- line: i + 1,
386
- code: lines[i],
387
- context: contextArr.join('\n'),
388
- score: _scoreSearchLine(lines[i]),
389
- });
390
- }
391
- }
392
- }
393
-
394
- // 按 score 降序排列(实际使用行优先)
395
- matches.sort((a, b) => b.score - a.score);
396
-
397
- const result = {
398
- matches,
399
- total,
400
- searchedFiles: files.length,
401
- skippedThirdParty,
402
- ...(() => {
403
- // P2.2: 搜索超限提示 — 引导使用 AST 工具
404
- state._searchCallCount = (state._searchCallCount || 0) + 1;
405
- if (state._searchCallCount > 12 && ctx.source === 'system') {
406
- return {
407
- hint: `💡 你已搜索 ${state._searchCallCount} 次。考虑使用 get_class_info / get_class_hierarchy / get_project_overview 获取结构化信息,效率更高。`,
408
- };
409
- }
410
- return {};
411
- })(),
412
- };
413
-
414
- // 缓存搜索结果
415
- state._searchCache.set(cacheKey, { matches: result.matches, total: result.total });
416
-
417
- return result;
418
- },
419
- };
420
-
421
- // ────────────────────────────────────────────────────────────
422
- // 2. read_project_file — 读取项目文件
423
- // ────────────────────────────────────────────────────────────
424
- const readProjectFile = {
425
- name: 'read_project_file',
426
- description:
427
- '读取项目中指定文件的内容(部分或全部)。' +
428
- '通常在 search_project_code 找到匹配后使用,获取更完整的上下文。' +
429
- '批量读取:传入 filePaths 数组可一次读取多个文件,减少工具调用次数。',
430
- parameters: {
431
- type: 'object',
432
- properties: {
433
- filePath: { type: 'string', description: '相对于项目根目录的文件路径(单个文件时使用)' },
434
- filePaths: {
435
- type: 'array',
436
- items: { type: 'string' },
437
- description: '批量读取:多个文件路径数组。与 filePath 互斥,优先使用 filePaths。',
438
- },
439
- startLine: { type: 'number', description: '起始行号(1-based),默认 1' },
440
- endLine: { type: 'number', description: '结束行号(1-based),默认文件末尾' },
441
- maxLines: {
442
- type: 'number',
443
- description: '最大返回行数,默认 200(批量模式下每个文件最多 100 行)',
444
- },
445
- },
446
- required: [],
447
- },
448
- handler: async (params, ctx) => {
449
- // ── 去重缓存初始化 ──
450
- const state = ctx._sharedState || ctx;
451
- if (!state._readCache) {
452
- state._readCache = new Map();
453
- }
454
-
455
- // ── 批量模式:filePaths 数组 ──
456
- if (Array.isArray(params.filePaths) && params.filePaths.length > 0) {
457
- const batchPaths = params.filePaths.slice(0, 8); // 最多 8 个文件
458
- const batchResults = {};
459
- let dedupCount = 0;
460
- for (const fp of batchPaths) {
461
- const cacheKey = `${fp}|${params.startLine || 1}|${params.endLine || ''}|${params.maxLines || 100}`;
462
- if (state._readCache.has(cacheKey)) {
463
- batchResults[fp] = { ...state._readCache.get(cacheKey), _cached: true };
464
- dedupCount++;
465
- continue;
466
- }
467
- const sub = await readProjectFile.handler(
468
- {
469
- ...params,
470
- filePath: fp,
471
- filePaths: undefined,
472
- maxLines: Math.min(params.maxLines || 100, 100),
473
- },
474
- ctx
475
- );
476
- const entry = sub.error
477
- ? { error: sub.error }
478
- : { content: sub.content, totalLines: sub.totalLines, language: sub.language };
479
- state._readCache.set(cacheKey, entry);
480
- batchResults[fp] = entry;
481
- }
482
- return {
483
- batchResults,
484
- filesRead: batchPaths.length,
485
- ...(dedupCount > 0
486
- ? { _deduped: dedupCount, hint: `${dedupCount} 个文件命中缓存,请避免重复读取相同文件。` }
487
- : {}),
488
- };
489
- }
490
-
491
- // 兼容各种参数名变体 (ToolRegistry 层已做 snake→camel 归一化,
492
- // 这里兜底处理漏网之鱼)
493
- const filePath =
494
- params.filePath ||
495
- params.path ||
496
- params.file_path ||
497
- params.filepath ||
498
- params.file ||
499
- params.filename;
500
- const { startLine = 1, maxLines = 200 } = params;
501
- const projectRoot = ctx.projectRoot || process.cwd();
502
-
503
- if (!filePath || typeof filePath !== 'string') {
504
- return { error: '参数错误: 请提供 filePath(相对于项目根目录的文件路径)或 filePaths 数组' };
505
- }
506
-
507
- // ── 单文件去重检查 ──
508
- const readCacheKey = `${filePath}|${startLine}|${params.endLine || ''}|${maxLines}`;
509
- if (state._readCache.has(readCacheKey)) {
510
- return {
511
- ...state._readCache.get(readCacheKey),
512
- _cached: true,
513
- hint: `⚠ 已读取过该文件相同行范围,返回缓存结果。如需其他行范围请指定不同的 startLine/endLine。`,
514
- };
515
- }
516
-
517
- // 安全检查: 禁止路径遍历
518
- const normalized = path.normalize(filePath);
519
- if (normalized.startsWith('..') || path.isAbsolute(normalized)) {
520
- return { error: 'Path traversal not allowed. Use relative paths within the project.' };
521
- }
522
-
523
- // 优先从内存缓存读取(bootstrap 场景)
524
- const fileCache = ctx.fileCache || null;
525
- let content = null;
526
-
527
- if (fileCache && Array.isArray(fileCache)) {
528
- const cached = fileCache.find(
529
- (f) =>
530
- (f.relativePath || f.path || '') === filePath ||
531
- (f.relativePath || f.path || '') === normalized
532
- );
533
- if (cached) {
534
- content = cached.content;
535
- }
536
- }
537
-
538
- // 降级: 从磁盘读取
539
- if (content === null) {
540
- const fullPath = path.resolve(projectRoot, normalized);
541
- // 二次安全检查: 确保解析后仍在 projectRoot 内
542
- if (!fullPath.startsWith(projectRoot)) {
543
- return { error: 'Path traversal not allowed.' };
544
- }
545
- try {
546
- content = fs.readFileSync(fullPath, 'utf-8');
547
- } catch (err) {
548
- return { error: `File not found or unreadable: ${err.message}` };
549
- }
550
- }
551
-
552
- const allLines = content.split('\n');
553
- const totalLines = allLines.length;
554
- const start = Math.max(1, startLine);
555
- let end = params.endLine || totalLines;
556
- end = Math.min(end, totalLines);
557
-
558
- // 限制返回行数
559
- if (end - start + 1 > maxLines) {
560
- end = start + maxLines - 1;
561
- }
562
-
563
- const selectedLines = allLines.slice(start - 1, end);
564
-
565
- // 推断语言
566
- const ext = path.extname(filePath).toLowerCase();
567
- const language = LanguageService.langFromExt(ext);
568
-
569
- const readResult = {
570
- filePath,
571
- totalLines,
572
- startLine: start,
573
- endLine: end,
574
- content: selectedLines.join('\n'),
575
- language,
576
- };
577
-
578
- // 缓存读取结果
579
- state._readCache.set(readCacheKey, { content: readResult.content, totalLines, language });
580
-
581
- return readResult;
582
- },
583
- };
584
-
585
- // ────────────────────────────────────────────────────────────
586
- // 2b. list_project_structure — 项目目录结构 (v10 Agent-Pull)
587
- // ────────────────────────────────────────────────────────────
588
- const listProjectStructure = {
589
- name: 'list_project_structure',
590
- description:
591
- '列出项目目录结构和文件统计信息。不读取文件内容,只返回目录树和元数据。' +
592
- '适用场景:了解项目整体布局、识别关键目录、规划探索路径。',
593
- parameters: {
594
- type: 'object',
595
- properties: {
596
- directory: { type: 'string', description: '相对于项目根目录的子目录路径,默认根目录' },
597
- depth: { type: 'number', description: '目录展开深度,默认 3' },
598
- includeStats: {
599
- type: 'boolean',
600
- description: '是否包含文件统计(语言分布、行数),默认 true',
601
- },
602
- },
603
- },
604
- handler: async (params, ctx) => {
605
- const directory = params.directory || '';
606
- const depth = Math.min(params.depth ?? 3, 5); // 最深 5 层
607
- const includeStats = params.includeStats !== false;
608
- const projectRoot = ctx.projectRoot || process.cwd();
609
-
610
- // 安全检查
611
- const normalized = path.normalize(directory);
612
- if (normalized.startsWith('..') || path.isAbsolute(normalized)) {
613
- return { error: 'Path traversal not allowed. Use relative paths within the project.' };
614
- }
615
- const targetDir = directory ? path.resolve(projectRoot, normalized) : projectRoot;
616
- if (!targetDir.startsWith(projectRoot)) {
617
- return { error: 'Path traversal not allowed.' };
618
- }
619
-
620
- const treeLines = [];
621
- const stats = { totalFiles: 0, totalDirs: 0, byLanguage: {}, totalLines: 0 };
622
-
623
- const walk = (dir, relBase, currentDepth, prefix) => {
624
- if (currentDepth > depth) {
625
- return;
626
- }
627
- let entries;
628
- try {
629
- entries = fs.readdirSync(dir, { withFileTypes: true });
630
- } catch {
631
- return;
632
- }
633
-
634
- // 排序: 目录在前,文件在后
635
- entries.sort((a, b) => {
636
- const aIsDir = a.isDirectory();
637
- const bIsDir = b.isDirectory();
638
- if (aIsDir !== bIsDir) {
639
- return aIsDir ? -1 : 1;
640
- }
641
- return a.name.localeCompare(b.name);
642
- });
643
-
644
- // 过滤隐藏和三方
645
- entries = entries.filter((e) => {
646
- if (e.name.startsWith('.')) {
647
- return false;
648
- }
649
- const rel = relBase ? `${relBase}/${e.name}` : e.name;
650
- if (THIRD_PARTY_RE.test(`${rel}/`)) {
651
- return false;
652
- }
653
- return true;
654
- });
655
-
656
- for (let i = 0; i < entries.length; i++) {
657
- const entry = entries[i];
658
- const isLast = i === entries.length - 1;
659
- const connector = isLast ? '└── ' : '├── ';
660
- const childPrefix = prefix + (isLast ? ' ' : '│ ');
661
- const rel = relBase ? `${relBase}/${entry.name}` : entry.name;
662
- const fullPath = path.join(dir, entry.name);
663
-
664
- if (entry.isDirectory()) {
665
- // 计算子文件数
666
- let childCount = 0;
667
- try {
668
- childCount = fs.readdirSync(fullPath).length;
669
- } catch {
670
- /* skip */
671
- }
672
- treeLines.push(`${prefix}${connector}${entry.name}/ (${childCount})`);
673
- stats.totalDirs++;
674
- walk(fullPath, rel, currentDepth + 1, childPrefix);
675
- } else if (entry.isFile()) {
676
- const ext = path.extname(entry.name).toLowerCase();
677
- let lineCount = 0;
678
- let size = 0;
679
- if (includeStats) {
680
- try {
681
- const st = fs.statSync(fullPath);
682
- size = st.size;
683
- if (SOURCE_EXT_RE.test(entry.name) && size < 512 * 1024) {
684
- const content = fs.readFileSync(fullPath, 'utf-8');
685
- lineCount = content.split('\n').length;
686
- stats.totalLines += lineCount;
687
- }
688
- } catch {
689
- /* skip */
690
- }
691
- }
692
- const lang = LanguageService.displayNameFromExt(ext);
693
- if (lang !== ext) {
694
- stats.byLanguage[lang] = (stats.byLanguage[lang] || 0) + 1;
695
- }
696
- const sizeLabel = size > 1024 ? `${(size / 1024).toFixed(0)}KB` : `${size}B`;
697
- const lineLabel = lineCount > 0 ? `, ${lineCount}L` : '';
698
- treeLines.push(`${prefix}${connector}${entry.name} (${sizeLabel}${lineLabel})`);
699
- stats.totalFiles++;
700
- }
701
- }
702
- };
703
-
704
- walk(targetDir, directory, 1, '');
705
-
706
- return {
707
- directory: directory || '.',
708
- tree: treeLines.join('\n'),
709
- stats: includeStats ? stats : undefined,
710
- };
711
- },
712
- };
713
-
714
- // ────────────────────────────────────────────────────────────
715
- // 2c. get_file_summary — 文件摘要 (v10 Agent-Pull)
716
- // ────────────────────────────────────────────────────────────
717
-
718
- /** 语言相关的声明提取正则 */
719
- const SUMMARY_EXTRACTORS = {
720
- objectivec: {
721
- imports: /^\s*(#import\s+.+|#include\s+.+|@import\s+\w+;)/gm,
722
- declarations:
723
- /^\s*(@interface\s+\w+[\s:(].*|@protocol\s+\w+[\s<(].*|@implementation\s+\w+|typedef\s+(?:NS_ENUM|NS_OPTIONS)\s*\([^)]+\)\s*\{?)/gm,
724
- methods: /^\s*[-+]\s*\([^)]+\)\s*[^;{]+/gm,
725
- properties: /^\s*@property\s*\([^)]*\)\s*[^;]+;/gm,
726
- },
727
- swift: {
728
- imports: /^\s*import\s+\w+/gm,
729
- declarations:
730
- /^\s*(?:open|public|internal|fileprivate|private|final)?\s*(?:class|struct|enum|protocol|actor|extension)\s+\w+[^{]*/gm,
731
- methods:
732
- /^\s*(?:open|public|internal|fileprivate|private|override|static|class)?\s*func\s+\w+[^{]*/gm,
733
- properties:
734
- /^\s*(?:open|public|internal|fileprivate|private|static|class|lazy)?\s*(?:var|let)\s+\w+\s*:\s*[^={\n]+/gm,
735
- },
736
- javascript: {
737
- imports: /^\s*(?:import\s+.+from\s+['"].+['"]|const\s+\{?\s*\w+.*\}?\s*=\s*require\s*\(.+\))/gm,
738
- declarations: /^\s*(?:export\s+)?(?:default\s+)?(?:class|function|const|let|var)\s+\w+/gm,
739
- methods: /^\s*(?:async\s+)?(?:static\s+)?(?:get\s+|set\s+)?(?:#?\w+)\s*\([^)]*\)\s*\{/gm,
740
- },
741
- typescript: {
742
- imports: /^\s*import\s+.+from\s+['"].+['"]/gm,
743
- declarations:
744
- /^\s*(?:export\s+)?(?:default\s+)?(?:class|interface|type|enum|function|const|let|var|abstract\s+class)\s+\w+/gm,
745
- methods:
746
- /^\s*(?:async\s+)?(?:static\s+)?(?:public|private|protected)?\s*(?:get\s+|set\s+)?(?:#?\w+)\s*\([^)]*\)\s*[:{]/gm,
747
- },
748
- python: {
749
- imports: /^\s*(?:import\s+\w+|from\s+\w+\s+import\s+.+)/gm,
750
- declarations: /^\s*class\s+\w+[^:]*:/gm,
751
- methods: /^\s*(?:async\s+)?def\s+\w+\s*\([^)]*\)/gm,
752
- },
753
- go: {
754
- imports: /^\s*(?:import\s+"[^"]+"|import\s+\w+\s+"[^"]+")/gm,
755
- declarations:
756
- /^\s*(?:type\s+\w+\s+(?:struct|interface|func)\b.*)/gm,
757
- methods:
758
- /^\s*func\s+(?:\(\s*\w+\s+\*?\w+\s*\)\s+)?\w+\s*\([^)]*\)[^{]*/gm,
759
- },
760
- java: {
761
- imports: /^\s*import\s+(?:static\s+)?[\w.]+\*?;/gm,
762
- declarations:
763
- /^\s*(?:public|private|protected)?\s*(?:abstract|final|static)?\s*(?:class|interface|enum|record|@interface)\s+\w+/gm,
764
- methods:
765
- /^\s*(?:public|private|protected)?\s*(?:abstract|static|final|synchronized|default)?\s*(?:<[^>]+>\s+)?\w[\w<>\[\],\s]*\s+\w+\s*\([^)]*\)/gm,
766
- },
767
- kotlin: {
768
- imports: /^\s*import\s+[\w.]+/gm,
769
- declarations:
770
- /^\s*(?:open|abstract|data|sealed|inner|value|inline)?\s*(?:class|interface|object|enum\s+class|fun\s+interface)\s+\w+/gm,
771
- methods:
772
- /^\s*(?:override\s+)?(?:suspend\s+)?(?:fun|val|var)\s+(?:<[^>]+>\s+)?\w+/gm,
773
- },
774
- dart: {
775
- imports: /^\s*import\s+['"][^'"]+['"];?/gm,
776
- declarations:
777
- /^\s*(?:abstract\s+|sealed\s+)?(?:class|mixin|extension|enum|typedef)\s+\w+[^{]*/gm,
778
- methods:
779
- /^\s*(?:@override\s+)?(?:static\s+)?(?:Future|Stream|void|\w[\w<>?]*)?\s+\w+\s*\([^)]*\)/gm,
780
- properties:
781
- /^\s*(?:static\s+)?(?:final\s+|late\s+|const\s+)?(?:\w[\w<>?]*)\s+\w+\s*[;=]/gm,
782
- },
783
- };
784
- // Alias variants
785
- SUMMARY_EXTRACTORS['objectivec++'] = SUMMARY_EXTRACTORS.objectivec;
786
- SUMMARY_EXTRACTORS.jsx = SUMMARY_EXTRACTORS.javascript;
787
- SUMMARY_EXTRACTORS.tsx = SUMMARY_EXTRACTORS.typescript;
788
-
789
- const getFileSummary = {
790
- name: 'get_file_summary',
791
- description:
792
- '获取文件的结构摘要(导入、声明、方法签名),不包含实现代码。' +
793
- '比 read_project_file 更轻量,适合快速了解文件角色和 API。',
794
- parameters: {
795
- type: 'object',
796
- properties: {
797
- filePath: { type: 'string', description: '相对于项目根目录的文件路径' },
798
- },
799
- required: ['filePath'],
800
- },
801
- handler: async (params, ctx) => {
802
- const filePath = params.filePath || params.file_path || params.path || params.file;
803
- const projectRoot = ctx.projectRoot || process.cwd();
804
-
805
- if (!filePath || typeof filePath !== 'string') {
806
- return { error: '参数错误: 请提供 filePath' };
807
- }
808
-
809
- // 安全检查
810
- const normalized = path.normalize(filePath);
811
- if (normalized.startsWith('..') || path.isAbsolute(normalized)) {
812
- return { error: 'Path traversal not allowed.' };
813
- }
814
-
815
- // 优先从内存缓存读取
816
- const fileCache = ctx.fileCache || null;
817
- let content = null;
818
-
819
- if (fileCache && Array.isArray(fileCache)) {
820
- const cached = fileCache.find(
821
- (f) =>
822
- (f.relativePath || f.path || '') === filePath ||
823
- (f.relativePath || f.path || '') === normalized
824
- );
825
- if (cached) {
826
- content = cached.content;
827
- }
828
- }
829
-
830
- if (content === null) {
831
- const fullPath = path.resolve(projectRoot, normalized);
832
- if (!fullPath.startsWith(projectRoot)) {
833
- return { error: 'Path traversal not allowed.' };
834
- }
835
- try {
836
- content = fs.readFileSync(fullPath, 'utf-8');
837
- } catch (err) {
838
- return { error: `File not found or unreadable: ${err.message}` };
839
- }
840
- }
841
-
842
- // 推断语言
843
- const ext = path.extname(filePath).toLowerCase();
844
- const language = LanguageService.langFromExt(ext);
845
- const extractor = SUMMARY_EXTRACTORS[language];
846
-
847
- const result = {
848
- filePath,
849
- language,
850
- lineCount: content.split('\n').length,
851
- imports: [],
852
- declarations: [],
853
- methods: [],
854
- properties: [],
855
- };
856
-
857
- if (!extractor) {
858
- // 未知语言: 返回前 30 行作为概览
859
- result.preview = content.split('\n').slice(0, 30).join('\n');
860
- return result;
861
- }
862
-
863
- // 提取各类声明
864
- const extract = (regex) => {
865
- const matches = [];
866
- let m;
867
- regex.lastIndex = 0;
868
- while ((m = regex.exec(content)) !== null) {
869
- matches.push(m[0].trim());
870
- }
871
- return matches;
872
- };
873
-
874
- if (extractor.imports) {
875
- result.imports = extract(extractor.imports);
876
- }
877
- if (extractor.declarations) {
878
- result.declarations = extract(extractor.declarations);
879
- }
880
- if (extractor.methods) {
881
- result.methods = extract(extractor.methods).slice(0, 50); // 限制数量
882
- }
883
- if (extractor.properties) {
884
- result.properties = extract(extractor.properties).slice(0, 30);
885
- }
886
-
887
- return result;
888
- },
889
- };
890
-
891
- // ────────────────────────────────────────────────────────────
892
- // 2d. semantic_search_code — 语义搜索 (v10 Agent-Pull)
893
- // ────────────────────────────────────────────────────────────
894
- const semanticSearchCode = {
895
- name: 'semantic_search_code',
896
- description:
897
- '在知识库中进行语义搜索。使用自然语言描述你要查找的代码模式或概念,' +
898
- '返回语义最相关的知识条目。比关键词搜索更适合模糊/概念性查询。' +
899
- '示例: "网络请求的错误处理策略"、"线程安全的单例实现"',
900
- parameters: {
901
- type: 'object',
902
- properties: {
903
- query: { type: 'string', description: '自然语言搜索查询' },
904
- topK: { type: 'number', description: '返回结果数量,默认 5' },
905
- category: { type: 'string', description: '按分类过滤 (View/Service/Network/Model 等)' },
906
- language: { type: 'string', description: '按语言过滤 (swift/objectivec 等)' },
907
- },
908
- required: ['query'],
909
- },
910
- handler: async (params, ctx) => {
911
- const query = params.query || params.search || params.keyword;
912
- const topK = Math.min(params.topK ?? 5, 20);
913
- const { category, language } = params;
914
-
915
- if (!query || typeof query !== 'string') {
916
- return { error: '参数错误: 请提供 query (自然语言搜索查询)' };
917
- }
918
-
919
- // 尝试获取 SearchEngine
920
- let searchEngine = null;
921
- try {
922
- searchEngine = ctx.container?.get('searchEngine');
923
- } catch {
924
- /* not available */
925
- }
926
-
927
- if (!searchEngine) {
928
- // 尝试获取 VectorStore 直接搜索
929
- let vectorStore = null;
930
- try {
931
- vectorStore = ctx.container?.get('vectorStore');
932
- } catch {
933
- /* not available */
934
- }
935
-
936
- if (!vectorStore) {
937
- return {
938
- error:
939
- '语义搜索不可用: SearchEngine 和 VectorStore 均未初始化。可使用 search_project_code 进行关键词搜索替代。',
940
- fallbackTool: 'search_project_code',
941
- };
942
- }
943
-
944
- // 直接使用 VectorStore — 需要 embedding
945
- let aiProvider = null;
946
- try {
947
- aiProvider = ctx.container?.get('aiProvider');
948
- } catch {
949
- /* not available */
950
- }
951
-
952
- if (!aiProvider || typeof aiProvider.generateEmbedding !== 'function') {
953
- // 向量搜索需要 embedding,降级到关键词匹配
954
- const filter = {};
955
- if (category) {
956
- filter.category = category;
957
- }
958
- if (language) {
959
- filter.language = language;
960
- }
961
-
962
- const results = await vectorStore.hybridSearch([], query, { topK, filter });
963
- return {
964
- mode: 'keyword-fallback',
965
- query,
966
- message: 'AI Provider 不支持 embedding,已降级到关键词匹配',
967
- results: results.map((r) => ({
968
- id: r.item.id,
969
- content: (r.item.content || '').slice(0, 500),
970
- score: Math.round(r.score * 100) / 100,
971
- metadata: r.item.metadata || {},
972
- })),
973
- };
974
- }
975
-
976
- // 生成 embedding → 向量搜索
977
- try {
978
- const embedding = await aiProvider.generateEmbedding(query);
979
- const filter = {};
980
- if (category) {
981
- filter.category = category;
982
- }
983
- if (language) {
984
- filter.language = language;
985
- }
986
-
987
- const results = await vectorStore.hybridSearch(embedding, query, { topK, filter });
988
- return {
989
- mode: 'vector',
990
- query,
991
- results: results.map((r) => ({
992
- id: r.item.id,
993
- content: (r.item.content || '').slice(0, 500),
994
- score: Math.round(r.score * 100) / 100,
995
- metadata: r.item.metadata || {},
996
- })),
997
- };
998
- } catch (err) {
999
- return { error: `向量搜索失败: ${err.message}`, fallbackTool: 'search_project_code' };
1000
- }
1001
- }
1002
-
1003
- // 使用 SearchEngine (BM25 + 可选向量)
1004
- try {
1005
- const result = await searchEngine.search(query, {
1006
- mode: 'semantic',
1007
- limit: topK * 2,
1008
- groupByKind: true,
1009
- });
1010
-
1011
- let items = result?.items || [];
1012
- const actualMode = result?.mode || 'bm25';
1013
-
1014
- // 应用过滤
1015
- if (category) {
1016
- items = items.filter((i) => (i.category || '').toLowerCase() === category.toLowerCase());
1017
- }
1018
- if (language) {
1019
- items = items.filter((i) => (i.language || '').toLowerCase() === language.toLowerCase());
1020
- }
1021
- items = items.slice(0, topK);
1022
-
1023
- return {
1024
- mode: actualMode,
1025
- query,
1026
- degraded: actualMode !== 'semantic',
1027
- totalResults: items.length,
1028
- results: items.map((item) => ({
1029
- id: item.id,
1030
- title: item.title || '',
1031
- content: (item.content || item.description || '').slice(0, 500),
1032
- score: Math.round((item.score || 0) * 100) / 100,
1033
- knowledgeType: item.knowledgeType || item.kind || '',
1034
- category: item.category || '',
1035
- language: item.language || '',
1036
- })),
1037
- };
1038
- } catch (err) {
1039
- return { error: `搜索失败: ${err.message}`, fallbackTool: 'search_project_code' };
1040
- }
1041
- },
1042
- };
1043
-
1044
- // ────────────────────────────────────────────────────────────
1045
- // 3. search_recipes
1046
- // ────────────────────────────────────────────────────────────
1047
- const searchRecipes = {
1048
- name: 'search_recipes',
1049
- description:
1050
- '搜索知识库中的 Recipe(代码片段/最佳实践/架构模式)。支持关键词搜索和按分类/语言/类型筛选。',
1051
- parameters: {
1052
- type: 'object',
1053
- properties: {
1054
- keyword: { type: 'string', description: '搜索关键词' },
1055
- category: {
1056
- type: 'string',
1057
- description: '分类过滤 (View/Service/Tool/Model/Network/Storage/UI/Utility)',
1058
- },
1059
- language: { type: 'string', description: '编程语言过滤 (swift/objectivec/typescript 等)' },
1060
- knowledgeType: {
1061
- type: 'string',
1062
- description: '知识类型过滤 (code-standard/code-pattern/architecture/best-practice 等)',
1063
- },
1064
- limit: { type: 'number', description: '返回数量上限,默认 10' },
1065
- },
1066
- },
1067
- handler: async (params, ctx) => {
1068
- const knowledgeService = ctx.container.get('knowledgeService');
1069
- const { keyword, category, language, knowledgeType, limit = 10 } = params;
1070
-
1071
- if (keyword) {
1072
- return knowledgeService.search(keyword, { page: 1, pageSize: limit });
1073
- }
1074
-
1075
- const filters = { lifecycle: 'active' };
1076
- if (category) {
1077
- filters.category = category;
1078
- }
1079
- if (language) {
1080
- filters.language = language;
1081
- }
1082
- if (knowledgeType) {
1083
- filters.knowledgeType = knowledgeType;
1084
- }
1085
-
1086
- return knowledgeService.list(filters, { page: 1, pageSize: limit });
1087
- },
1088
- };
1089
-
1090
- // ────────────────────────────────────────────────────────────
1091
- // 2. search_candidates
1092
- // ────────────────────────────────────────────────────────────
1093
- const searchCandidates = {
1094
- name: 'search_candidates',
1095
- description: '搜索或列出候选项(待审核的代码片段)。支持关键词搜索和按状态/语言/分类筛选。',
1096
- parameters: {
1097
- type: 'object',
1098
- properties: {
1099
- keyword: { type: 'string', description: '搜索关键词' },
1100
- status: { type: 'string', description: '状态过滤 (pending/approved/rejected/applied)' },
1101
- language: { type: 'string', description: '编程语言过滤' },
1102
- category: { type: 'string', description: '分类过滤' },
1103
- limit: { type: 'number', description: '返回数量上限,默认 10' },
1104
- },
1105
- },
1106
- handler: async (params, ctx) => {
1107
- const knowledgeService = ctx.container.get('knowledgeService');
1108
- const { keyword, status, language, category, limit = 10 } = params;
1109
-
1110
- if (keyword) {
1111
- return knowledgeService.search(keyword, { page: 1, pageSize: limit });
1112
- }
1113
-
1114
- // V3: status 映射为 lifecycle
1115
- const filters = {};
1116
- if (status) {
1117
- filters.lifecycle = status;
1118
- }
1119
- if (language) {
1120
- filters.language = language;
1121
- }
1122
- if (category) {
1123
- filters.category = category;
1124
- }
1125
-
1126
- return knowledgeService.list(filters, { page: 1, pageSize: limit });
1127
- },
1128
- };
1129
-
1130
- // ────────────────────────────────────────────────────────────
1131
- // 3. get_recipe_detail
1132
- // ────────────────────────────────────────────────────────────
1133
- const getRecipeDetail = {
1134
- name: 'get_recipe_detail',
1135
- description: '获取单个 Recipe 的完整详情(代码、摘要、使用指南、关系等)。',
1136
- parameters: {
1137
- type: 'object',
1138
- properties: {
1139
- recipeId: { type: 'string', description: 'Recipe ID' },
1140
- },
1141
- required: ['recipeId'],
1142
- },
1143
- handler: async (params, ctx) => {
1144
- const knowledgeService = ctx.container.get('knowledgeService');
1145
- try {
1146
- const entry = await knowledgeService.get(params.recipeId);
1147
- return typeof entry.toJSON === 'function' ? entry.toJSON() : entry;
1148
- } catch {
1149
- return { error: `Knowledge entry '${params.recipeId}' not found` };
1150
- }
1151
- },
1152
- };
1153
-
1154
- // ────────────────────────────────────────────────────────────
1155
- // 4. get_project_stats
1156
- // ────────────────────────────────────────────────────────────
1157
- const getProjectStats = {
1158
- name: 'get_project_stats',
1159
- description:
1160
- '获取项目知识库的整体统计:Recipe 数量/分类分布、候选项数量/状态分布、知识图谱节点/边数。',
1161
- parameters: { type: 'object', properties: {} },
1162
- handler: async (_params, ctx) => {
1163
- const knowledgeService = ctx.container.get('knowledgeService');
1164
- const stats = await knowledgeService.getStats();
1165
-
1166
- // 尝试获取知识图谱统计
1167
- let graphStats = null;
1168
- try {
1169
- const kgService = ctx.container.get('knowledgeGraphService');
1170
- graphStats = kgService.getStats();
1171
- } catch {
1172
- /* KG not available */
1173
- }
1174
-
1175
- return {
1176
- knowledge: stats,
1177
- knowledgeGraph: graphStats,
1178
- };
1179
- },
1180
- };
1181
-
1182
- // ────────────────────────────────────────────────────────────
1183
- // 5. search_knowledge
1184
- // ────────────────────────────────────────────────────────────
1185
- const searchKnowledge = {
1186
- name: 'search_knowledge',
1187
- description: 'RAG 知识库语义搜索 — 结合向量检索和关键词检索,返回与查询最相关的知识片段。',
1188
- parameters: {
1189
- type: 'object',
1190
- properties: {
1191
- query: { type: 'string', description: '搜索查询' },
1192
- topK: { type: 'number', description: '返回结果数,默认 5' },
1193
- },
1194
- required: ['query'],
1195
- },
1196
- handler: async (params, ctx) => {
1197
- const { query, topK = 5 } = params;
1198
-
1199
- // 优先使用 SearchEngine(有 BM25 + 向量搜索)
1200
- try {
1201
- const searchEngine = ctx.container.get('searchEngine');
1202
- const results = await searchEngine.search(query, { limit: topK });
1203
- if (results && results.length > 0) {
1204
- const enriched = results.slice(0, topK).map((r, i) => ({
1205
- ...r,
1206
- reasoning: {
1207
- whyRelevant:
1208
- r.score != null
1209
- ? `匹配分 ${(r.score * 100).toFixed(0)}%${r.matchType ? ` (${r.matchType})` : ''}`
1210
- : '语义相关',
1211
- rank: i + 1,
1212
- },
1213
- }));
1214
- const topScore = enriched[0]?.score ?? 0;
1215
- return {
1216
- source: 'searchEngine',
1217
- results: enriched,
1218
- _meta: {
1219
- confidence: topScore > 0.7 ? 'high' : topScore > 0.3 ? 'medium' : 'low',
1220
- hint: topScore < 0.3 ? '匹配度较低,结果可能不够相关。建议尝试更具体的查询词。' : null,
1221
- },
1222
- };
1223
- }
1224
- } catch {
1225
- /* SearchEngine not available */
1226
- }
1227
-
1228
- // 降级: RetrievalFunnel + 全量候选
1229
- try {
1230
- const funnel = ctx.container.get('retrievalFunnel');
1231
- const knowledgeRepo = ctx.container.get('knowledgeRepository');
1232
- const allResult = await knowledgeRepo.findWithPagination({}, { page: 1, pageSize: 500 });
1233
- const allRecipes = allResult?.items || [];
1234
-
1235
- // 规范化为 funnel 输入格式
1236
- const candidates = allRecipes.map((r) => ({
1237
- id: r.id,
1238
- title: r.title,
1239
- content: r.content || r.code || '',
1240
- description: r.description || '',
1241
- language: r.language,
1242
- category: r.category,
1243
- trigger: r.trigger || '',
1244
- }));
1245
-
1246
- if (candidates.length > 0) {
1247
- const results = await funnel.execute(query, candidates, {});
1248
- return { source: 'retrievalFunnel', results: results.slice(0, topK) };
1249
- }
1250
- } catch {
1251
- /* RetrievalFunnel not available */
1252
- }
1253
-
1254
- return {
1255
- source: 'none',
1256
- results: [],
1257
- message: 'No search engine available',
1258
- _meta: {
1259
- confidence: 'none',
1260
- hint: '搜索引擎不可用。请确认向量索引已构建(rebuild_index)。',
1261
- },
1262
- };
1263
- },
1264
- };
1265
-
1266
- // ────────────────────────────────────────────────────────────
1267
- // 6. get_related_recipes
1268
- // ────────────────────────────────────────────────────────────
1269
- const getRelatedRecipes = {
1270
- name: 'get_related_recipes',
1271
- description: '通过知识图谱查询某个 Recipe 的关联 Recipe(requires/extends/enforces 等关系)。',
1272
- parameters: {
1273
- type: 'object',
1274
- properties: {
1275
- recipeId: { type: 'string', description: 'Recipe ID' },
1276
- relation: {
1277
- type: 'string',
1278
- description:
1279
- '关系类型过滤 (requires/extends/enforces/depends_on/inherits/implements/calls/prerequisite),不传则返回全部关系',
1280
- },
1281
- },
1282
- required: ['recipeId'],
1283
- },
1284
- handler: async (params, ctx) => {
1285
- const kgService = ctx.container.get('knowledgeGraphService');
1286
- const { recipeId, relation } = params;
1287
-
1288
- if (relation) {
1289
- const edges = kgService.getRelated(recipeId, 'recipe', relation);
1290
- return { recipeId, relation, edges };
1291
- }
1292
-
1293
- const edges = kgService.getEdges(recipeId, 'recipe', 'both');
1294
- return { recipeId, ...edges };
1295
- },
1296
- };
1297
-
1298
- // ────────────────────────────────────────────────────────────
1299
- // 7. summarize_code
1300
- // ────────────────────────────────────────────────────────────
1301
- const summarizeCode = {
1302
- name: 'summarize_code',
1303
- description: 'AI 代码摘要 — 分析代码片段并生成结构化摘要(包含功能描述、关键 API、使用建议)。',
1304
- parameters: {
1305
- type: 'object',
1306
- properties: {
1307
- code: { type: 'string', description: '代码内容' },
1308
- language: { type: 'string', description: '编程语言' },
1309
- },
1310
- required: ['code'],
1311
- },
1312
- handler: async (params, ctx) => {
1313
- if (!ctx.aiProvider) {
1314
- return { error: 'AI provider not available' };
1315
- }
1316
- return ctx.aiProvider.summarize(params.code, params.language);
1317
- },
1318
- };
1319
-
1320
- // ────────────────────────────────────────────────────────────
1321
- // 8. extract_recipes
1322
- // ────────────────────────────────────────────────────────────
1323
- const extractRecipes = {
1324
- name: 'extract_recipes',
1325
- description:
1326
- '从源码文件中批量提取可复用的 Recipe 结构(代码标准、设计模式、最佳实践)。支持自动 provider fallback。',
1327
- parameters: {
1328
- type: 'object',
1329
- properties: {
1330
- targetName: { type: 'string', description: 'SPM Target / 模块名称' },
1331
- files: {
1332
- type: 'array',
1333
- items: {
1334
- type: 'object',
1335
- properties: { name: { type: 'string' }, content: { type: 'string' } },
1336
- },
1337
- description: '文件数组 [{name, content}]',
1338
- },
1339
- },
1340
- required: ['targetName', 'files'],
1341
- },
1342
- handler: async (params, ctx) => {
1343
- if (!ctx.aiProvider) {
1344
- return { error: 'AI provider not available' };
1345
- }
1346
- const { targetName, files, comprehensive } = params;
1347
-
1348
- // 加载语言参考 Skill(如有),注入到 AI 提取 prompt
1349
- let skillReference = null;
1350
- try {
1351
- const { loadBootstrapSkills } = await import('../../external/mcp/handlers/bootstrap.js');
1352
- const langProfile = ctx.aiProvider._detectLanguageProfile?.(files);
1353
- const primaryLang = langProfile?.primaryLanguage;
1354
- if (primaryLang) {
1355
- const skillCtx = loadBootstrapSkills(primaryLang);
1356
- skillReference = skillCtx.languageSkill ? skillCtx.languageSkill.substring(0, 2000) : null;
1357
- }
1358
- } catch {
1359
- /* Skills not available, proceed without */
1360
- }
1361
-
1362
- // AST 代码结构分析(如可用),注入到 AI 提取 prompt
1363
- let astContext = null;
1364
- try {
1365
- const { analyzeProject, generateContextForAgent, isAvailable } = await import(
1366
- '../../../core/AstAnalyzer.js'
1367
- );
1368
- if (isAvailable()) {
1369
- const sourceFiles = files
1370
- .filter((f) => /\.(m|mm|h|swift|js|ts|jsx|tsx)$/.test(f.name || ''))
1371
- .map((f) => ({ path: f.name, source: f.content }));
1372
- if (sourceFiles.length > 0) {
1373
- const langProfile2 = ctx.aiProvider._detectLanguageProfile?.(files);
1374
- const lang = langProfile2?.primaryLanguage === 'swift' ? 'swift' : 'objc';
1375
- const summary = analyzeProject(sourceFiles, lang);
1376
- astContext = generateContextForAgent(summary);
1377
- }
1378
- }
1379
- } catch {
1380
- /* AST not available, proceed without */
1381
- }
1382
-
1383
- const extractOpts = {};
1384
- if (skillReference) {
1385
- extractOpts.skillReference = skillReference;
1386
- }
1387
- if (astContext) {
1388
- extractOpts.astContext = astContext;
1389
- }
1390
- if (comprehensive) {
1391
- extractOpts.comprehensive = true;
1392
- }
1393
- // 传递用户语言偏好,让 AI 输出匹配用户语言
1394
- if (ctx.lang && ctx.lang !== 'en') {
1395
- extractOpts.lang = ctx.lang;
1396
- }
1397
-
1398
- // 首选:使用当前 aiProvider
1399
- let recipes;
1400
- let fallbackUsed;
1401
- try {
1402
- recipes = await ctx.aiProvider.extractRecipes(targetName, files, extractOpts);
1403
- } catch (primaryErr) {
1404
- // 尝试 fallback(如果 AiFactory 可用)
1405
- let recovered = false;
1406
- try {
1407
- const aiFactory = ctx.container?.singletons?._aiFactory;
1408
- if (aiFactory?.isGeoOrProviderError?.(primaryErr)) {
1409
- const currentProvider = (process.env.ASD_AI_PROVIDER || 'google').toLowerCase();
1410
- const fallbacks = aiFactory.getAvailableFallbacks(currentProvider);
1411
- for (const fbName of fallbacks) {
1412
- try {
1413
- const fbProvider = aiFactory.createProvider({ provider: fbName });
1414
- recipes = await fbProvider.extractRecipes(targetName, files, extractOpts);
1415
- fallbackUsed = fbName;
1416
- recovered = true;
1417
- break;
1418
- } catch {
1419
- /* next fallback */
1420
- }
1421
- }
1422
- }
1423
- } catch {
1424
- /* AiFactory not available */
1425
- }
1426
- if (!recovered) {
1427
- throw primaryErr;
1428
- }
1429
- }
1430
-
1431
- if (!Array.isArray(recipes)) {
1432
- recipes = [];
1433
- }
1434
- if (recipes.length === 0) {
1435
- ctx.logger?.warn?.(
1436
- `[extract_recipes] AI returned 0 recipes for ${targetName} (${files.length} files)`
1437
- );
1438
- }
1439
-
1440
- // ── V3 直透:AI 已输出完整 V3 结构,仅做来源标记 + 程序化评分/标签 ──
1441
- let qualityScorer = null;
1442
- let recipeExtractor = null;
1443
- try {
1444
- qualityScorer = ctx.container?.get?.('qualityScorer');
1445
- } catch {
1446
- /* not available */
1447
- }
1448
- try {
1449
- recipeExtractor = ctx.container?.get?.('recipeExtractor');
1450
- } catch {
1451
- /* not available */
1452
- }
1453
-
1454
- for (const recipe of recipes) {
1455
- // 来源 & 生命周期(非 AI 职责)
1456
- recipe.source = recipe.source || 'ai-scan';
1457
- recipe.lifecycle = recipe.lifecycle || 'pending';
1458
-
1459
- // RecipeExtractor 语义标签增强(程序化补充,不替代 AI tags)
1460
- const codeText = recipe.content?.pattern || '';
1461
- if (recipeExtractor && codeText) {
1462
- try {
1463
- const extracted = recipeExtractor.extractFromContent(
1464
- codeText,
1465
- `${recipe.title || 'unknown'}.${recipe.language || 'unknown'}`,
1466
- ''
1467
- );
1468
- if (extracted.semanticTags?.length > 0) {
1469
- recipe.tags = [...new Set([...(recipe.tags || []), ...extracted.semanticTags])];
1470
- }
1471
- if (
1472
- (!recipe.category || recipe.category === 'Utility') &&
1473
- extracted.category &&
1474
- extracted.category !== 'general'
1475
- ) {
1476
- recipe.category = extracted.category;
1477
- }
1478
- } catch {
1479
- /* best effort */
1480
- }
1481
- }
1482
-
1483
- // QualityScorer 评分 → quality 结构化
1484
- if (qualityScorer) {
1485
- try {
1486
- const scoreResult = qualityScorer.score(recipe);
1487
- recipe.quality = {
1488
- completeness: 0,
1489
- adaptation: 0,
1490
- documentation: 0,
1491
- overall: scoreResult.score ?? 0,
1492
- grade: scoreResult.grade || '',
1493
- };
1494
- } catch {
1495
- /* best effort */
1496
- }
1497
- }
1498
- }
1499
-
1500
- const result = { targetName, extracted: recipes.length, recipes };
1501
- if (fallbackUsed) {
1502
- result.fallbackUsed = fallbackUsed;
1503
- }
1504
- return result;
1505
- },
1506
- };
1507
-
1508
- // ────────────────────────────────────────────────────────────
1509
- // 9. enrich_candidate
1510
- // ────────────────────────────────────────────────────────────
1511
- const enrichCandidate = {
1512
- name: 'enrich_candidate',
1513
- description:
1514
- '① 结构补齐 — 自动填充缺失的结构性语义字段(rationale/knowledgeType/complexity/scope/steps/constraints)。批量处理,只填空不覆盖。建议在 refine_bootstrap_candidates 之前执行。',
1515
- parameters: {
1516
- type: 'object',
1517
- properties: {
1518
- candidateIds: {
1519
- type: 'array',
1520
- items: { type: 'string' },
1521
- description: '候选 ID 列表 (最多 20 个)',
1522
- },
1523
- },
1524
- required: ['candidateIds'],
1525
- },
1526
- handler: async (params, ctx) => {
1527
- if (!ctx.aiProvider) {
1528
- return { error: 'AI provider not available' };
1529
- }
1530
- // V3: 使用 MCP handler enrichCandidates 的逻辑
1531
- const { enrichCandidates: enrichFn } = await import('../../external/mcp/handlers/candidate.js');
1532
- const result = await enrichFn(ctx, { candidateIds: params.candidateIds });
1533
- return result?.data || result;
1534
- },
1535
- };
1536
-
1537
- // ────────────────────────────────────────────────────────────
1538
- // 9b. refine_bootstrap_candidates (Phase 6)
1539
- // ────────────────────────────────────────────────────────────
1540
- const refineBootstrapCandidates = {
1541
- name: 'refine_bootstrap_candidates',
1542
- description:
1543
- '② 内容润色 — 逐条精炼 Bootstrap 候选的内容质量:改善 summary、补充架构 insight、推断 relations 关联、调整 confidence、丰富 tags。建议在 enrich_candidate 之后执行。',
1544
- parameters: {
1545
- type: 'object',
1546
- properties: {
1547
- candidateIds: {
1548
- type: 'array',
1549
- items: { type: 'string' },
1550
- description: '指定候选 ID 列表(可选,默认全部 bootstrap 候选)',
1551
- },
1552
- userPrompt: {
1553
- type: 'string',
1554
- description: '用户自定义润色提示词,指导 AI 润色方向(如“侧重描述线程安全注意事项”)',
1555
- },
1556
- dryRun: { type: 'boolean', description: '仅预览 AI 润色结果,不写入数据库' },
1557
- },
1558
- },
1559
- handler: async (params, ctx) => {
1560
- if (!ctx.aiProvider) {
1561
- return { error: 'AI provider not available' };
1562
- }
1563
- // V3: 委托给 bootstrap handler 的 refine 逻辑
1564
- const { bootstrapRefine } = await import('../../external/mcp/handlers/bootstrap.js');
1565
- const result = await bootstrapRefine(ctx, {
1566
- candidateIds: params.candidateIds,
1567
- userPrompt: params.userPrompt,
1568
- dryRun: params.dryRun,
1569
- });
1570
- return result?.data || result;
1571
- },
1572
- };
1573
-
1574
- // ────────────────────────────────────────────────────────────
1575
- // 10. check_duplicate
1576
- // ────────────────────────────────────────────────────────────
1577
- const checkDuplicate = {
1578
- name: 'check_duplicate',
1579
- description:
1580
- '候选查重 — 检测候选代码是否与已有 Recipe 重复(基于标题/摘要/代码的 Jaccard 相似度)。',
1581
- parameters: {
1582
- type: 'object',
1583
- properties: {
1584
- candidate: { type: 'object', description: '候选对象 { title, summary, code, usageGuide }' },
1585
- candidateId: { type: 'string', description: '或提供候选 ID,从数据库读取' },
1586
- projectRoot: { type: 'string', description: '项目根目录(可选,默认当前项目)' },
1587
- threshold: { type: 'number', description: '相似度阈值,默认 0.5' },
1588
- },
1589
- },
1590
- handler: async (params, ctx) => {
1591
- let cand = params.candidate;
1592
- const projectRoot = params.projectRoot || ctx.projectRoot;
1593
- const threshold = params.threshold ?? 0.5;
1594
-
1595
- // 如果提供 candidateId,从数据库读取条目信息
1596
- if (!cand && params.candidateId) {
1597
- try {
1598
- const knowledgeService = ctx.container.get('knowledgeService');
1599
- const found = await knowledgeService.get(params.candidateId);
1600
- if (found) {
1601
- const json = typeof found.toJSON === 'function' ? found.toJSON() : found;
1602
- cand = {
1603
- title: json.title || '',
1604
- summary: json.description || '',
1605
- code: json.content?.pattern || '',
1606
- usageGuide: '',
1607
- };
1608
- }
1609
- } catch {
1610
- /* ignore */
1611
- }
1612
- }
1613
-
1614
- if (!cand) {
1615
- return { similar: [], message: 'No candidate provided' };
1616
- }
1617
-
1618
- const similar = findSimilarRecipes(projectRoot, cand, {
1619
- threshold,
1620
- topK: 10,
1621
- });
1622
-
1623
- return {
1624
- similar,
1625
- hasDuplicate: similar.some((s) => s.similarity >= 0.7),
1626
- highestSimilarity: similar.length > 0 ? similar[0].similarity : 0,
1627
- _meta: {
1628
- confidence: similar.length === 0 ? 'none' : similar[0].similarity >= 0.7 ? 'high' : 'low',
1629
- hint:
1630
- similar.length === 0
1631
- ? '未发现相似 Recipe,可放心提交。'
1632
- : similar[0].similarity >= 0.7
1633
- ? '发现高度相似 Recipe,建议人工审核是否重复。'
1634
- : '有低相似度匹配,大概率不是重复。',
1635
- },
1636
- };
1637
- },
1638
- };
1639
-
1640
- // ────────────────────────────────────────────────────────────
1641
- // 11. discover_relations
1642
- // ────────────────────────────────────────────────────────────
1643
- const discoverRelations = {
1644
- name: 'discover_relations',
1645
- description:
1646
- 'AI 知识图谱关系发现 — 分析 Recipe 对之间的潜在关系(requires/extends/enforces/calls 等),并自动写入知识图谱。',
1647
- parameters: {
1648
- type: 'object',
1649
- properties: {
1650
- recipePairs: {
1651
- type: 'array',
1652
- items: {
1653
- type: 'object',
1654
- properties: {
1655
- a: {
1656
- type: 'object',
1657
- properties: {
1658
- id: { type: 'string' },
1659
- title: { type: 'string' },
1660
- category: { type: 'string' },
1661
- code: { type: 'string' },
1662
- },
1663
- },
1664
- b: {
1665
- type: 'object',
1666
- properties: {
1667
- id: { type: 'string' },
1668
- title: { type: 'string' },
1669
- category: { type: 'string' },
1670
- code: { type: 'string' },
1671
- },
1672
- },
1673
- },
1674
- },
1675
- description:
1676
- 'Recipe 对数组 [{ a: {id, title, category, code}, b: {id, title, category, code} }]',
1677
- },
1678
- dryRun: { type: 'boolean', description: '仅分析不写入,默认 false' },
1679
- },
1680
- required: ['recipePairs'],
1681
- },
1682
- handler: async (params, ctx) => {
1683
- if (!ctx.aiProvider) {
1684
- return { error: 'AI provider not available' };
1685
- }
1686
-
1687
- const { recipePairs, dryRun = false } = params;
1688
- if (!recipePairs || recipePairs.length === 0) {
1689
- return { relations: [] };
1690
- }
1691
-
1692
- // 构建 LLM prompt
1693
- const pairsText = recipePairs
1694
- .map(
1695
- (p, i) => `
1696
- --- Pair #${i + 1} ---
1697
- Recipe A [${p.a.id}]: ${p.a.title} (${p.a.category}/${p.a.language || ''})
1698
- ${p.a.code ? `Code: ${p.a.code.substring(0, 300)}` : ''}
1699
-
1700
- Recipe B [${p.b.id}]: ${p.b.title} (${p.b.category}/${p.b.language || ''})
1701
- ${p.b.code ? `Code: ${p.b.code.substring(0, 300)}` : ''}`
1702
- )
1703
- .join('\n');
1704
-
1705
- const prompt = `# Role
1706
- You are a Software Architect analyzing relationships between code recipes (knowledge units).
1707
-
1708
- # Goal
1709
- For each Recipe pair below, determine if there is a meaningful relationship.
1710
-
1711
- # Relationship Types
1712
- - requires: A needs B to function
1713
- - extends: A builds upon / enriches B
1714
- - enforces: A enforces rules defined in B
1715
- - depends_on: A depends on B
1716
- - inherits: A inherits from B (class/protocol)
1717
- - implements: A implements interface/protocol defined in B
1718
- - calls: A calls API defined in B
1719
- - prerequisite: B must be learned/applied before A
1720
- - none: No meaningful relationship
1721
-
1722
- # Output
1723
- Return a JSON array. For each pair with a relationship (skip "none"):
1724
- { "index": 0, "from_id": "...", "to_id": "...", "relation": "requires", "confidence": 0.85, "reason": "A uses the network client defined in B" }
1725
-
1726
- Return ONLY a JSON array. No markdown, no extra text. Return [] if no relationships found.
1727
-
1728
- # Recipe Pairs
1729
- ${pairsText}`;
1730
-
1731
- const parsed = await ctx.aiProvider.chatWithStructuredOutput(prompt, {
1732
- openChar: '[',
1733
- closeChar: ']',
1734
- temperature: 0.2,
1735
- });
1736
- const relations = Array.isArray(parsed) ? parsed : [];
1737
-
1738
- // 写入知识图谱(除非 dryRun)
1739
- if (!dryRun && relations.length > 0) {
1740
- try {
1741
- const kgService = ctx.container.get('knowledgeGraphService');
1742
- for (const rel of relations) {
1743
- if (rel.from_id && rel.to_id && rel.relation && rel.relation !== 'none') {
1744
- kgService.addEdge(rel.from_id, 'recipe', rel.to_id, 'recipe', rel.relation, {
1745
- confidence: rel.confidence || 0.5,
1746
- reason: rel.reason || '',
1747
- source: 'ai-discovery',
1748
- });
1749
- }
1750
- }
1751
- } catch {
1752
- /* KG not available */
1753
- }
1754
- }
1755
-
1756
- return {
1757
- analyzed: recipePairs.length,
1758
- relations: relations.filter((r) => r.relation !== 'none'),
1759
- written: dryRun ? 0 : relations.filter((r) => r.relation !== 'none').length,
1760
- };
1761
- },
1762
- };
1763
-
1764
- // ────────────────────────────────────────────────────────────
1765
- // 12. add_graph_edge
1766
- // ────────────────────────────────────────────────────────────
1767
- const addGraphEdge = {
1768
- name: 'add_graph_edge',
1769
- description: '手动添加知识图谱关系边(从 A 到 B 的关系)。',
1770
- parameters: {
1771
- type: 'object',
1772
- properties: {
1773
- fromId: { type: 'string', description: '源节点 ID' },
1774
- fromType: { type: 'string', description: '源节点类型 (recipe/candidate)' },
1775
- toId: { type: 'string', description: '目标节点 ID' },
1776
- toType: { type: 'string', description: '目标节点类型 (recipe/candidate)' },
1777
- relation: {
1778
- type: 'string',
1779
- description:
1780
- '关系类型 (requires/extends/enforces/depends_on/inherits/implements/calls/prerequisite)',
1781
- },
1782
- weight: { type: 'number', description: '权重 0-1,默认 1.0' },
1783
- },
1784
- required: ['fromId', 'fromType', 'toId', 'toType', 'relation'],
1785
- },
1786
- handler: async (params, ctx) => {
1787
- const kgService = ctx.container.get('knowledgeGraphService');
1788
- return kgService.addEdge(
1789
- params.fromId,
1790
- params.fromType,
1791
- params.toId,
1792
- params.toType,
1793
- params.relation,
1794
- { weight: params.weight || 1.0, source: 'manual' }
1795
- );
1796
- },
1797
- };
1798
-
1799
- // ════════════════════════════════════════════════════════════
1800
- // NEW TOOLS (13-31)
1801
- // ════════════════════════════════════════════════════════════
1802
-
1803
- // ────────────────────────────────────────────────────────────
1804
- // 7b. list_guard_rules
1805
- // ────────────────────────────────────────────────────────────
1806
- const listGuardRules = {
1807
- name: 'list_guard_rules',
1808
- description: '列出所有 Guard 规则(boundary-constraint 类型的 Recipe)。支持按语言/状态过滤。',
1809
- parameters: {
1810
- type: 'object',
1811
- properties: {
1812
- language: { type: 'string', description: '按语言过滤 (swift/objc 等)' },
1813
- includeBuiltIn: { type: 'boolean', description: '是否包含内置规则,默认 true' },
1814
- limit: { type: 'number', description: '返回数量上限,默认 50' },
1815
- },
1816
- },
1817
- handler: async (params, ctx) => {
1818
- const { language, includeBuiltIn = true, limit = 50 } = params;
1819
- const results = [];
1820
-
1821
- // 数据库自定义规则
1822
- try {
1823
- const guardService = ctx.container.get('guardService');
1824
- const dbRules = await guardService.listRules({}, { page: 1, pageSize: limit });
1825
- results.push(...(dbRules.data || dbRules.items || []));
1826
- } catch {
1827
- /* not available */
1828
- }
1829
-
1830
- // 内置规则
1831
- if (includeBuiltIn) {
1832
- try {
1833
- const guardCheckEngine = ctx.container.get('guardCheckEngine');
1834
- const builtIn = guardCheckEngine
1835
- .getRules(language || null)
1836
- .filter((r) => r.source === 'built-in');
1837
- results.push(...builtIn);
1838
- } catch {
1839
- /* not available */
1840
- }
1841
- }
1842
-
1843
- return { total: results.length, rules: results.slice(0, limit) };
1844
- },
1845
- };
1846
-
1847
- // ────────────────────────────────────────────────────────────
1848
- // 8b. get_recommendations
1849
- // ────────────────────────────────────────────────────────────
1850
- const getRecommendations = {
1851
- name: 'get_recommendations',
1852
- description: '获取推荐的 Recipe 列表(基于使用频率和质量排序)。',
1853
- parameters: {
1854
- type: 'object',
1855
- properties: {
1856
- limit: { type: 'number', description: '返回数量,默认 10' },
1857
- },
1858
- },
1859
- handler: async (params, ctx) => {
1860
- const knowledgeService = ctx.container.get('knowledgeService');
1861
- // V3: 推荐 = 活跃条目按使用量排序
1862
- return knowledgeService.list(
1863
- { lifecycle: 'active' },
1864
- { page: 1, pageSize: params.limit || 10 }
1865
- );
1866
- },
1867
- };
1868
-
1869
- // ────────────────────────────────────────────────────────────
1870
- // 12. ai_translate
1871
- // ────────────────────────────────────────────────────────────
1872
- const aiTranslate = {
1873
- name: 'ai_translate',
1874
- description: 'AI 翻译 — 将中文 summary/usageGuide 翻译为英文。',
1875
- parameters: {
1876
- type: 'object',
1877
- properties: {
1878
- summary: { type: 'string', description: '中文摘要' },
1879
- usageGuide: { type: 'string', description: '中文使用指南' },
1880
- },
1881
- },
1882
- handler: async (params, ctx) => {
1883
- if (!ctx.aiProvider) {
1884
- return { error: 'AI provider not available' };
1885
- }
1886
- const { summary, usageGuide } = params;
1887
- if (!summary && !usageGuide) {
1888
- return { summaryEn: '', usageGuideEn: '' };
1889
- }
1890
-
1891
- const systemPrompt =
1892
- 'You are a technical translator. Translate from Chinese to English. Keep technical terms unchanged. Return ONLY valid JSON: { "summaryEn": "...", "usageGuideEn": "..." }.';
1893
- const parts = [];
1894
- if (summary) {
1895
- parts.push(`summary: ${summary}`);
1896
- }
1897
- if (usageGuide) {
1898
- parts.push(`usageGuide: ${usageGuide}`);
1899
- }
1900
-
1901
- const parsed = await ctx.aiProvider.chatWithStructuredOutput(parts.join('\n'), {
1902
- systemPrompt,
1903
- temperature: 0.2,
1904
- });
1905
- return parsed || { summaryEn: summary || '', usageGuideEn: usageGuide || '' };
1906
- },
1907
- };
1908
-
1909
- // ────────────────────────────────────────────────────────────
1910
- // 13. guard_check_code
1911
- // ────────────────────────────────────────────────────────────
1912
- const guardCheckCode = {
1913
- name: 'guard_check_code',
1914
- description: '对代码运行 Guard 规则检查,返回违规列表(支持内置规则 + 数据库自定义规则)。',
1915
- parameters: {
1916
- type: 'object',
1917
- properties: {
1918
- code: { type: 'string', description: '待检查的源代码' },
1919
- language: { type: 'string', description: '编程语言 (swift/objc/javascript 等)' },
1920
- scope: { type: 'string', description: '检查范围 (file/target/project),默认 file' },
1921
- },
1922
- required: ['code'],
1923
- },
1924
- handler: async (params, ctx) => {
1925
- const { code, language, scope = 'file' } = params;
1926
-
1927
- // 优先用 GuardCheckEngine(内置 + DB 规则)
1928
- try {
1929
- const engine = ctx.container.get('guardCheckEngine');
1930
- const violations = engine.checkCode(code, language || 'unknown', { scope });
1931
- // reasoning 已由 GuardCheckEngine.checkCode() 内置附加
1932
- return { violationCount: violations.length, violations };
1933
- } catch {
1934
- /* not available */
1935
- }
1936
-
1937
- // 降级到 GuardService.checkCode(仅 DB 规则)
1938
- try {
1939
- const guardService = ctx.container.get('guardService');
1940
- const matches = await guardService.checkCode(code, { language });
1941
- return { violationCount: matches.length, violations: matches };
1942
- } catch (err) {
1943
- return { error: err.message };
1944
- }
1945
- },
1946
- };
1947
-
1948
- // ────────────────────────────────────────────────────────────
1949
- // 14. query_violations
1950
- // ────────────────────────────────────────────────────────────
1951
- const queryViolations = {
1952
- name: 'query_violations',
1953
- description: '查询 Guard 违规历史记录和统计。',
1954
- parameters: {
1955
- type: 'object',
1956
- properties: {
1957
- file: { type: 'string', description: '按文件路径过滤' },
1958
- limit: { type: 'number', description: '返回数量,默认 20' },
1959
- statsOnly: { type: 'boolean', description: '仅返回统计数据,默认 false' },
1960
- },
1961
- },
1962
- handler: async (params, ctx) => {
1963
- const { file, limit = 20, statsOnly = false } = params;
1964
- const store = ctx.container.get('violationsStore');
1965
-
1966
- if (statsOnly) {
1967
- return store.getStats();
1968
- }
1969
-
1970
- if (file) {
1971
- return { runs: store.getRunsByFile(file) };
1972
- }
1973
-
1974
- return store.list({}, { page: 1, limit });
1975
- },
1976
- };
1977
-
1978
- // ────────────────────────────────────────────────────────────
1979
- // 15. generate_guard_rule
1980
- // ────────────────────────────────────────────────────────────
1981
- const generateGuardRule = {
1982
- name: 'generate_guard_rule',
1983
- description: 'AI 生成 Guard 规则 — 描述你想阻止的代码模式,AI 自动生成正则表达式和规则定义。',
1984
- parameters: {
1985
- type: 'object',
1986
- properties: {
1987
- description: {
1988
- type: 'string',
1989
- description: '规则描述(例如 "禁止在主线程使用同步网络请求")',
1990
- },
1991
- language: { type: 'string', description: '目标语言 (swift/objc 等)' },
1992
- severity: { type: 'string', description: '严重程度 (error/warning/info),默认 warning' },
1993
- autoCreate: { type: 'boolean', description: '是否自动创建到数据库,默认 false' },
1994
- },
1995
- required: ['description'],
1996
- },
1997
- handler: async (params, ctx) => {
1998
- if (!ctx.aiProvider) {
1999
- return { error: 'AI provider not available' };
2000
- }
2001
- const { description, language = 'swift', severity = 'warning', autoCreate = false } = params;
2002
-
2003
- const prompt = `Generate a Guard rule for this requirement:
2004
- Description: ${description}
2005
- Language: ${language}
2006
- Severity: ${severity}
2007
-
2008
- Return ONLY valid JSON:
2009
- {
2010
- "name": "rule-name-kebab-case",
2011
- "description": "One-line description in English",
2012
- "description_cn": "一行中文描述",
2013
- "pattern": "regex pattern for matching the problematic code",
2014
- "languages": ["${language}"],
2015
- "severity": "${severity}",
2016
- "testCases": {
2017
- "shouldMatch": ["code example that should trigger"],
2018
- "shouldNotMatch": ["code example that should NOT trigger"]
2019
- }
2020
- }`;
2021
-
2022
- const rule = await ctx.aiProvider.chatWithStructuredOutput(prompt, { temperature: 0.2 });
2023
- if (!rule) {
2024
- return { error: 'Failed to parse AI response' };
2025
- }
2026
-
2027
- // 验证正则表达式
2028
- try {
2029
- new RegExp(rule.pattern);
2030
- } catch (e) {
2031
- return { error: `Invalid regex pattern: ${e.message}`, rule };
2032
- }
2033
-
2034
- // 自动创建
2035
- if (autoCreate && rule.name && rule.pattern) {
2036
- try {
2037
- const guardService = ctx.container.get('guardService');
2038
- const created = await guardService.createRule(
2039
- {
2040
- name: rule.name,
2041
- description: rule.description || description,
2042
- pattern: rule.pattern,
2043
- languages: rule.languages || [language],
2044
- severity: rule.severity || severity,
2045
- },
2046
- { userId: 'agent' }
2047
- );
2048
- return { rule, created: true, recipeId: created.id };
2049
- } catch (err) {
2050
- return { rule, created: false, error: err.message };
2051
- }
2052
- }
2053
-
2054
- return { rule, created: false };
2055
- },
2056
- };
2057
-
2058
- // ────────────────────────────────────────────────────────────
2059
- // ────────────────────────────────────────────────────────────
2060
- // Bootstrap 维度展示分组 — 将 9 个细粒度维度合并为 4 个展示组
2061
- // ────────────────────────────────────────────────────────────
2062
-
2063
- const DIMENSION_DISPLAY_GROUP = {
2064
- architecture: 'architecture', // → 架构与设计
2065
- 'code-pattern': 'architecture', // → 架构与设计
2066
- 'project-profile': 'architecture', // → 架构与设计
2067
- 'best-practice': 'best-practice', // → 规范与实践
2068
- 'code-standard': 'best-practice', // → 规范与实践
2069
- 'event-and-data-flow': 'event-and-data-flow', // → 事件与数据流
2070
- 'objc-deep-scan': 'objc-deep-scan', // → 深度扫描
2071
- 'category-scan': 'objc-deep-scan', // → 深度扫描
2072
- 'agent-guidelines': 'agent-guidelines', // skill-only
2073
- };
2074
-
2075
- // ────────────────────────────────────────────────────────────
2076
- // Bootstrap 维度类型校验 — submit_knowledge / submit_with_check 共用
2077
- // 基于 dimensionMeta 类型标注系统,而非关键词模糊匹配
2078
- // ────────────────────────────────────────────────────────────
2079
-
2080
- /**
2081
- * 基于维度元数据 (dimensionMeta) 检查提交是否合法
2082
- * @param {{ id: string, outputType: 'candidate'|'skill'|'dual', allowedKnowledgeTypes: string[] }} dimensionMeta
2083
- * @param {object} params - submit_knowledge 的参数
2084
- * @param {object} [logger]
2085
- * @returns {{ status: string, reason: string } | null} 不合法返回 rejected,合法返回 null
2086
- */
2087
- function _checkDimensionType(dimensionMeta, params, logger) {
2088
- // 1. Skill-only 维度不允许提交 Candidate
2089
- if (dimensionMeta.outputType === 'skill') {
2090
- logger?.info(
2091
- `[submit_knowledge] ✗ rejected — dimension "${dimensionMeta.id}" is skill-only, cannot submit candidates`
2092
- );
2093
- return {
2094
- status: 'rejected',
2095
- reason: `当前维度 "${dimensionMeta.id}" 的输出类型为 skill-only,不允许调用 submit_knowledge。请只在最终回复中提供 dimensionDigest JSON。`,
2096
- };
2097
- }
2098
-
2099
- // 2. knowledgeType 校验 — 不在允许列表时自动修正为第一个允许类型
2100
- const allowed = dimensionMeta.allowedKnowledgeTypes || [];
2101
- if (allowed.length > 0 && params.knowledgeType) {
2102
- if (!allowed.includes(params.knowledgeType)) {
2103
- const corrected = allowed[0];
2104
- logger?.warn(
2105
- `[submit_knowledge] knowledgeType "${params.knowledgeType}" → "${corrected}" (auto-corrected for dimension "${dimensionMeta.id}")`
2106
- );
2107
- params.knowledgeType = corrected;
2108
- }
2109
- }
2110
-
2111
- return null;
2112
- }
2113
-
2114
- // 16. submit_knowledge
2115
- // ────────────────────────────────────────────────────────────
2116
- const submitCandidate = {
2117
- name: 'submit_knowledge',
2118
- description: '提交新的代码候选项到知识库审核队列。',
2119
- parameters: {
2120
- type: 'object',
2121
- properties: {
2122
- // ── 内容(V3 content 子对象) ──
2123
- content: {
2124
- type: 'object',
2125
- description: '{ markdown: "项目特写 Markdown(≥200字)", pattern: "核心代码 3-8 行", rationale: "设计原理" }',
2126
- },
2127
-
2128
- // ── 基本信息 ──
2129
- title: { type: 'string', description: '候选标题(中文 ≤20 字)' },
2130
- description: { type: 'string', description: '中文简述 ≤80 字,引用真实类名' },
2131
- tags: { type: 'array', items: { type: 'string' }, description: '标签列表' },
2132
-
2133
- // ── Cursor 交付(AI 必填)──
2134
- trigger: { type: 'string', description: '@前缀 kebab-case 唯一标识符' },
2135
- kind: { type: 'string', enum: ['rule', 'pattern', 'fact'], description: '知识类型' },
2136
- topicHint: {
2137
- type: 'string',
2138
- enum: ['networking', 'ui', 'data', 'architecture', 'conventions'],
2139
- description: '主题分类',
2140
- },
2141
- whenClause: { type: 'string', description: '触发场景英文' },
2142
- doClause: { type: 'string', description: '正向指令英文祈使句 ≤60 tokens' },
2143
- dontClause: { type: 'string', description: "反向约束英文(不以 Don't 开头)" },
2144
-
2145
- // ── 推理(必填) ──
2146
- reasoning: {
2147
- type: 'object',
2148
- description: '{ whyStandard: string, sources: string[], confidence: number } — 全部必填',
2149
- },
2150
-
2151
- // ── V3 扩展字段 ──
2152
- scope: {
2153
- type: 'string',
2154
- enum: ['universal', 'project-specific', 'team-convention'],
2155
- description: '适用范围',
2156
- },
2157
- complexity: {
2158
- type: 'string',
2159
- enum: ['basic', 'intermediate', 'advanced'],
2160
- description: '复杂度',
2161
- },
2162
- headers: {
2163
- type: 'array',
2164
- items: { type: 'string' },
2165
- description: '依赖的 import/require 行(无 import 时传 [])',
2166
- },
2167
- knowledgeType: { type: 'string', description: '知识维度:code-pattern / architecture / best-practice 等' },
2168
- usageGuide: { type: 'string', description: '使用指南 Markdown(### 章节格式)' },
2169
- sourceFile: { type: 'string', description: '来源文件相对路径' },
2170
- },
2171
- required: ['content', 'title', 'trigger', 'kind', 'doClause', 'description', 'headers', 'reasoning'],
2172
- },
2173
- handler: async (params, ctx) => {
2174
- const knowledgeService = ctx.container.get('knowledgeService');
2175
-
2176
- // ── Bootstrap 维度类型校验 ──
2177
- const dimMeta = ctx._dimensionMeta;
2178
- if (dimMeta && ctx.source === 'system') {
2179
- const rejected = _checkDimensionType(dimMeta, params, ctx.logger);
2180
- if (rejected) {
2181
- return rejected;
2182
- }
2183
-
2184
- // 自动注入维度标签
2185
- if (!params.tags) {
2186
- params.tags = [];
2187
- }
2188
- if (!params.tags.includes(dimMeta.id)) {
2189
- params.tags.push(dimMeta.id);
2190
- }
2191
- if (!params.tags.includes('bootstrap')) {
2192
- params.tags.push('bootstrap');
2193
- }
2194
-
2195
- // Bootstrap 模式: 将 category 覆盖为展示分组 ID
2196
- params._category = DIMENSION_DISPLAY_GROUP[dimMeta.id] || dimMeta.id;
2197
-
2198
- // ── CandidateGuardrail 质量验证 ──
2199
- const guardrail = new CandidateGuardrail(ctx._submittedTitles || new Set(), dimMeta, ctx._submittedPatterns || new Set());
2200
- const guardResult = guardrail.validate(params);
2201
- if (!guardResult.valid) {
2202
- ctx.logger?.info(`[submit_knowledge] ✗ guardrail rejected: ${guardResult.error}`);
2203
- return {
2204
- status: 'rejected',
2205
- error: guardResult.error,
2206
- hint: '请根据错误信息调整内容后重新提交。',
2207
- };
2208
- }
2209
- }
2210
-
2211
- // ── 系统自动设置 ──
2212
- const systemFields = {
2213
- language: ctx._projectLanguage || '',
2214
- category: dimMeta ? DIMENSION_DISPLAY_GROUP[dimMeta.id] || dimMeta.id : 'general',
2215
- knowledgeType: dimMeta?.allowedKnowledgeTypes?.[0] || 'code-pattern',
2216
- source: ctx.source === 'system' ? 'bootstrap' : 'agent',
2217
- };
2218
-
2219
- // ── 直传 → KnowledgeEntry ──
2220
- const reasoning = params.reasoning || { whyStandard: '', sources: ['agent'], confidence: 0.7 };
2221
- if (Array.isArray(reasoning.sources) && reasoning.sources.length === 0) {
2222
- reasoning.sources = ['agent'];
2223
- }
2224
-
2225
- // V3 content 直透
2226
- const contentObj =
2227
- params.content && typeof params.content === 'object'
2228
- ? params.content
2229
- : { markdown: '', pattern: '' };
2230
-
2231
- const data = {
2232
- ...systemFields,
2233
- title: params.title || '',
2234
- description: params.description || '',
2235
- tags: params.tags || [],
2236
- trigger: params.trigger || '',
2237
- kind: params.kind || 'pattern',
2238
- topicHint: params.topicHint || '',
2239
- whenClause: params.whenClause || '',
2240
- doClause: params.doClause || '',
2241
- dontClause: params.dontClause || '',
2242
- coreCode: contentObj.pattern || '',
2243
- content: contentObj,
2244
- reasoning,
2245
- // V3 扩展字段直透
2246
- scope: params.scope || '',
2247
- complexity: params.complexity || '',
2248
- headers: params.headers || [],
2249
- // sourceFile: 优先取 params,Bootstrap 回退从 reasoning.sources 推断
2250
- sourceFile:
2251
- params.sourceFile ||
2252
- (Array.isArray(reasoning.sources) &&
2253
- reasoning.sources.length > 0 &&
2254
- reasoning.sources[0] !== 'agent'
2255
- ? reasoning.sources[0]
2256
- : ''),
2257
- // 7.3.9 agentNotes/aiInsight 注入
2258
- agentNotes: dimMeta
2259
- ? { dimensionId: dimMeta.id, outputType: dimMeta.outputType || 'candidate' }
2260
- : null,
2261
- aiInsight: reasoning.whyStandard || params.description || null,
2262
- };
2263
-
2264
- if (dimMeta && ctx.source === 'system') {
2265
- const displayGroup = DIMENSION_DISPLAY_GROUP[dimMeta.id] || dimMeta.id;
2266
- data.tags = [...new Set([...(data.tags || []), displayGroup])];
2267
- }
2268
-
2269
- const saved = await knowledgeService.create(data, { userId: 'agent' });
2270
-
2271
- // ── QualityScorer 自动评分 ──
2272
- try {
2273
- await knowledgeService.updateQuality(saved.id, { userId: 'agent' });
2274
- } catch {
2275
- /* best effort — 不阻塞创建流程 */
2276
- }
2277
-
2278
- return saved;
2279
- },
2280
- };
2281
-
2282
- // ────────────────────────────────────────────────────────────
2283
- // 16b. save_document — 保存开发文档到知识库
2284
- // ────────────────────────────────────────────────────────────
2285
- const saveDocument = {
2286
- name: 'save_document',
2287
- description:
2288
- '保存开发文档到知识库(架构设计、排查报告、决策记录、调研笔记等)。仅需 title + markdown,无需 Cursor Delivery 字段。文档自动发布,可通过 autosnippet_search 检索。',
2289
- parameters: {
2290
- type: 'object',
2291
- properties: {
2292
- title: { type: 'string', description: '文档标题' },
2293
- markdown: { type: 'string', description: '文档 Markdown 全文' },
2294
- description: { type: 'string', description: '一句话摘要(可选)' },
2295
- tags: {
2296
- type: 'array',
2297
- items: { type: 'string' },
2298
- description: '标签: adr, debug-report, design-doc, research, performance 等',
2299
- },
2300
- scope: {
2301
- type: 'string',
2302
- enum: ['universal', 'project-specific'],
2303
- description: '适用范围(默认 project-specific)',
2304
- },
2305
- },
2306
- required: ['title', 'markdown'],
2307
- },
2308
- handler: async (params, ctx) => {
2309
- const knowledgeService = ctx.container.get('knowledgeService');
2310
-
2311
- const data = {
2312
- title: params.title.trim(),
2313
- description: params.description || '',
2314
- knowledgeType: 'dev-document',
2315
- kind: 'fact',
2316
- source: 'agent',
2317
- scope: params.scope || 'project-specific',
2318
- tags: params.tags || [],
2319
- content: {
2320
- markdown: params.markdown,
2321
- pattern: '',
2322
- },
2323
- trigger: '',
2324
- doClause: '',
2325
- dontClause: '',
2326
- whenClause: '',
2327
- topicHint: '',
2328
- coreCode: '',
2329
- reasoning: {
2330
- whyStandard: 'Agent development document',
2331
- sources: ['agent'],
2332
- confidence: 0.8,
2333
- },
2334
- };
2335
-
2336
- const saved = await knowledgeService.create(data, { userId: 'agent' });
2337
-
2338
- // 自动发布(文档不需要人工审核)
2339
- try {
2340
- await knowledgeService.publish(saved.id, { userId: 'agent' });
2341
- } catch {
2342
- /* best effort */
2343
- }
2344
-
2345
- return {
2346
- id: saved.id,
2347
- title: saved.title,
2348
- lifecycle: 'active',
2349
- knowledgeType: 'dev-document',
2350
- message: `文档「${saved.title}」已保存到知识库`,
2351
- };
2352
- },
2353
- };
2354
-
2355
- // ────────────────────────────────────────────────────────────
2356
- // 17. approve_candidate
2357
- // ────────────────────────────────────────────────────────────
2358
- const approveCandidate = {
2359
- name: 'approve_candidate',
2360
- description: '批准候选项(PENDING → APPROVED)。',
2361
- parameters: {
2362
- type: 'object',
2363
- properties: {
2364
- candidateId: { type: 'string', description: '候选 ID' },
2365
- },
2366
- required: ['candidateId'],
2367
- },
2368
- handler: async (params, ctx) => {
2369
- const knowledgeService = ctx.container.get('knowledgeService');
2370
- return knowledgeService.approve(params.candidateId, { userId: 'agent' });
2371
- },
2372
- };
2373
-
2374
- // ────────────────────────────────────────────────────────────
2375
- // 18. reject_candidate
2376
- // ────────────────────────────────────────────────────────────
2377
- const rejectCandidate = {
2378
- name: 'reject_candidate',
2379
- description: '驳回候选项并填写驳回理由。',
2380
- parameters: {
2381
- type: 'object',
2382
- properties: {
2383
- candidateId: { type: 'string', description: '候选 ID' },
2384
- reason: { type: 'string', description: '驳回理由' },
2385
- },
2386
- required: ['candidateId', 'reason'],
2387
- },
2388
- handler: async (params, ctx) => {
2389
- const knowledgeService = ctx.container.get('knowledgeService');
2390
- return knowledgeService.reject(params.candidateId, params.reason, { userId: 'agent' });
2391
- },
2392
- };
2393
-
2394
- // ────────────────────────────────────────────────────────────
2395
- // 19. publish_recipe
2396
- // ────────────────────────────────────────────────────────────
2397
- const publishRecipe = {
2398
- name: 'publish_recipe',
2399
- description: '发布 Recipe(DRAFT → ACTIVE)。',
2400
- parameters: {
2401
- type: 'object',
2402
- properties: {
2403
- recipeId: { type: 'string', description: 'Recipe ID' },
2404
- },
2405
- required: ['recipeId'],
2406
- },
2407
- handler: async (params, ctx) => {
2408
- const knowledgeService = ctx.container.get('knowledgeService');
2409
- return knowledgeService.publish(params.recipeId, { userId: 'agent' });
2410
- },
2411
- };
2412
-
2413
- // ────────────────────────────────────────────────────────────
2414
- // 20. deprecate_recipe
2415
- // ────────────────────────────────────────────────────────────
2416
- const deprecateRecipe = {
2417
- name: 'deprecate_recipe',
2418
- description: '弃用 Recipe 并填写弃用原因。',
2419
- parameters: {
2420
- type: 'object',
2421
- properties: {
2422
- recipeId: { type: 'string', description: 'Recipe ID' },
2423
- reason: { type: 'string', description: '弃用原因' },
2424
- },
2425
- required: ['recipeId', 'reason'],
2426
- },
2427
- handler: async (params, ctx) => {
2428
- const knowledgeService = ctx.container.get('knowledgeService');
2429
- return knowledgeService.deprecate(params.recipeId, params.reason, { userId: 'agent' });
2430
- },
2431
- };
2432
-
2433
- // ────────────────────────────────────────────────────────────
2434
- // 21. update_recipe
2435
- // ────────────────────────────────────────────────────────────
2436
- const updateRecipe = {
2437
- name: 'update_recipe',
2438
- description: '更新 Recipe 的指定字段(title/description/content/category/tags 等)。',
2439
- parameters: {
2440
- type: 'object',
2441
- properties: {
2442
- recipeId: { type: 'string', description: 'Recipe ID' },
2443
- updates: { type: 'object', description: '要更新的字段和值' },
2444
- },
2445
- required: ['recipeId', 'updates'],
2446
- },
2447
- handler: async (params, ctx) => {
2448
- const knowledgeService = ctx.container.get('knowledgeService');
2449
- return knowledgeService.update(params.recipeId, params.updates, { userId: 'agent' });
2450
- },
2451
- };
2452
-
2453
- // ────────────────────────────────────────────────────────────
2454
- // 22. record_usage
2455
- // ────────────────────────────────────────────────────────────
2456
- const recordUsage = {
2457
- name: 'record_usage',
2458
- description: '记录 Recipe 的使用(adoption 被采纳 / application 被应用)。',
2459
- parameters: {
2460
- type: 'object',
2461
- properties: {
2462
- recipeId: { type: 'string', description: 'Recipe ID' },
2463
- type: { type: 'string', description: 'adoption 或 application,默认 adoption' },
2464
- },
2465
- required: ['recipeId'],
2466
- },
2467
- handler: async (params, ctx) => {
2468
- const knowledgeService = ctx.container.get('knowledgeService');
2469
- const type = params.type || 'adoption';
2470
- await knowledgeService.incrementUsage(params.recipeId, type);
2471
- return { success: true, recipeId: params.recipeId, type };
2472
- },
2473
- };
2474
-
2475
- // ────────────────────────────────────────────────────────────
2476
- // 23. quality_score
2477
- // ────────────────────────────────────────────────────────────
2478
- const qualityScore = {
2479
- name: 'quality_score',
2480
- description:
2481
- 'Recipe 质量评分 — 5 维度综合评估(完整性/格式/代码质量/元数据/互动),返回分数和等级(A-F)。',
2482
- parameters: {
2483
- type: 'object',
2484
- properties: {
2485
- recipeId: { type: 'string', description: 'Recipe ID(从数据库读取后评分)' },
2486
- recipe: {
2487
- type: 'object',
2488
- description: '或直接提供 Recipe 对象 { title, trigger, code, language, ... }',
2489
- },
2490
- },
2491
- },
2492
- handler: async (params, ctx) => {
2493
- const qualityScorer = ctx.container.get('qualityScorer');
2494
- let recipe = params.recipe;
2495
-
2496
- if (!recipe && params.recipeId) {
2497
- const knowledgeService = ctx.container.get('knowledgeService');
2498
- try {
2499
- const entry = await knowledgeService.get(params.recipeId);
2500
- recipe = typeof entry.toJSON === 'function' ? entry.toJSON() : entry;
2501
- } catch {
2502
- return { error: `Knowledge entry '${params.recipeId}' not found` };
2503
- }
2504
- }
2505
- if (!recipe) {
2506
- return { error: 'Provide recipeId or recipe object' };
2507
- }
2508
-
2509
- return qualityScorer.score(recipe);
2510
- },
2511
- };
2512
-
2513
- // ────────────────────────────────────────────────────────────
2514
- // 24. validate_candidate
2515
- // ────────────────────────────────────────────────────────────
2516
- const validateCandidate = {
2517
- name: 'validate_candidate',
2518
- description:
2519
- '候选校验 — 检查候选是否满足提交要求(必填字段/格式/质量),返回 errors 和 warnings。',
2520
- parameters: {
2521
- type: 'object',
2522
- properties: {
2523
- candidate: {
2524
- type: 'object',
2525
- description: '候选对象 { title, trigger, category, language, code, reasoning, ... }',
2526
- },
2527
- },
2528
- required: ['candidate'],
2529
- },
2530
- handler: async (params, ctx) => {
2531
- const validator = ctx.container.get('recipeCandidateValidator');
2532
- return validator.validate(params.candidate);
2533
- },
2534
- };
2535
-
2536
- // ────────────────────────────────────────────────────────────
2537
- // 25. get_feedback_stats
2538
- // ────────────────────────────────────────────────────────────
2539
- const getFeedbackStats = {
2540
- name: 'get_feedback_stats',
2541
- description: '获取用户反馈统计 — 全局交互事件统计 + 热门 Recipe + 指定 Recipe 的详细反馈。',
2542
- parameters: {
2543
- type: 'object',
2544
- properties: {
2545
- recipeId: { type: 'string', description: '查询指定 Recipe 的反馈(可选)' },
2546
- topN: { type: 'number', description: '热门 Recipe 数量,默认 10' },
2547
- },
2548
- },
2549
- handler: async (params, ctx) => {
2550
- const feedbackCollector = ctx.container.get('feedbackCollector');
2551
- const result = {};
2552
-
2553
- result.global = feedbackCollector.getGlobalStats();
2554
- result.topRecipes = feedbackCollector.getTopRecipes(params.topN || 10);
2555
-
2556
- if (params.recipeId) {
2557
- result.recipeStats = feedbackCollector.getRecipeStats(params.recipeId);
2558
- }
2559
-
2560
- return result;
2561
- },
2562
- };
2563
-
2564
- // ────────────────────────────────────────────────────────────
2565
- // 29. graph_impact_analysis
2566
- // ────────────────────────────────────────────────────────────
2567
- const graphImpactAnalysis = {
2568
- name: 'graph_impact_analysis',
2569
- description: '知识图谱影响范围分析 — 查找修改某个 Recipe 后可能受影响的所有下游依赖。',
2570
- parameters: {
2571
- type: 'object',
2572
- properties: {
2573
- recipeId: { type: 'string', description: 'Recipe ID' },
2574
- maxDepth: { type: 'number', description: '最大深度,默认 3' },
2575
- },
2576
- required: ['recipeId'],
2577
- },
2578
- handler: async (params, ctx) => {
2579
- const kgService = ctx.container.get('knowledgeGraphService');
2580
- const impacted = kgService.getImpactAnalysis(params.recipeId, 'recipe', params.maxDepth || 3);
2581
- return { recipeId: params.recipeId, impactedCount: impacted.length, impacted };
2582
- },
2583
- };
2584
-
2585
- // ────────────────────────────────────────────────────────────
2586
- // 30. rebuild_index
2587
- // ────────────────────────────────────────────────────────────
2588
- const rebuildIndex = {
2589
- name: 'rebuild_index',
2590
- description:
2591
- '向量索引重建 — 重新扫描 Recipe 文件并更新向量索引(用于索引过期或新增大量 Recipe 后)。',
2592
- parameters: {
2593
- type: 'object',
2594
- properties: {
2595
- force: { type: 'boolean', description: '强制重建(跳过增量检测),默认 false' },
2596
- dryRun: { type: 'boolean', description: '仅预览不实际写入,默认 false' },
2597
- },
2598
- },
2599
- handler: async (params, ctx) => {
2600
- const pipeline = ctx.container.get('indexingPipeline');
2601
- return pipeline.run({ force: params.force || false, dryRun: params.dryRun || false });
2602
- },
2603
- };
2604
-
2605
- // ────────────────────────────────────────────────────────────
2606
- // 31. query_audit_log
2607
- // ────────────────────────────────────────────────────────────
2608
- const queryAuditLog = {
2609
- name: 'query_audit_log',
2610
- description: '审计日志查询 — 查看系统操作历史(谁在什么时间做了什么操作)。',
2611
- parameters: {
2612
- type: 'object',
2613
- properties: {
2614
- action: {
2615
- type: 'string',
2616
- description: '按操作类型过滤 (create_candidate/approve_candidate/create_guard_rule 等)',
2617
- },
2618
- actor: { type: 'string', description: '按操作者过滤' },
2619
- limit: { type: 'number', description: '返回数量,默认 20' },
2620
- },
2621
- },
2622
- handler: async (params, ctx) => {
2623
- const auditLogger = ctx.container.get('auditLogger');
2624
- const { action, actor, limit = 20 } = params;
2625
-
2626
- if (actor) {
2627
- return auditLogger.getByActor(actor, limit);
2628
- }
2629
- if (action) {
2630
- return auditLogger.getByAction(action, limit);
2631
- }
2632
- return auditLogger.getStats();
2633
- },
2634
- };
2635
-
2636
- // ────────────────────────────────────────────────────────────
2637
- // 32. load_skill — 按需加载 Agent Skill 文档
2638
- // ────────────────────────────────────────────────────────────
2639
- const loadSkill = {
2640
- name: 'load_skill',
2641
- description:
2642
- '加载指定的 Agent Skill 文档,获取领域操作指南和最佳实践参考。可用于冷启动指南 (autosnippet-coldstart)、语言参考 (autosnippet-reference-swift/objc/jsts/python/java/kotlin/go/dart/rust) 等。',
2643
- parameters: {
2644
- type: 'object',
2645
- properties: {
2646
- skillName: {
2647
- type: 'string',
2648
- description: 'Skill 目录名(如 autosnippet-coldstart, autosnippet-reference-swift, autosnippet-reference-go 等)',
2649
- },
2650
- },
2651
- required: ['skillName'],
2652
- },
2653
- handler: async (params) => {
2654
- // 项目级 Skills 优先(覆盖同名内置 Skill)
2655
- const projectSkillPath = path.join(PROJECT_SKILLS_DIR, params.skillName, 'SKILL.md');
2656
- const builtinSkillPath = path.join(SKILLS_DIR, params.skillName, 'SKILL.md');
2657
- const skillPath = fs.existsSync(projectSkillPath) ? projectSkillPath : builtinSkillPath;
2658
- try {
2659
- const content = fs.readFileSync(skillPath, 'utf8');
2660
- const source = skillPath === projectSkillPath ? 'project' : 'builtin';
2661
- return { skillName: params.skillName, source, content };
2662
- } catch {
2663
- const available = new Set();
2664
- try {
2665
- fs.readdirSync(SKILLS_DIR, { withFileTypes: true })
2666
- .filter((d) => d.isDirectory())
2667
- .forEach((d) => available.add(d.name));
2668
- } catch {}
2669
- try {
2670
- fs.readdirSync(PROJECT_SKILLS_DIR, { withFileTypes: true })
2671
- .filter((d) => d.isDirectory())
2672
- .forEach((d) => available.add(d.name));
2673
- } catch {}
2674
- return { error: `Skill "${params.skillName}" not found`, availableSkills: [...available] };
2675
- }
2676
- },
2677
- };
2678
-
2679
- // ────────────────────────────────────────────────────────────
2680
- // 33. create_skill — 创建项目级 Skill
2681
- // ────────────────────────────────────────────────────────────
2682
- const createSkillTool = {
2683
- name: 'create_skill',
2684
- description:
2685
- '创建项目级 Skill 文档,写入 AutoSnippet/skills/<name>/SKILL.md。Skill 是 Agent 的领域知识增强文档。创建后自动更新编辑器索引。',
2686
- parameters: {
2687
- type: 'object',
2688
- properties: {
2689
- name: {
2690
- type: 'string',
2691
- description: 'Skill 名称(kebab-case,如 my-auth-guide),3-64 字符',
2692
- },
2693
- description: { type: 'string', description: 'Skill 一句话描述(写入 frontmatter)' },
2694
- content: { type: 'string', description: 'Skill 正文内容(Markdown 格式,不含 frontmatter)' },
2695
- overwrite: { type: 'boolean', description: '如果同名 Skill 已存在,是否覆盖(默认 false)' },
2696
- },
2697
- required: ['name', 'description', 'content'],
2698
- },
2699
- handler: async (params, ctx) => {
2700
- const { createSkill } = await import('../../external/mcp/handlers/skill.js');
2701
- // 根据 ChatAgent 的 source 推断 createdBy
2702
- const createdBy = ctx?.source === 'system' ? 'system-ai' : 'user-ai';
2703
- const raw = createSkill(null, { ...params, createdBy });
2704
- try {
2705
- return JSON.parse(raw);
2706
- } catch {
2707
- return { success: false, error: raw };
2708
- }
2709
- },
2710
- };
2711
-
2712
- // ────────────────────────────────────────────────────────────
2713
- // 34. suggest_skills — 基于使用模式推荐 Skill 创建
2714
- // ────────────────────────────────────────────────────────────
2715
- const suggestSkills = {
2716
- name: 'suggest_skills',
2717
- description:
2718
- '基于项目使用模式分析,推荐创建 Skill。分析 Guard 违规频率、Memory 偏好积累、Recipe 分布缺口、候选积压率。返回推荐列表(含 name/description/rationale/priority),可据此直接调用 create_skill 创建。',
2719
- parameters: {
2720
- type: 'object',
2721
- properties: {},
2722
- required: [],
2723
- },
2724
- handler: async (_params, ctx) => {
2725
- const { SkillAdvisor } = await import('../../service/skills/SkillAdvisor.js');
2726
- const database = ctx?.container?.get?.('database') || null;
2727
- const projectRoot = ctx?.projectRoot || process.cwd();
2728
- const advisor = new SkillAdvisor(projectRoot, { database });
2729
- return advisor.suggest();
2730
- },
2731
- };
2732
-
2733
- // ────────────────────────────────────────────────────────────
2734
- // 34. bootstrap_knowledge — 冷启动知识库初始化
2735
- // ────────────────────────────────────────────────────────────
2736
- const bootstrapKnowledgeTool = {
2737
- name: 'bootstrap_knowledge',
2738
- description:
2739
- '冷启动知识库初始化(纯启发式,不使用 AI): SPM Target 扫描 → 依赖图谱 → Guard 审计 → 9 维度 Candidate 自动创建。支持 Skill 增强维度定义。产出为初稿候选,后续由 DAG pipeline 自动编排 AI 增强(enrich → refine)。',
2740
- parameters: {
2741
- type: 'object',
2742
- properties: {
2743
- maxFiles: { type: 'number', description: '最大扫描文件数,默认 500' },
2744
- skipGuard: { type: 'boolean', description: '是否跳过 Guard 审计,默认 false' },
2745
- contentMaxLines: { type: 'number', description: '每文件读取最大行数,默认 120' },
2746
- loadSkills: {
2747
- type: 'boolean',
2748
- description: '是否加载 Skills 增强维度定义(推荐开启),默认 true',
2749
- },
2750
- },
2751
- },
2752
- handler: async (params, ctx) => {
2753
- const { bootstrapKnowledge } = await import('../../external/mcp/handlers/bootstrap.js');
2754
- const logger = Logger.getInstance();
2755
- const result = await bootstrapKnowledge(
2756
- { container: ctx.container, logger },
2757
- {
2758
- maxFiles: params.maxFiles || 500,
2759
- skipGuard: params.skipGuard || false,
2760
- contentMaxLines: params.contentMaxLines || 120,
2761
- loadSkills: params.loadSkills ?? true,
2762
- }
2763
- );
2764
- // bootstrapKnowledge 返回 envelope JSON string,解析提取 data
2765
- const parsed = typeof result === 'string' ? JSON.parse(result) : result;
2766
- return parsed?.data || parsed;
2767
- },
2768
- };
2769
-
2770
- // ────────────────────────────────────────────────────────────
2771
- // 导出全部工具
2772
- // ────────────────────────────────────────────────────────────
2773
-
2774
- // ────────────────────────────────────────────────────────────
2775
- // 34. analyze_code — 组合工具 (Guard + Recipe 搜索)
2776
- // ────────────────────────────────────────────────────────────
2777
- const analyzeCode = {
2778
- name: 'analyze_code',
2779
- description:
2780
- '综合分析一段代码:Guard 规范检查 + 相关 Recipe 搜索。一次调用完成完整分析,减少多轮工具调用。',
2781
- parameters: {
2782
- type: 'object',
2783
- properties: {
2784
- code: { type: 'string', description: '待分析的源码' },
2785
- language: { type: 'string', description: '编程语言 (swift/objc/javascript 等)' },
2786
- filePath: { type: 'string', description: '文件路径(可选,用于上下文)' },
2787
- },
2788
- required: ['code'],
2789
- },
2790
- handler: async (params, ctx) => {
2791
- const { code, language, filePath } = params;
2792
- const results = {};
2793
-
2794
- // 并行执行 Guard 检查 + Recipe 搜索
2795
- const [guardResult, searchResult] = await Promise.all([
2796
- (async () => {
2797
- try {
2798
- const engine = ctx.container.get('guardCheckEngine');
2799
- const violations = engine.checkCode(code, language || 'unknown', { scope: 'file' });
2800
- return { violationCount: violations.length, violations };
2801
- } catch {
2802
- try {
2803
- const guardService = ctx.container.get('guardService');
2804
- const matches = await guardService.checkCode(code, { language });
2805
- return { violationCount: matches.length, violations: matches };
2806
- } catch {
2807
- return { violationCount: 0, violations: [] };
2808
- }
2809
- }
2810
- })(),
2811
- (async () => {
2812
- try {
2813
- const searchEngine = ctx.container.get('searchEngine');
2814
- // 取代码首段作为搜索词
2815
- const query = code.substring(0, 200).replace(/\n/g, ' ');
2816
- const rawResults = await searchEngine.search(query, { limit: 5 });
2817
- return { results: rawResults || [], total: rawResults?.length || 0 };
2818
- } catch {
2819
- return { results: [], total: 0 };
2820
- }
2821
- })(),
2822
- ]);
2823
-
2824
- results.guard = guardResult;
2825
- results.relatedRecipes = searchResult;
2826
- results.filePath = filePath || '(inline)';
2827
-
2828
- const hasFindings = guardResult.violationCount > 0 || searchResult.total > 0;
2829
- results._meta = {
2830
- confidence: hasFindings ? 'high' : 'low',
2831
- hint: hasFindings
2832
- ? `已完成 Guard 检查(${guardResult.violationCount} 个违规)+ Recipe 搜索(${searchResult.total} 条匹配)。`
2833
- : '未发现 Guard 违规,也未找到相关 Recipe。可能需要先冷启动知识库。',
2834
- };
2835
-
2836
- return results;
2837
- },
2838
- };
2839
-
2840
- // ────────────────────────────────────────────────────────────
2841
- // 35. knowledge_overview — 组合工具 (一次获取全部类型的 Recipe 统计)
2842
- // ────────────────────────────────────────────────────────────
2843
- const knowledgeOverview = {
2844
- name: 'knowledge_overview',
2845
- description:
2846
- '一次性获取知识库全貌:各类型 Recipe 分布 + 候选状态 + 知识图谱概况 + 质量概览。比分别调用 get_project_stats + search_recipes 更高效。',
2847
- parameters: {
2848
- type: 'object',
2849
- properties: {
2850
- includeTopRecipes: { type: 'boolean', description: '是否包含热门 Recipe 列表,默认 true' },
2851
- limit: { type: 'number', description: '每类返回数量,默认 5' },
2852
- },
2853
- },
2854
- handler: async (params, ctx) => {
2855
- const { includeTopRecipes = true, limit = 5 } = params;
2856
- const result = {};
2857
-
2858
- // 并行获取统计 + 可选的热门列表
2859
- const [statsResult, feedbackResult] = await Promise.all([
2860
- (async () => {
2861
- try {
2862
- const knowledgeService = ctx.container.get('knowledgeService');
2863
- return knowledgeService.getStats();
2864
- } catch {
2865
- return null;
2866
- }
2867
- })(),
2868
- (async () => {
2869
- if (!includeTopRecipes) {
2870
- return null;
2871
- }
2872
- try {
2873
- const feedbackCollector = ctx.container.get('feedbackCollector');
2874
- return feedbackCollector.getTopRecipes(limit);
2875
- } catch {
2876
- return null;
2877
- }
2878
- })(),
2879
- ]);
2880
-
2881
- if (statsResult) {
2882
- result.knowledge = statsResult;
2883
- }
2884
-
2885
- // 知识图谱统计
2886
- try {
2887
- const kgService = ctx.container.get('knowledgeGraphService');
2888
- result.knowledgeGraph = kgService.getStats();
2889
- } catch {
2890
- /* KG not available */
2891
- }
2892
-
2893
- if (feedbackResult) {
2894
- result.topRecipes = feedbackResult;
2895
- }
2896
-
2897
- const recipeCount = result.recipes?.total || result.recipes?.count || 0;
2898
- result._meta = {
2899
- confidence: recipeCount > 0 ? 'high' : 'none',
2900
- hint: recipeCount === 0 ? '知识库为空,建议先执行冷启动(bootstrap_knowledge)。' : null,
2901
- };
2902
-
2903
- return result;
2904
- },
2905
- };
2906
-
2907
- // ────────────────────────────────────────────────────────────
2908
- // 36. submit_with_check — 组合工具 (查重 + 提交)
2909
- // ────────────────────────────────────────────────────────────
2910
- const submitWithCheck = {
2911
- name: 'submit_with_check',
2912
- description:
2913
- '安全提交候选:先执行查重检测,无重复则自动提交。一次调用完成 check_duplicate + submit_knowledge。',
2914
- parameters: {
2915
- type: 'object',
2916
- properties: {
2917
- content: {
2918
- type: 'object',
2919
- description: '{ markdown: "项目特写 Markdown", pattern: "核心代码 3-8 行,必须语法完整(括号配对、不能以 } 开头或 { 结尾)" }',
2920
- },
2921
- title: { type: 'string', description: '候选标题' },
2922
- description: { type: 'string', description: '中文简述 ≤80 字' },
2923
- trigger: { type: 'string', description: '@前缀 kebab-case 唯一标识符' },
2924
- kind: { type: 'string', enum: ['rule', 'pattern', 'fact'] },
2925
- topicHint: {
2926
- type: 'string',
2927
- enum: ['networking', 'ui', 'data', 'architecture', 'conventions'],
2928
- },
2929
- whenClause: { type: 'string', description: '触发场景英文' },
2930
- doClause: { type: 'string', description: '正向指令英文' },
2931
- dontClause: { type: 'string', description: '反向约束英文' },
2932
- tags: { type: 'array', items: { type: 'string' } },
2933
- reasoning: { type: 'object', description: '{ whyStandard, sources, confidence }' },
2934
- threshold: { type: 'number', description: '相似度阈值,默认 0.7' },
2935
- },
2936
- required: ['content', 'title', 'trigger', 'kind', 'doClause'],
2937
- },
2938
- handler: async (params, ctx) => {
2939
- const projectRoot = ctx.projectRoot;
2940
-
2941
- // ── Bootstrap 维度类型校验 ──
2942
- const dimMeta = ctx._dimensionMeta;
2943
- if (dimMeta && ctx.source === 'system') {
2944
- const rejected = _checkDimensionType(dimMeta, params, ctx.logger);
2945
- if (rejected) {
2946
- return rejected;
2947
- }
2948
-
2949
- if (!params.tags) {
2950
- params.tags = [];
2951
- }
2952
- if (!params.tags.includes(dimMeta.id)) {
2953
- params.tags.push(dimMeta.id);
2954
- }
2955
- if (!params.tags.includes('bootstrap')) {
2956
- params.tags.push('bootstrap');
2957
- }
2958
- }
2959
-
2960
- // Step 1: 查重
2961
- const threshold = params.threshold || 0.7;
2962
- const contentObj2 =
2963
- params.content && typeof params.content === 'object'
2964
- ? params.content
2965
- : { markdown: '', pattern: '' };
2966
- const cand = {
2967
- title: params.title || '',
2968
- summary: params.description || '',
2969
- code: contentObj2.markdown || contentObj2.pattern || '',
2970
- };
2971
- const similar = findSimilarRecipes(projectRoot, cand, { threshold: 0.5, topK: 5 });
2972
- const hasDuplicate = similar.some((s) => s.similarity >= threshold);
2973
-
2974
- if (hasDuplicate) {
2975
- return {
2976
- submitted: false,
2977
- reason: 'duplicate_blocked',
2978
- similar,
2979
- highestSimilarity: similar[0]?.similarity || 0,
2980
- _meta: {
2981
- confidence: 'high',
2982
- hint: `发现高度相似 Recipe(相似度 ${(similar[0]?.similarity * 100).toFixed(0)}%),已阻止提交。`,
2983
- },
2984
- };
2985
- }
2986
-
2987
- // Step 2: 提交 — 委托给 submit_knowledge handler
2988
- try {
2989
- const knowledgeService = ctx.container.get('knowledgeService');
2990
- const reasoning = params.reasoning || {
2991
- whyStandard: '',
2992
- sources: ['agent'],
2993
- confidence: 0.7,
2994
- };
2995
-
2996
- const systemFields = {
2997
- language: ctx._projectLanguage || '',
2998
- category: dimMeta ? DIMENSION_DISPLAY_GROUP[dimMeta.id] || dimMeta.id : 'general',
2999
- knowledgeType: dimMeta?.allowedKnowledgeTypes?.[0] || 'code-pattern',
3000
- source: ctx.source === 'system' ? 'bootstrap' : 'agent',
3001
- };
3002
-
3003
- const data = {
3004
- ...systemFields,
3005
- title: params.title || '',
3006
- description: params.description || '',
3007
- tags: params.tags || [],
3008
- trigger: params.trigger || '',
3009
- kind: params.kind || 'pattern',
3010
- topicHint: params.topicHint || '',
3011
- whenClause: params.whenClause || '',
3012
- doClause: params.doClause || '',
3013
- dontClause: params.dontClause || '',
3014
- coreCode: contentObj2.pattern || '',
3015
- content: contentObj2,
3016
- reasoning,
3017
- };
3018
-
3019
- const created = await knowledgeService.create(data, { userId: 'agent' });
3020
-
3021
- return {
3022
- submitted: true,
3023
- entry: typeof created.toJSON === 'function' ? created.toJSON() : created,
3024
- similar: similar.length > 0 ? similar : [],
3025
- _meta: {
3026
- confidence: 'high',
3027
- hint:
3028
- similar.length > 0
3029
- ? `已提交,但有 ${similar.length} 个低相似度匹配。`
3030
- : '已提交,无重复风险。',
3031
- },
3032
- };
3033
- } catch (err) {
3034
- return { submitted: false, reason: 'submit_error', error: err.message };
3035
- }
3036
- },
3037
- };
3038
-
3039
- // ═══════════════════════════════════════════════════════
3040
- // 元工具: Lazy Tool Schema 按需加载
3041
- // ═══════════════════════════════════════════════════════
3042
-
3043
- /**
3044
- * get_tool_details — 查询工具的完整参数 schema
3045
- *
3046
- * 与 Cline .clinerules 按需加载类似:
3047
- * System Prompt 只包含工具名+一行描述,LLM 需要调用某个工具前
3048
- * 先通过此元工具获取完整参数定义,避免 prompt 过长浪费 token。
3049
- */
3050
- const getToolDetails = {
3051
- name: 'get_tool_details',
3052
- description: '查询指定工具的完整参数 Schema。在调用不熟悉的工具之前,先用此工具获取参数详情。',
3053
- parameters: {
3054
- type: 'object',
3055
- properties: {
3056
- toolName: {
3057
- type: 'string',
3058
- description: '要查询的工具名称(snake_case)',
3059
- },
3060
- },
3061
- required: ['toolName'],
3062
- },
3063
- handler: async ({ toolName }, context) => {
3064
- const registry = context.container?.get('toolRegistry');
3065
- if (!registry) {
3066
- return { error: 'ToolRegistry not available' };
3067
- }
3068
-
3069
- const schemas = registry.getToolSchemas();
3070
- const found = schemas.find((t) => t.name === toolName);
3071
- if (!found) {
3072
- const allNames = schemas.map((t) => t.name);
3073
- return {
3074
- error: `Tool "${toolName}" not found`,
3075
- availableTools: allNames,
3076
- };
3077
- }
3078
-
3079
- return {
3080
- name: found.name,
3081
- description: found.description,
3082
- parameters: found.parameters,
3083
- };
3084
- },
3085
- };
3086
-
3087
- // ─── 元工具: 任务规划 ───────────────────────────────────
3088
- const planTask = {
3089
- name: 'plan_task',
3090
- description:
3091
- '分析当前任务并制定结构化执行计划。在开始复杂任务前调用此工具可提高执行效率和决策质量。输出将记录到日志供审计,但不会改变实际执行流程。',
3092
- parameters: {
3093
- type: 'object',
3094
- properties: {
3095
- steps: {
3096
- type: 'array',
3097
- description: '执行步骤列表',
3098
- items: {
3099
- type: 'object',
3100
- properties: {
3101
- id: { type: 'number', description: '步骤序号' },
3102
- action: { type: 'string', description: '具体动作描述' },
3103
- tool: { type: 'string', description: '计划使用的工具名' },
3104
- depends_on: { type: 'array', items: { type: 'number' }, description: '依赖的步骤 ID' },
3105
- },
3106
- required: ['id', 'action'],
3107
- },
3108
- },
3109
- strategy: {
3110
- type: 'string',
3111
- description: '执行策略说明(如: 先搜索补充示例再批量提交)',
3112
- },
3113
- estimated_iterations: {
3114
- type: 'number',
3115
- description: '预估需要的迭代轮数',
3116
- },
3117
- },
3118
- required: ['steps', 'strategy'],
3119
- },
3120
- handler: async (params, context) => {
3121
- const plan = {
3122
- steps: params.steps || [],
3123
- strategy: params.strategy || '',
3124
- estimatedIterations: params.estimated_iterations || params.steps?.length || 1,
3125
- };
3126
- context.logger?.info('[plan_task] execution plan', plan);
3127
- return {
3128
- status: 'plan_recorded',
3129
- stepCount: plan.steps.length,
3130
- strategy: plan.strategy,
3131
- message: `执行计划已记录 (${plan.steps.length} 步, 预估 ${plan.estimatedIterations} 轮迭代)。开始按计划执行。`,
3132
- };
3133
- },
3134
- };
3135
-
3136
- // ─── 元工具: 自我质量审查 ───────────────────────────────
3137
- const reviewMyOutput = {
3138
- name: 'review_my_output',
3139
- description:
3140
- '回查本次会话中已提交的候选,检查质量红线是否满足。包括: 项目特写风格、description 泛化措辞、代码示例来源标注、Cursor 交付字段完整性等。返回通过/问题列表。建议在提交完所有候选后调用一次进行自检。',
3141
- parameters: {
3142
- type: 'object',
3143
- properties: {
3144
- check_rules: {
3145
- type: 'array',
3146
- description: '要检查的质量规则(可选, 默认检查全部)',
3147
- items: { type: 'string' },
3148
- },
3149
- },
3150
- },
3151
- handler: async (params, context) => {
3152
- const submitted = (context._sessionToolCalls || []).filter(
3153
- (tc) => tc.tool === 'submit_knowledge' || tc.tool === 'submit_with_check'
3154
- );
3155
-
3156
- if (submitted.length === 0) {
3157
- return { status: 'no_candidates', message: '本次会话尚未提交任何候选。' };
3158
- }
3159
-
3160
- const issues = [];
3161
- const checked = [];
3162
-
3163
- for (const tc of submitted) {
3164
- const p = tc.params || {};
3165
- const contentObj3 = p.content && typeof p.content === 'object' ? p.content : {};
3166
- const markdown = contentObj3.markdown || '';
3167
- const title = p.title || '';
3168
- const description = p.description || '';
3169
- const candidateIssues = [];
3170
-
3171
- // 检查 1: 项目特写后缀
3172
- if (!title.includes('— 项目特写') && !markdown.includes('— 项目特写')) {
3173
- candidateIssues.push('缺少 "— 项目特写" 后缀');
3174
- }
3175
-
3176
- // 检查 2: 项目特写融合叙事质量 — 必须同时包含代码和描述性文字
3177
- const hasCodeBlock = /```[\s\S]*?```/.test(markdown);
3178
- if (!hasCodeBlock) {
3179
- candidateIssues.push('特写缺少代码示例,应包含基本用法代码');
3180
- }
3181
- // 去掉代码块后,剩余描述性文字应足够
3182
- const proseLength = markdown
3183
- .replace(/```[\s\S]*?```/g, '')
3184
- .replace(/[#>\-*`\n]/g, '')
3185
- .trim().length;
3186
- if (proseLength < 50) {
3187
- candidateIssues.push('特写缺少项目特点描述,应融合基本用法和项目特点');
3188
- }
3189
-
3190
- // 检查 3: description 泛化措辞
3191
- if (/本模块|该文件|这个类|该项目/.test(description)) {
3192
- candidateIssues.push('description 使用了泛化措辞,应引用具体类名和数字');
3193
- }
3194
-
3195
- // 检查 4: description 过短
3196
- if (description.length < 15) {
3197
- candidateIssues.push(
3198
- `description 过短 (${description.length} 字), 应≥15字并包含具体类名和数字`
3199
- );
3200
- }
3201
-
3202
- // 检查 5: content.markdown 过短(可能是空壳)
3203
- if (markdown.length < 200) {
3204
- candidateIssues.push(`content.markdown 文档过短 (${markdown.length} 字), 可能缺少实质内容`);
3205
- }
3206
-
3207
- // 检查 6: 代码示例来源
3208
- const hasSourceAnnotation = /\([^)]*\.\w+[^)]*:\d+\)|\([^)]*\.\w+[^)]*\)/.test(markdown);
3209
- if (hasCodeBlock && !hasSourceAnnotation) {
3210
- candidateIssues.push('代码示例可能缺少来源文件标注 (建议标注 "来源: FileName.m:行号")');
3211
- }
3212
-
3213
- // 检查 7: Cursor 交付字段
3214
- if (!p.trigger) {
3215
- candidateIssues.push('缺少 trigger 字段');
3216
- }
3217
- if (!p.doClause) {
3218
- candidateIssues.push('缺少 doClause 字段');
3219
- }
3220
- if (!p.kind) {
3221
- candidateIssues.push('缺少 kind 字段');
3222
- }
3223
-
3224
- if (candidateIssues.length > 0) {
3225
- issues.push({ title, issues: candidateIssues });
3226
- }
3227
- checked.push({
3228
- title,
3229
- passed: candidateIssues.length === 0,
3230
- issueCount: candidateIssues.length,
3231
- });
3232
- }
3233
-
3234
- if (issues.length === 0) {
3235
- return {
3236
- status: 'all_passed',
3237
- checkedCount: submitted.length,
3238
- message: `✅ ${submitted.length} 条候选全部通过质量检查。`,
3239
- };
3240
- }
3241
-
3242
- const issueLines = issues.flatMap(({ title, issues: iss }) =>
3243
- iss.map((i) => `• "${title}": ${i}`)
3244
- );
3245
-
3246
- return {
3247
- status: 'issues_found',
3248
- checkedCount: submitted.length,
3249
- passedCount: submitted.length - issues.length,
3250
- failedCount: issues.length,
3251
- details: checked,
3252
- message: `⚠️ ${issues.length}/${submitted.length} 条候选存在质量问题:\n${issueLines.join('\n')}\n\n请修正后重新提交。`,
3253
- };
3254
- },
3255
- };
3256
-
3257
- // ════════════════════════════════════════════════════════════
3258
- // AST 结构化分析 (7) — v3.0 AI-First Bootstrap AST 工具
3259
- // ════════════════════════════════════════════════════════════
3260
-
3261
- /**
3262
- * 辅助: 安全获取 ProjectGraph 实例
3263
- * @param {object} ctx
3264
- * @returns {import('../../core/ast/ProjectGraph.js').default|null}
3265
- */
3266
- function _getProjectGraph(ctx) {
3267
- try {
3268
- return ctx.container?.get('projectGraph') || null;
3269
- } catch {
3270
- return null;
3271
- }
3272
- }
3273
-
3274
- // ────────────────────────────────────────────────────────────
3275
- // 44. get_project_overview — 项目 AST 概览
3276
- // ────────────────────────────────────────────────────────────
3277
- const getProjectOverview = {
3278
- name: 'get_project_overview',
3279
- description:
3280
- '获取项目的整体结构概览:文件统计、模块列表、入口点、类/协议/Category 数量。' +
3281
- '适用场景:了解项目规模和架构布局,规划探索路径。',
3282
- parameters: {
3283
- type: 'object',
3284
- properties: {},
3285
- },
3286
- handler: async (_params, ctx) => {
3287
- const graph = _getProjectGraph(ctx);
3288
- if (!graph) {
3289
- return 'AST 分析不可用 — ProjectGraph 未构建。请检查 tree-sitter 是否已安装。';
3290
- }
3291
-
3292
- const o = graph.getOverview();
3293
- const lines = [
3294
- `📊 项目 AST 概览 (构建耗时 ${o.buildTimeMs}ms)`,
3295
- ``,
3296
- `文件: ${o.totalFiles} | 类: ${o.totalClasses} | 协议: ${o.totalProtocols} | Category: ${o.totalCategories} | 方法: ${o.totalMethods}`,
3297
- ``,
3298
- `── 模块 ──`,
3299
- ];
3300
- for (const mod of o.topLevelModules) {
3301
- const count = o.classesPerModule[mod] || 0;
3302
- lines.push(` ${mod}/ — ${count} 个类`);
3303
- }
3304
- if (o.entryPoints.length > 0) {
3305
- lines.push(``, `── 入口点 ──`);
3306
- for (const ep of o.entryPoints) {
3307
- lines.push(` ${ep}`);
3308
- }
3309
- }
3310
- return lines.join('\n');
3311
- },
3312
- };
3313
-
3314
- // ────────────────────────────────────────────────────────────
3315
- // 45. get_class_hierarchy — 类继承层级
3316
- // ────────────────────────────────────────────────────────────
3317
- const getClassHierarchy = {
3318
- name: 'get_class_hierarchy',
3319
- description:
3320
- '查看指定类的继承链(向上到根类)和直接子类列表。' +
3321
- '传入 className 查看指定类,不传则返回项目中所有根类及其子树。',
3322
- parameters: {
3323
- type: 'object',
3324
- properties: {
3325
- className: { type: 'string', description: '类名 (可选, 不填则返回完整层级)' },
3326
- },
3327
- },
3328
- handler: async (params, ctx) => {
3329
- const graph = _getProjectGraph(ctx);
3330
- if (!graph) {
3331
- return 'AST 分析不可用 — ProjectGraph 未构建。';
3332
- }
3333
-
3334
- const className = params.className || params.class_name;
3335
- if (className) {
3336
- const chain = graph.getInheritanceChain(className);
3337
- const subs = graph.getSubclasses(className);
3338
- if (chain.length === 0) {
3339
- return `未找到类 ${className}`;
3340
- }
3341
-
3342
- const lines = [`🔗 ${className} 继承链:`, ` ${chain.join(' → ')}`];
3343
- if (subs.length > 0) {
3344
- lines.push(``, `直接子类 (${subs.length}):`);
3345
- for (const s of subs) {
3346
- lines.push(` ├── ${s}`);
3347
- }
3348
- }
3349
- return lines.join('\n');
3350
- }
3351
-
3352
- // 全量: 找出所有根类 (没有父类或父类不在项目中的类)
3353
- const allClasses = graph.getAllClassNames();
3354
- const roots = allClasses.filter((c) => {
3355
- const chain = graph.getInheritanceChain(c);
3356
- return chain.length <= 1 || !allClasses.includes(chain[1]);
3357
- });
3358
-
3359
- const lines = [`🌳 项目类层级 (${allClasses.length} 个类, ${roots.length} 棵树)`];
3360
- for (const root of roots.slice(0, 30)) {
3361
- const descendants = graph.getAllDescendants(root);
3362
- lines.push(` ${root} (${descendants.length} 个后代)`);
3363
- for (const d of descendants.slice(0, 5)) {
3364
- lines.push(` └── ${d}`);
3365
- }
3366
- if (descendants.length > 5) {
3367
- lines.push(` ... 还有 ${descendants.length - 5} 个`);
3368
- }
3369
- }
3370
- if (roots.length > 30) {
3371
- lines.push(`... 还有 ${roots.length - 30} 棵树`);
3372
- }
3373
- return lines.join('\n');
3374
- },
3375
- };
3376
-
3377
- // ────────────────────────────────────────────────────────────
3378
- // 46. get_class_info — 类详细信息
3379
- // ────────────────────────────────────────────────────────────
3380
- const getClassInfo = {
3381
- name: 'get_class_info',
3382
- description: '获取指定类的详细信息: 属性、方法签名、导入、继承关系、Category 扩展。',
3383
- parameters: {
3384
- type: 'object',
3385
- properties: {
3386
- className: { type: 'string', description: '类名 (必填)' },
3387
- },
3388
- required: ['className'],
3389
- },
3390
- handler: async (params, ctx) => {
3391
- const graph = _getProjectGraph(ctx);
3392
- if (!graph) {
3393
- return 'AST 分析不可用 — ProjectGraph 未构建。';
3394
- }
3395
-
3396
- const className = params.className || params.class_name;
3397
- const info = graph.getClassInfo(className);
3398
- if (!info) {
3399
- return `未找到类 "${className}"。可以使用 get_project_overview 查看项目中的所有类。`;
3400
- }
3401
-
3402
- const chain = graph.getInheritanceChain(className);
3403
- const cats = graph.getCategoryExtensions(className);
3404
- const subs = graph.getSubclasses(className);
3405
-
3406
- const lines = [
3407
- `📦 ${info.name}`,
3408
- `文件: ${info.filePath}:${info.line}`,
3409
- `继承: ${chain.join(' → ')}`,
3410
- ];
3411
-
3412
- if (info.protocols.length > 0) {
3413
- lines.push(`遵循: <${info.protocols.join(', ')}>`);
3414
- }
3415
-
3416
- if (info.properties.length > 0) {
3417
- lines.push(``, `── 属性 (${info.properties.length}) ──`);
3418
- for (const p of info.properties) {
3419
- const attrs = p.attributes.length > 0 ? ` (${p.attributes.join(', ')})` : '';
3420
- lines.push(` ${p.name}: ${p.type}${attrs}`);
3421
- }
3422
- }
3423
-
3424
- if (info.methods.length > 0) {
3425
- lines.push(``, `── 方法 (${info.methods.length}) ──`);
3426
- const classMethods = info.methods.filter((m) => m.isClassMethod);
3427
- const instanceMethods = info.methods.filter((m) => !m.isClassMethod);
3428
- for (const m of classMethods) {
3429
- const cx = m.complexity > 3 ? ` [复杂度:${m.complexity}]` : '';
3430
- lines.push(` + ${m.selector} → ${m.returnType}${cx}`);
3431
- }
3432
- for (const m of instanceMethods) {
3433
- const cx = m.complexity > 3 ? ` [复杂度:${m.complexity}]` : '';
3434
- lines.push(` - ${m.selector} → ${m.returnType}${cx}`);
3435
- }
3436
- }
3437
-
3438
- if (cats.length > 0) {
3439
- lines.push(``, `── Category 扩展 (${cats.length}) ──`);
3440
- for (const cat of cats) {
3441
- const methodNames = cat.methods.map((m) => m.selector).join(', ');
3442
- lines.push(` ${info.name}(${cat.categoryName}) — ${cat.filePath} — [${methodNames}]`);
3443
- }
3444
- }
3445
-
3446
- if (subs.length > 0) {
3447
- lines.push(``, `── 直接子类 (${subs.length}) ──`);
3448
- for (const s of subs) {
3449
- lines.push(` ${s}`);
3450
- }
3451
- }
3452
-
3453
- return lines.join('\n');
3454
- },
3455
- };
3456
-
3457
- // ────────────────────────────────────────────────────────────
3458
- // 47. get_protocol_info — 协议详细信息
3459
- // ────────────────────────────────────────────────────────────
3460
- const getProtocolInfo = {
3461
- name: 'get_protocol_info',
3462
- description: '获取指定协议的定义(必选/可选方法)及所有遵循该协议的类。',
3463
- parameters: {
3464
- type: 'object',
3465
- properties: {
3466
- protocolName: { type: 'string', description: '协议名 (必填)' },
3467
- },
3468
- required: ['protocolName'],
3469
- },
3470
- handler: async (params, ctx) => {
3471
- const graph = _getProjectGraph(ctx);
3472
- if (!graph) {
3473
- return 'AST 分析不可用 — ProjectGraph 未构建。';
3474
- }
3475
-
3476
- const protocolName = params.protocolName || params.protocol_name;
3477
- const info = graph.getProtocolInfo(protocolName);
3478
- if (!info) {
3479
- return `未找到协议 "${protocolName}"。可以使用 get_project_overview 查看项目中的所有协议。`;
3480
- }
3481
-
3482
- const lines = [`📋 @protocol ${info.name}`, `文件: ${info.filePath}:${info.line}`];
3483
-
3484
- if (info.inherits.length > 0) {
3485
- lines.push(`继承: <${info.inherits.join(', ')}>`);
3486
- }
3487
-
3488
- if (info.requiredMethods.length > 0) {
3489
- lines.push(``, `── @required (${info.requiredMethods.length}) ──`);
3490
- for (const m of info.requiredMethods) {
3491
- lines.push(` ${m.isClassMethod ? '+' : '-'} ${m.selector} → ${m.returnType}`);
3492
- }
3493
- }
3494
-
3495
- if (info.optionalMethods.length > 0) {
3496
- lines.push(``, `── @optional (${info.optionalMethods.length}) ──`);
3497
- for (const m of info.optionalMethods) {
3498
- lines.push(` ${m.isClassMethod ? '+' : '-'} ${m.selector} → ${m.returnType}`);
3499
- }
3500
- }
3501
-
3502
- if (info.conformers.length > 0) {
3503
- lines.push(``, `── 遵循者 (${info.conformers.length}) ──`);
3504
- for (const c of info.conformers) {
3505
- lines.push(` ${c}`);
3506
- }
3507
- } else {
3508
- lines.push(``, `⚠️ 暂未发现遵循此协议的类`);
3509
- }
3510
-
3511
- return lines.join('\n');
3512
- },
3513
- };
3514
-
3515
- // ────────────────────────────────────────────────────────────
3516
- // 48. get_method_overrides — 方法覆写查询
3517
- // ────────────────────────────────────────────────────────────
3518
- const getMethodOverrides = {
3519
- name: 'get_method_overrides',
3520
- description: '查找覆写了指定方法的所有子类。适用于理解方法在继承树中的多态行为。',
3521
- parameters: {
3522
- type: 'object',
3523
- properties: {
3524
- className: { type: 'string', description: '定义该方法的基类名 (必填)' },
3525
- methodName: { type: 'string', description: '方法名或 selector (必填)' },
3526
- },
3527
- required: ['className', 'methodName'],
3528
- },
3529
- handler: async (params, ctx) => {
3530
- const graph = _getProjectGraph(ctx);
3531
- if (!graph) {
3532
- return 'AST 分析不可用 — ProjectGraph 未构建。';
3533
- }
3534
-
3535
- const className = params.className || params.class_name;
3536
- const methodName = params.methodName || params.method_name;
3537
- const overrides = graph.getMethodOverrides(className, methodName);
3538
-
3539
- if (overrides.length === 0) {
3540
- return `"${className}.${methodName}" 没有在任何子类中被覆写。`;
3541
- }
3542
-
3543
- const lines = [`🔀 ${className}.${methodName} 的覆写 (${overrides.length} 处):`];
3544
- for (const o of overrides) {
3545
- const cx = o.method.complexity > 3 ? ` [复杂度:${o.method.complexity}]` : '';
3546
- lines.push(` ${o.className} — ${o.filePath}:${o.method.line}${cx}`);
3547
- }
3548
- return lines.join('\n');
3549
- },
3550
- };
3551
-
3552
- // ────────────────────────────────────────────────────────────
3553
- // 49. get_category_map — Category 扩展映射
3554
- // ────────────────────────────────────────────────────────────
3555
- const getCategoryMap = {
3556
- name: 'get_category_map',
3557
- description:
3558
- '获取指定类或整个项目的 ObjC Category 扩展映射。Category 是 ObjC 的核心模式,了解它有助于发现功能划分。',
3559
- parameters: {
3560
- type: 'object',
3561
- properties: {
3562
- className: {
3563
- type: 'string',
3564
- description: '类名 — 可选, 不填则返回整个项目中有 Category 的类列表',
3565
- },
3566
- },
3567
- },
3568
- handler: async (params, ctx) => {
3569
- const graph = _getProjectGraph(ctx);
3570
- if (!graph) {
3571
- return 'AST 分析不可用 — ProjectGraph 未构建。';
3572
- }
3573
-
3574
- const className = params.className || params.class_name;
3575
- if (className) {
3576
- const cats = graph.getCategoryExtensions(className);
3577
- if (cats.length === 0) {
3578
- return `"${className}" 没有 Category 扩展。`;
3579
- }
3580
-
3581
- const lines = [`📂 ${className} 的 Category 扩展 (${cats.length}):`];
3582
- for (const cat of cats) {
3583
- lines.push(` ${className}(${cat.categoryName}) — ${cat.filePath}:${cat.line}`);
3584
- for (const m of cat.methods) {
3585
- lines.push(` ${m.isClassMethod ? '+' : '-'} ${m.selector}`);
3586
- }
3587
- if (cat.protocols.length > 0) {
3588
- lines.push(` 遵循: <${cat.protocols.join(', ')}>`);
3589
- }
3590
- }
3591
- return lines.join('\n');
3592
- }
3593
-
3594
- // 全量概览
3595
- const allClasses = graph.getAllClassNames();
3596
- const withCats = allClasses
3597
- .map((c) => ({ name: c, cats: graph.getCategoryExtensions(c) }))
3598
- .filter((x) => x.cats.length > 0)
3599
- .sort((a, b) => b.cats.length - a.cats.length);
3600
-
3601
- if (withCats.length === 0) {
3602
- return '项目中没有发现 Category 扩展。';
3603
- }
3604
-
3605
- const lines = [`📂 项目 Category 概览 (${withCats.length} 个类有 Category):`];
3606
- for (const { name, cats } of withCats.slice(0, 30)) {
3607
- const catNames = cats.map((c) => c.categoryName).join(', ');
3608
- lines.push(` ${name} — ${cats.length} 个: (${catNames})`);
3609
- }
3610
- if (withCats.length > 30) {
3611
- lines.push(`... 还有 ${withCats.length - 30} 个类`);
3612
- }
3613
- return lines.join('\n');
3614
- },
3615
- };
3616
-
3617
- // ────────────────────────────────────────────────────────────
3618
- // 50. get_previous_analysis — 前序维度分析结果 (可选)
3619
- // ────────────────────────────────────────────────────────────
3620
-
3621
- const getPreviousAnalysis = {
3622
- name: 'get_previous_analysis',
3623
- description:
3624
- '获取前序维度的分析摘要。在 bootstrap 中,每个维度可能有前面维度的分析结果可用。' +
3625
- '调用此工具可以获取之前维度产出的候选标题、设计决策等上下文,避免重复分析。' +
3626
- '注意: 只有在你认为前序上下文对当前任务有帮助时才调用。',
3627
- parameters: {
3628
- type: 'object',
3629
- properties: {},
3630
- },
3631
- handler: async (_params, ctx) => {
3632
- // 从 ctx._dimensionMeta 读取前序分析
3633
- const meta = ctx._dimensionMeta;
3634
- if (!meta || !meta.previousAnalysis) {
3635
- return '没有前序维度的分析结果可用。';
3636
- }
3637
-
3638
- const prev = meta.previousAnalysis;
3639
- if (typeof prev === 'string') {
3640
- return prev;
3641
- }
3642
-
3643
- // 格式化前序分析
3644
- const lines = ['📋 前序维度分析摘要:'];
3645
- if (Array.isArray(prev)) {
3646
- for (const item of prev) {
3647
- if (typeof item === 'string') {
3648
- lines.push(` ${item}`);
3649
- } else if (item.dimension && item.summary) {
3650
- lines.push(``, `── ${item.dimension} ──`);
3651
- lines.push(` ${item.summary}`);
3652
- if (item.candidateTitles?.length > 0) {
3653
- lines.push(` 已提交候选: ${item.candidateTitles.join(', ')}`);
3654
- }
3655
- }
3656
- }
3657
- } else if (typeof prev === 'object') {
3658
- for (const [key, value] of Object.entries(prev)) {
3659
- lines.push(` ${key}: ${typeof value === 'string' ? value : JSON.stringify(value)}`);
3660
- }
3661
- }
3662
- return lines.join('\n');
3663
- },
3664
- };
3665
-
3666
- // ────────────────────────────────────────────────────────────
3667
- // 51. note_finding — 记录关键发现到工作记忆 (Scratchpad)
3668
- // ────────────────────────────────────────────────────────────
3669
- const noteFinding = {
3670
- name: 'note_finding',
3671
- description:
3672
- '记录一个关键发现到工作记忆的 Scratchpad。在分析过程中发现重要模式、设计决策或事实时调用。' +
3673
- '这些发现会在上下文窗口压缩后依然保留,确保分析后期不会遗忘早期重要发现。' +
3674
- '建议在发现关键架构模式、核心类职责、重要设计约束时调用。',
3675
- parameters: {
3676
- type: 'object',
3677
- properties: {
3678
- finding: {
3679
- type: 'string',
3680
- description:
3681
- '关键发现描述 (≤150 字)。应是具体、可验证的陈述,例如 "BDNetworkManager 使用单例模式,所有请求通过其发起"',
3682
- },
3683
- evidence: {
3684
- type: 'string',
3685
- description: '支持证据 (文件路径:行号),例如 "BDNetworkManager.m:45"',
3686
- },
3687
- importance: {
3688
- type: 'number',
3689
- description: '重要性评分 1-10。8+ = 影响全局架构,5-7 = 常见模式,1-4 = 细节备注',
3690
- },
3691
- },
3692
- required: ['finding'],
3693
- },
3694
- handler: async (params, ctx) => {
3695
- const workingMemory = ctx._workingMemory;
3696
- if (!workingMemory) {
3697
- return '⚠ 工作记忆未初始化 (仅在 bootstrap 分析期间可用)';
3698
- }
3699
-
3700
- const finding = params.finding || '';
3701
- const evidence = params.evidence || '';
3702
- const importance = params.importance || 5;
3703
- const round = ctx._currentRound || 0;
3704
-
3705
- workingMemory.noteKeyFinding(finding, evidence, importance, round);
3706
-
3707
- return `📌 已记录发现 [${importance}/10]: "${finding.substring(0, 80)}" — 当前共 ${workingMemory.scratchpadSize} 条关键发现`;
3708
- },
3709
- };
3710
-
3711
- // ────────────────────────────────────────────────────────────
3712
- // 52. get_previous_evidence — 检索前序维度的代码证据
3713
- // ────────────────────────────────────────────────────────────
3714
- const getPreviousEvidence = {
3715
- name: 'get_previous_evidence',
3716
- description:
3717
- '获取前序维度对特定文件/类/模式的分析证据。避免重复搜索和读取已经被其他维度分析过的内容。' +
3718
- '当你要搜索某个类名或文件时,先调用此工具看前序维度是否已有发现。',
3719
- parameters: {
3720
- type: 'object',
3721
- properties: {
3722
- query: {
3723
- type: 'string',
3724
- description: '搜索查询 (文件名、类名、模式名、关键词)',
3725
- },
3726
- dimId: {
3727
- type: 'string',
3728
- description: '指定维度 ID (可选,默认搜索所有前序维度)',
3729
- },
3730
- },
3731
- required: ['query'],
3732
- },
3733
- handler: async (params, ctx) => {
3734
- const episodicMemory = ctx._episodicMemory;
3735
- if (!episodicMemory) {
3736
- return '没有前序维度的证据可用。';
3737
- }
3738
-
3739
- const results = episodicMemory.searchEvidence(params.query, params.dimId || undefined);
3740
-
3741
- if (results.length === 0) {
3742
- return `没有找到与 "${params.query}" 相关的前序证据。建议自行搜索。`;
3743
- }
3744
-
3745
- const lines = [`📋 前序维度证据 (匹配 "${params.query}", ${results.length} 条):`];
3746
- for (const r of results.slice(0, 8)) {
3747
- lines.push(` 📄 ${r.filePath}`);
3748
- lines.push(
3749
- ` [${r.evidence.dimId}] [${r.evidence.importance || 5}/10] ${r.evidence.finding}`
3750
- );
3751
- }
3752
- if (results.length > 8) {
3753
- lines.push(` …还有 ${results.length - 8} 条证据`);
3754
- }
3755
- return lines.join('\n');
3756
- },
3757
- };
3758
-
3759
- // ────────────────────────────────────────────────────────────
3760
- // 53. query_code_graph — 查询代码实体图谱
3761
- // ────────────────────────────────────────────────────────────
3762
- const queryCodeGraph = {
3763
- name: 'query_code_graph',
3764
- description:
3765
- '查询代码实体图谱 (Code Entity Graph)。可查询类继承链、协议遵循者、实体搜索、影响分析等。' +
3766
- '图谱包含从 AST 提取的类、协议、Category、模块、设计模式及其关系。',
3767
- parameters: {
3768
- type: 'object',
3769
- properties: {
3770
- action: {
3771
- type: 'string',
3772
- enum: [
3773
- 'search',
3774
- 'inheritance_chain',
3775
- 'descendants',
3776
- 'conformances',
3777
- 'impact',
3778
- 'topology',
3779
- 'entity_edges',
3780
- ],
3781
- description:
3782
- '查询动作: search=搜索实体, inheritance_chain=继承链, descendants=子类/遵循者, conformances=协议遵循, impact=影响分析, topology=拓扑概览, entity_edges=实体的所有边',
3783
- },
3784
- entity_id: {
3785
- type: 'string',
3786
- description: '实体 ID (类名/协议名)。search 时为搜索关键词。',
3787
- },
3788
- entity_type: {
3789
- type: 'string',
3790
- enum: ['class', 'protocol', 'category', 'module', 'pattern'],
3791
- description: '实体类型过滤 (可选)',
3792
- },
3793
- max_depth: {
3794
- type: 'number',
3795
- description: '遍历深度 (默认 3)',
3796
- },
3797
- },
3798
- required: ['action', 'entity_id'],
3799
- },
3800
- handler: async (params, ctx) => {
3801
- try {
3802
- const { CodeEntityGraph } = await import('./../../service/knowledge/CodeEntityGraph.js');
3803
- const db = ctx?.container?.get('database');
3804
- if (!db) {
3805
- return '代码实体图谱不可用: 数据库未初始化';
3806
- }
3807
-
3808
- const projectRoot = ctx?.projectRoot || process.env.ASD_PROJECT_DIR || '';
3809
- const ceg = new CodeEntityGraph(db, { projectRoot });
3810
- const maxDepth = params.max_depth || 3;
3811
-
3812
- switch (params.action) {
3813
- case 'search': {
3814
- const results = ceg.searchEntities(params.entity_id, {
3815
- type: params.entity_type,
3816
- limit: 15,
3817
- });
3818
- if (results.length === 0) {
3819
- return `未找到匹配 "${params.entity_id}" 的代码实体。`;
3820
- }
3821
- const lines = [`🔍 代码实体搜索 "${params.entity_id}" (${results.length} 条):`];
3822
- for (const e of results) {
3823
- lines.push(
3824
- ` • ${e.entityType}: \`${e.name}\`${e.filePath ? ` (${e.filePath}:${e.line || '?'})` : ''}${e.superclass ? ` → ${e.superclass}` : ''}`
3825
- );
3826
- }
3827
- return lines.join('\n');
3828
- }
3829
-
3830
- case 'inheritance_chain': {
3831
- const chain = ceg.getInheritanceChain(params.entity_id, maxDepth);
3832
- if (chain.length <= 1) {
3833
- return `\`${params.entity_id}\` 没有已知的继承关系。`;
3834
- }
3835
- return `📐 继承链: \`${chain.join(' → ')}\``;
3836
- }
3837
-
3838
- case 'descendants': {
3839
- const type = params.entity_type || 'class';
3840
- const desc = ceg.getDescendants(params.entity_id, type, maxDepth);
3841
- if (desc.length === 0) {
3842
- return `\`${params.entity_id}\` 没有已知的子类/遵循者。`;
3843
- }
3844
- const lines = [`📊 ${params.entity_id} 的后代 (${desc.length}):`];
3845
- for (const d of desc.slice(0, 20)) {
3846
- lines.push(` ${' '.repeat(d.depth - 1)}└─ \`${d.id}\` (${d.type}, ${d.relation})`);
3847
- }
3848
- return lines.join('\n');
3849
- }
3850
-
3851
- case 'conformances': {
3852
- const protos = ceg.getConformances(params.entity_id);
3853
- if (protos.length === 0) {
3854
- return `\`${params.entity_id}\` 没有已知的协议遵循。`;
3855
- }
3856
- return `📋 \`${params.entity_id}\` 遵循: ${protos.map((p) => `\`${p}\``).join(', ')}`;
3857
- }
3858
-
3859
- case 'impact': {
3860
- const type = params.entity_type || 'class';
3861
- const impact = ceg.getImpactRadius(params.entity_id, type, maxDepth);
3862
- if (impact.length === 0) {
3863
- return `修改 \`${params.entity_id}\` 没有检测到直接影响。`;
3864
- }
3865
- const lines = [`⚡ 修改 \`${params.entity_id}\` 的影响范围 (${impact.length}):`];
3866
- for (const i of impact.slice(0, 20)) {
3867
- lines.push(` ${' '.repeat(i.depth - 1)}⬆ \`${i.id}\` (${i.type}, via ${i.relation})`);
3868
- }
3869
- return lines.join('\n');
3870
- }
3871
-
3872
- case 'topology': {
3873
- const topo = ceg.getTopology();
3874
- if (topo.totalEntities === 0) {
3875
- return '代码实体图谱为空。需先执行 Bootstrap。';
3876
- }
3877
- const lines = ['📈 代码实体图谱概览:'];
3878
- lines.push(' 实体:');
3879
- for (const [type, count] of Object.entries(topo.entities)) {
3880
- lines.push(` • ${type}: ${count}`);
3881
- }
3882
- lines.push(` 总边数: ${topo.totalEdges}`);
3883
- if (topo.hotNodes.length > 0) {
3884
- lines.push(' 核心实体 (入度最高):');
3885
- for (const n of topo.hotNodes.slice(0, 8)) {
3886
- lines.push(` • \`${n.id}\` (${n.type}, 入度=${n.inDegree})`);
3887
- }
3888
- }
3889
- return lines.join('\n');
3890
- }
3891
-
3892
- case 'entity_edges': {
3893
- const type = params.entity_type || 'class';
3894
- const edges = ceg.getEntityEdges(params.entity_id, type);
3895
- const total = edges.outgoing.length + edges.incoming.length;
3896
- if (total === 0) {
3897
- return `\`${params.entity_id}\` 没有已知的图谱边。`;
3898
- }
3899
- const lines = [`🔗 \`${params.entity_id}\` 的关系 (${total} 条):`];
3900
- if (edges.outgoing.length > 0) {
3901
- lines.push(' 出边:');
3902
- for (const e of edges.outgoing.slice(0, 10)) {
3903
- lines.push(` → \`${e.toId}\` (${e.toType}, ${e.relation})`);
3904
- }
3905
- }
3906
- if (edges.incoming.length > 0) {
3907
- lines.push(' 入边:');
3908
- for (const e of edges.incoming.slice(0, 10)) {
3909
- lines.push(` ← \`${e.fromId}\` (${e.fromType}, ${e.relation})`);
3910
- }
3911
- }
3912
- return lines.join('\n');
3913
- }
3914
-
3915
- default:
3916
- return `未知动作: ${params.action}`;
3917
- }
3918
- } catch (err) {
3919
- return `代码实体图谱查询失败: ${err.message}`;
3920
- }
3921
- },
3922
- };
3923
-
3924
- export const ALL_TOOLS = [
3925
- // 项目数据访问 (5) — 含 v10 Agent-Pull 工具
3926
- searchProjectCode,
3927
- readProjectFile,
3928
- listProjectStructure,
3929
- getFileSummary,
3930
- semanticSearchCode,
3931
- // 查询类 (8)
3932
- searchRecipes,
3933
- searchCandidates,
3934
- getRecipeDetail,
3935
- getProjectStats,
3936
- searchKnowledge,
3937
- getRelatedRecipes,
3938
- listGuardRules,
3939
- getRecommendations,
3940
- // AI 分析类 (5)
3941
- summarizeCode,
3942
- extractRecipes,
3943
- enrichCandidate,
3944
- refineBootstrapCandidates,
3945
- aiTranslate,
3946
- // Guard 安全类 (3)
3947
- guardCheckCode,
3948
- queryViolations,
3949
- generateGuardRule,
3950
- // 生命周期操作类 (7)
3951
- submitCandidate,
3952
- saveDocument,
3953
- approveCandidate,
3954
- rejectCandidate,
3955
- publishRecipe,
3956
- deprecateRecipe,
3957
- updateRecipe,
3958
- recordUsage,
3959
- // 质量与反馈类 (3)
3960
- qualityScore,
3961
- validateCandidate,
3962
- getFeedbackStats,
3963
- // 知识图谱类 (3)
3964
- checkDuplicate,
3965
- discoverRelations,
3966
- addGraphEdge,
3967
- // 基础设施类 (3)
3968
- graphImpactAnalysis,
3969
- rebuildIndex,
3970
- queryAuditLog,
3971
- // Skills & Bootstrap (4)
3972
- loadSkill,
3973
- createSkillTool,
3974
- suggestSkills,
3975
- bootstrapKnowledgeTool,
3976
- // 组合工具 (3) — 减少 ReAct 轮次
3977
- analyzeCode,
3978
- knowledgeOverview,
3979
- submitWithCheck,
3980
- // 元工具 (3) — Agent 自主能力增强
3981
- getToolDetails,
3982
- planTask,
3983
- reviewMyOutput,
3984
- // AST 结构化分析 (7) — v3.0 AI-First Bootstrap
3985
- getProjectOverview,
3986
- getClassHierarchy,
3987
- getClassInfo,
3988
- getProtocolInfo,
3989
- getMethodOverrides,
3990
- getCategoryMap,
3991
- getPreviousAnalysis,
3992
- // Agent Memory 增强 (2) — 工作记忆 + 情景记忆
3993
- noteFinding,
3994
- getPreviousEvidence,
3995
- // 代码实体图谱 (1) — Phase E
3996
- queryCodeGraph,
3997
- ];
3998
-
3999
- export default ALL_TOOLS;
18
+ export { ALL_TOOLS } from './tools/index.js';
19
+ export { default } from './tools/index.js';