autosnippet 2.5.0 → 2.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/bin/cli.js +35 -0
  2. package/dashboard/dist/assets/{icons-Dtm0E6DS.js → icons-Cq4-iQhP.js} +152 -87
  3. package/dashboard/dist/assets/index-DBxH7pVn.css +1 -0
  4. package/dashboard/dist/assets/index-Dw2F6qAS.js +197 -0
  5. package/dashboard/dist/assets/{react-markdown-CWxUbOf4.js → react-markdown-BA6FB2NP.js} +1 -1
  6. package/dashboard/dist/assets/{syntax-highlighter-CJ2drQQb.js → syntax-highlighter-CVLHn9O5.js} +1 -1
  7. package/dashboard/dist/assets/{vendor-f83ah6cm.js → vendor-BotF760a.js} +61 -61
  8. package/dashboard/dist/index.html +6 -6
  9. package/lib/bootstrap.js +1 -1
  10. package/lib/cli/SetupService.js +33 -8
  11. package/lib/cli/UpgradeService.js +139 -2
  12. package/lib/core/ast/ProjectGraph.js +599 -0
  13. package/lib/core/gateway/Gateway.js +19 -4
  14. package/lib/core/gateway/GatewayActionRegistry.js +2 -2
  15. package/lib/domain/recipe/Recipe.js +3 -0
  16. package/lib/external/ai/AiProvider.js +117 -10
  17. package/lib/external/ai/providers/ClaudeProvider.js +197 -0
  18. package/lib/external/ai/providers/GoogleGeminiProvider.js +235 -1
  19. package/lib/external/ai/providers/OpenAiProvider.js +131 -0
  20. package/lib/external/mcp/McpServer.js +2 -1
  21. package/lib/external/mcp/handlers/bootstrap/pipeline/dimension-context.js +216 -0
  22. package/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +468 -0
  23. package/lib/external/mcp/handlers/bootstrap/pipeline/tier-scheduler.js +162 -0
  24. package/lib/external/mcp/handlers/bootstrap/skills.js +225 -0
  25. package/lib/external/mcp/handlers/bootstrap.js +151 -1634
  26. package/lib/external/mcp/handlers/browse.js +1 -1
  27. package/lib/external/mcp/handlers/candidate.js +1 -33
  28. package/lib/external/mcp/handlers/skill.js +126 -31
  29. package/lib/external/mcp/tools.js +25 -3
  30. package/lib/http/middleware/requestLogger.js +23 -4
  31. package/lib/http/routes/ai.js +3 -1
  32. package/lib/http/routes/auth.js +3 -2
  33. package/lib/http/routes/candidates.js +49 -25
  34. package/lib/http/routes/commands.js +0 -8
  35. package/lib/http/routes/guardRules.js +1 -16
  36. package/lib/http/routes/recipes.js +4 -17
  37. package/lib/http/routes/search.js +16 -22
  38. package/lib/http/routes/skills.js +40 -3
  39. package/lib/http/routes/snippets.js +0 -33
  40. package/lib/http/routes/spm.js +37 -63
  41. package/lib/http/utils/routeHelpers.js +31 -0
  42. package/lib/infrastructure/audit/AuditStore.js +18 -0
  43. package/lib/infrastructure/config/Paths.js +9 -0
  44. package/lib/infrastructure/logging/Logger.js +86 -3
  45. package/lib/infrastructure/realtime/RealtimeService.js +2 -5
  46. package/lib/infrastructure/vector/JsonVectorAdapter.js +24 -1
  47. package/lib/injection/ServiceContainer.js +62 -3
  48. package/lib/service/bootstrap/BootstrapTaskManager.js +400 -0
  49. package/lib/service/candidate/CandidateFileWriter.js +68 -27
  50. package/lib/service/candidate/CandidateService.js +156 -10
  51. package/lib/service/chat/AnalystAgent.js +216 -0
  52. package/lib/service/chat/CandidateGuardrail.js +134 -0
  53. package/lib/service/chat/ChatAgent.js +1272 -155
  54. package/lib/service/chat/ContextWindow.js +730 -0
  55. package/lib/service/chat/ConversationStore.js +377 -0
  56. package/lib/service/chat/HandoffProtocol.js +180 -0
  57. package/lib/service/chat/Memory.js +40 -10
  58. package/lib/service/chat/ProducerAgent.js +240 -0
  59. package/lib/service/chat/ToolRegistry.js +149 -5
  60. package/lib/service/chat/tools.js +1493 -60
  61. package/lib/service/recipe/RecipeFileWriter.js +12 -1
  62. package/lib/service/skills/EventAggregator.js +187 -0
  63. package/lib/service/skills/SignalCollector.js +549 -0
  64. package/lib/service/skills/SkillAdvisor.js +324 -0
  65. package/lib/service/skills/SkillHooks.js +13 -5
  66. package/lib/service/spm/SpmService.js +2 -2
  67. package/package.json +1 -1
  68. package/templates/copilot-instructions.md +20 -3
  69. package/templates/cursor-rules/autosnippet-conventions.mdc +21 -4
  70. package/templates/cursor-rules/autosnippet-skills.mdc +45 -0
  71. package/dashboard/dist/assets/index-B7VpZOCz.css +0 -1
  72. package/dashboard/dist/assets/index-D87IZTmZ.js +0 -187
@@ -1,69 +1,89 @@
1
1
  /**
2
2
  * tools.js — ChatAgent 全部工具定义
3
3
  *
4
- * 37 个工具覆盖项目全部 AI 能力:
4
+ * 54 个工具覆盖项目全部 AI 能力:
5
5
  *
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
+ * └────────────────────────────────────────────────────────┘
6
13
  * ┌─── 查询类 (8) ─────────────────────────────────┐
7
- * │ 1. search_recipes 搜索 Recipe │
8
- * │ 2. search_candidates 搜索候选项 │
9
- * │ 3. get_recipe_detail 获取 Recipe 详情 │
10
- * │ 4. get_project_stats 获取项目统计 │
11
- * │ 5. search_knowledge RAG 知识库搜索 │
12
- * │ 6. get_related_recipes 知识图谱关联查询 │
13
- * │ 7. list_guard_rules 列出 Guard 规则 │
14
- * │ 8. get_recommendations 获取推荐 Recipe │
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 │
15
22
  * └─────────────────────────────────────────────────┘
16
23
  * ┌─── AI 分析类 (5) ──────────────────────────────────┐
17
- * │ 9. summarize_code 代码摘要 │
18
- * │ 10. extract_recipes 从源码提取 Recipe │
19
- * │ 11. enrich_candidate ① 结构补齐 │
20
- * │ 11b. refine_bootstrap_candidates ② 内容润色 │
21
- * │ 12. ai_translate AI 翻译 (中→英) │
24
+ * │ 11. summarize_code 代码摘要 │
25
+ * │ 12. extract_recipes 从源码提取 Recipe │
26
+ * │ 13. enrich_candidate ① 结构补齐 │
27
+ * │ 13b. refine_bootstrap_candidates ② 内容润色 │
28
+ * │ 14. ai_translate AI 翻译 (中→英) │
22
29
  * └─────────────────────────────────────────────────────┘
23
30
  * ┌─── Guard 安全类 (3) ───────────────────────────────┐
24
- * │ 13. guard_check_code Guard 规则代码检查 │
25
- * │ 14. query_violations 查询 Guard 违规记录 │
26
- * │ 15. generate_guard_rule AI 生成 Guard 规则 │
31
+ * │ 15. guard_check_code Guard 规则代码检查 │
32
+ * │ 16. query_violations 查询 Guard 违规记录 │
33
+ * │ 17. generate_guard_rule AI 生成 Guard 规则 │
27
34
  * └─────────────────────────────────────────────────────┘
28
35
  * ┌─── 生命周期操作类 (7) ─────────────────────────────┐
29
- * │ 16. submit_candidate 提交候选 │
30
- * │ 17. approve_candidate 批准候选 │
31
- * │ 18. reject_candidate 驳回候选 │
32
- * │ 19. publish_recipe 发布 Recipe │
33
- * │ 20. deprecate_recipe 弃用 Recipe │
34
- * │ 21. update_recipe 更新 Recipe 字段 │
35
- * │ 22. record_usage 记录 Recipe 使用 │
36
+ * │ 18. submit_candidate 提交候选 │
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 使用 │
36
43
  * └─────────────────────────────────────────────────────┘
37
44
  * ┌─── 质量与反馈类 (3) ───────────────────────────────┐
38
- * │ 23. quality_score Recipe 质量评分 │
39
- * │ 24. validate_candidate 候选校验 │
40
- * │ 25. get_feedback_stats 获取反馈统计 │
45
+ * │ 25. quality_score Recipe 质量评分 │
46
+ * │ 26. validate_candidate 候选校验 │
47
+ * │ 27. get_feedback_stats 获取反馈统计 │
41
48
  * └─────────────────────────────────────────────────────┘
42
49
  * ┌─── 知识图谱类 (3) ─────────────────────────────────┐
43
- * │ 26. check_duplicate 候选查重 │
44
- * │ 27. discover_relations 知识图谱关系发现 │
45
- * │ 28. add_graph_edge 添加知识图谱关系 │
50
+ * │ 28. check_duplicate 候选查重 │
51
+ * │ 29. discover_relations 知识图谱关系发现 │
52
+ * │ 30. add_graph_edge 添加知识图谱关系 │
46
53
  * └─────────────────────────────────────────────────────┘
47
54
  * ┌─── 基础设施类 (3) ─────────────────────────────────┐
48
- * │ 29. graph_impact_analysis 影响范围分析 │
49
- * │ 30. rebuild_index 向量索引重建 │
50
- * │ 31. query_audit_log 审计日志查询 │
55
+ * │ 31. graph_impact_analysis 影响范围分析 │
56
+ * │ 32. rebuild_index 向量索引重建 │
57
+ * │ 33. query_audit_log 审计日志查询 │
51
58
  * └─────────────────────────────────────────────────────┘
52
- * ┌─── Skills & Bootstrap (2) ─────────────────────────┐
53
- * │ 32. load_skill 加载 Agent Skill 文档 │
54
- * │ 33. bootstrap_knowledge 冷启动知识库初始化
59
+ * ┌─── Skills & Bootstrap (4) ─────────────────────────┐
60
+ * │ 34. load_skill 加载 Agent Skill 文档 │
61
+ * │ 35. create_skill 创建项目级 Skill
62
+ * │ 36. suggest_skills 推荐创建 Skill │
63
+ * │ 37. bootstrap_knowledge 冷启动知识库初始化 │
55
64
  * └─────────────────────────────────────────────────────┘
56
65
  * ┌─── 组合工具 (3) ───────────────────────────────────┐
57
- * │ 34. analyze_code Guard + Recipe 搜索 │
58
- * │ 35. knowledge_overview 全局知识库概览 │
59
- * │ 36. submit_with_check 查重 + 提交 │
66
+ * │ 38. analyze_code Guard + Recipe 搜索 │
67
+ * │ 39. knowledge_overview 全局知识库概览 │
68
+ * │ 40. submit_with_check 查重 + 提交 │
60
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 — 语义相似度知识搜索
61
80
  */
62
81
 
63
82
  import fs from 'node:fs';
64
83
  import path from 'node:path';
65
84
  import { fileURLToPath } from 'node:url';
66
85
  import { findSimilarRecipes } from '../candidate/SimilarityService.js';
86
+ import { CandidateGuardrail } from './CandidateGuardrail.js';
67
87
  import Logger from '../../infrastructure/logging/Logger.js';
68
88
 
69
89
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
@@ -74,8 +94,644 @@ const SKILLS_DIR = path.resolve(PROJECT_ROOT, 'skills');
74
94
  /** 项目级 skills 目录 */
75
95
  const PROJECT_SKILLS_DIR = path.resolve(PROJECT_ROOT, '.autosnippet', 'skills');
76
96
 
97
+ // ════════════════════════════════════════════════════════════
98
+ // 项目数据访问 (5) — 搜索/读取用户项目源码 + v10 Agent-Pull
99
+ // ════════════════════════════════════════════════════════════
100
+
101
+ // ────────────────────────────────────────────────────────────
102
+ // 1. search_project_code — 搜索项目源码
103
+ // ────────────────────────────────────────────────────────────
104
+
105
+ /** 三方库路径识别(与 bootstrap/shared/third-party-filter.js 对齐) */
106
+ const THIRD_PARTY_RE = /(?:^|\/)(?: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;
107
+
108
+ /** 源码文件扩展名 */
109
+ const SOURCE_EXT_RE = /\.(m|mm|swift|h|c|cpp|js|ts|jsx|tsx|py|rb|java|kt|go|rs)$/i;
110
+
111
+ /** 声明行识别 — 用于对匹配行打分(与 bootstrap/shared/scanner.js 对齐) */
112
+ const DECL_RE = /^\s*(@property\b|@interface\b|@protocol\b|@class\b|@synthesize\b|@dynamic\b|@end\b|NS_ASSUME_NONNULL|#import\b|#include\b|#define\b)/;
113
+ const TYPE_DECL_RE = /^\s*\w[\w<>*\s]+[\s*]+_?\w+\s*;$/;
114
+
115
+ function _scoreSearchLine(line) {
116
+ const t = line.trim();
117
+ if (DECL_RE.test(t)) return -2;
118
+ if (TYPE_DECL_RE.test(t)) return -1;
119
+ if (/^[-+]\s*\([^)]+\)\s*\w+[^{]*;\s*$/.test(t)) return -1;
120
+ if (/\[.*\w+.*\]/.test(t)) return 2; // ObjC message send
121
+ if (/\w+\s*\(/.test(t)) return 2; // function call
122
+ if (/\^\s*[{(]/.test(t)) return 1; // block literal
123
+ return 0;
124
+ }
125
+
126
+ const searchProjectCode = {
127
+ name: 'search_project_code',
128
+ description: '在用户项目源码中搜索指定模式。返回匹配的代码片段及上下文。' +
129
+ '自动过滤三方库代码(Pods/Carthage/node_modules),优先返回实际使用行而非声明行。' +
130
+ '适用场景:验证代码模式存在性、查找更多项目示例、理解项目中某个 API 的用法。',
131
+ parameters: {
132
+ type: 'object',
133
+ properties: {
134
+ pattern: { type: 'string', description: '搜索词或正则表达式' },
135
+ isRegex: { type: 'boolean', description: '是否为正则表达式,默认 false' },
136
+ fileFilter: { type: 'string', description: '文件扩展名过滤,如 ".m,.swift"' },
137
+ contextLines: { type: 'number', description: '匹配行前后的上下文行数,默认 5' },
138
+ maxResults: { type: 'number', description: '最大返回结果数,默认 8' },
139
+ },
140
+ required: ['pattern'],
141
+ },
142
+ handler: async (params, ctx) => {
143
+ // 兼容 AI 传 "query" / "search" / "keyword" 替代 "pattern"
144
+ const pattern = params.pattern || params.query || params.search || params.keyword || params.search_query;
145
+ const { isRegex = false, fileFilter, contextLines = 5, maxResults = 8 } = params;
146
+ const projectRoot = ctx.projectRoot || process.cwd();
147
+
148
+ if (!pattern || typeof pattern !== 'string') {
149
+ return { error: '参数错误: 请提供 pattern(搜索关键词或正则表达式)', matches: [], total: 0 };
150
+ }
151
+
152
+ // 构建搜索正则
153
+ let searchRe;
154
+ try {
155
+ searchRe = isRegex ? new RegExp(pattern, 'gi') : new RegExp(pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'gi');
156
+ } catch (err) {
157
+ return { error: `Invalid pattern: ${err.message}`, matches: [], total: 0 };
158
+ }
159
+
160
+ // 文件扩展名过滤
161
+ let extFilter = null;
162
+ if (fileFilter) {
163
+ const exts = fileFilter.split(',').map(e => e.trim().replace(/^\./, ''));
164
+ extFilter = new RegExp(`\\.(${exts.join('|')})$`, 'i');
165
+ }
166
+
167
+ // 收集文件列表 — 优先使用内存缓存(bootstrap 场景),否则从磁盘递归读取
168
+ const fileCache = ctx.fileCache || null;
169
+ let files;
170
+ let skippedThirdParty = 0;
171
+
172
+ if (fileCache && Array.isArray(fileCache)) {
173
+ // Bootstrap 场景: allFiles 已在内存
174
+ files = fileCache.filter(f => {
175
+ const p = f.relativePath || f.path || '';
176
+ if (THIRD_PARTY_RE.test(p)) { skippedThirdParty++; return false; }
177
+ if (extFilter && !extFilter.test(p)) return false;
178
+ if (!SOURCE_EXT_RE.test(p)) return false;
179
+ return true;
180
+ });
181
+ } else {
182
+ // Dashboard / SignalCollector 场景: 从磁盘递归读取
183
+ files = [];
184
+ const MAX_FILE_SIZE = 512 * 1024; // 512KB — 跳过超大文件
185
+ const walk = (dir, relBase = '') => {
186
+ try {
187
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
188
+ for (const entry of entries) {
189
+ const relPath = relBase ? `${relBase}/${entry.name}` : entry.name;
190
+ const fullPath = path.join(dir, entry.name);
191
+ // 支持 symlink: 解析为目录或文件
192
+ const isDir = entry.isDirectory() || (entry.isSymbolicLink() && (() => { try { return fs.statSync(fullPath).isDirectory(); } catch { return false; } })());
193
+ const isFile = entry.isFile() || (entry.isSymbolicLink() && (() => { try { return fs.statSync(fullPath).isFile(); } catch { return false; } })());
194
+ if (isDir) {
195
+ // 跳过隐藏目录和常见无关目录
196
+ if (entry.name.startsWith('.') || entry.name === 'node_modules' || entry.name === 'build') continue;
197
+ if (THIRD_PARTY_RE.test(relPath + '/')) { skippedThirdParty++; continue; }
198
+ walk(fullPath, relPath);
199
+ } else if (isFile) {
200
+ if (THIRD_PARTY_RE.test(relPath)) { skippedThirdParty++; continue; }
201
+ if (!SOURCE_EXT_RE.test(entry.name)) continue;
202
+ if (extFilter && !extFilter.test(entry.name)) continue;
203
+ try {
204
+ const stat = fs.statSync(fullPath);
205
+ if (stat.size > MAX_FILE_SIZE) continue; // 跳过超大文件
206
+ const content = fs.readFileSync(fullPath, 'utf-8');
207
+ files.push({ relativePath: relPath, content, name: entry.name });
208
+ } catch { /* skip unreadable files */ }
209
+ }
210
+ }
211
+ } catch { /* skip inaccessible dirs */ }
212
+ };
213
+ walk(projectRoot);
214
+ }
215
+
216
+ // 搜索匹配
217
+ const matches = [];
218
+ let total = 0;
219
+
220
+ for (const f of files) {
221
+ if (!f.content) continue;
222
+ // 快速预过滤
223
+ searchRe.lastIndex = 0;
224
+ if (!searchRe.test(f.content)) continue;
225
+
226
+ const lines = f.content.split('\n');
227
+ searchRe.lastIndex = 0;
228
+
229
+ for (let i = 0; i < lines.length; i++) {
230
+ searchRe.lastIndex = 0;
231
+ if (!searchRe.test(lines[i])) continue;
232
+ total++;
233
+
234
+ if (matches.length < maxResults) {
235
+ const start = Math.max(0, i - contextLines);
236
+ const end = Math.min(lines.length - 1, i + contextLines);
237
+ const contextArr = [];
238
+ for (let j = start; j <= end; j++) {
239
+ contextArr.push(lines[j]);
240
+ }
241
+
242
+ matches.push({
243
+ file: f.relativePath || f.path || f.name,
244
+ line: i + 1,
245
+ code: lines[i],
246
+ context: contextArr.join('\n'),
247
+ score: _scoreSearchLine(lines[i]),
248
+ });
249
+ }
250
+ }
251
+ }
252
+
253
+ // 按 score 降序排列(实际使用行优先)
254
+ matches.sort((a, b) => b.score - a.score);
255
+
256
+ return {
257
+ matches,
258
+ total,
259
+ searchedFiles: files.length,
260
+ skippedThirdParty,
261
+ };
262
+ },
263
+ };
264
+
265
+ // ────────────────────────────────────────────────────────────
266
+ // 2. read_project_file — 读取项目文件
267
+ // ────────────────────────────────────────────────────────────
268
+ const readProjectFile = {
269
+ name: 'read_project_file',
270
+ description: '读取项目中指定文件的内容(部分或全部)。' +
271
+ '通常在 search_project_code 找到匹配后使用,获取更完整的上下文。',
272
+ parameters: {
273
+ type: 'object',
274
+ properties: {
275
+ filePath: { type: 'string', description: '相对于项目根目录的文件路径' },
276
+ startLine: { type: 'number', description: '起始行号(1-based),默认 1' },
277
+ endLine: { type: 'number', description: '结束行号(1-based),默认文件末尾' },
278
+ maxLines: { type: 'number', description: '最大返回行数,默认 200' },
279
+ },
280
+ required: ['filePath'],
281
+ },
282
+ handler: async (params, ctx) => {
283
+ // 兼容各种参数名变体 (ToolRegistry 层已做 snake→camel 归一化,
284
+ // 这里兜底处理漏网之鱼)
285
+ const filePath = params.filePath || params.path || params.file_path || params.filepath || params.file || params.filename;
286
+ const { startLine = 1, maxLines = 200 } = params;
287
+ const projectRoot = ctx.projectRoot || process.cwd();
288
+
289
+ if (!filePath || typeof filePath !== 'string') {
290
+ return { error: '参数错误: 请提供 filePath(相对于项目根目录的文件路径)' };
291
+ }
292
+
293
+ // 安全检查: 禁止路径遍历
294
+ const normalized = path.normalize(filePath);
295
+ if (normalized.startsWith('..') || path.isAbsolute(normalized)) {
296
+ return { error: 'Path traversal not allowed. Use relative paths within the project.' };
297
+ }
298
+
299
+ // 优先从内存缓存读取(bootstrap 场景)
300
+ const fileCache = ctx.fileCache || null;
301
+ let content = null;
302
+
303
+ if (fileCache && Array.isArray(fileCache)) {
304
+ const cached = fileCache.find(f =>
305
+ (f.relativePath || f.path || '') === filePath ||
306
+ (f.relativePath || f.path || '') === normalized
307
+ );
308
+ if (cached) content = cached.content;
309
+ }
310
+
311
+ // 降级: 从磁盘读取
312
+ if (content === null) {
313
+ const fullPath = path.resolve(projectRoot, normalized);
314
+ // 二次安全检查: 确保解析后仍在 projectRoot 内
315
+ if (!fullPath.startsWith(projectRoot)) {
316
+ return { error: 'Path traversal not allowed.' };
317
+ }
318
+ try {
319
+ content = fs.readFileSync(fullPath, 'utf-8');
320
+ } catch (err) {
321
+ return { error: `File not found or unreadable: ${err.message}` };
322
+ }
323
+ }
324
+
325
+ const allLines = content.split('\n');
326
+ const totalLines = allLines.length;
327
+ const start = Math.max(1, startLine);
328
+ let end = params.endLine || totalLines;
329
+ end = Math.min(end, totalLines);
330
+
331
+ // 限制返回行数
332
+ if (end - start + 1 > maxLines) {
333
+ end = start + maxLines - 1;
334
+ }
335
+
336
+ const selectedLines = allLines.slice(start - 1, end);
337
+
338
+ // 推断语言
339
+ const ext = path.extname(filePath).toLowerCase();
340
+ const langMap = { '.m': 'objectivec', '.mm': 'objectivec', '.h': 'objectivec', '.swift': 'swift', '.js': 'javascript', '.ts': 'typescript', '.py': 'python', '.java': 'java', '.kt': 'kotlin', '.go': 'go', '.rs': 'rust', '.rb': 'ruby' };
341
+ const language = langMap[ext] || 'unknown';
342
+
343
+ return {
344
+ filePath,
345
+ totalLines,
346
+ startLine: start,
347
+ endLine: end,
348
+ content: selectedLines.join('\n'),
349
+ language,
350
+ };
351
+ },
352
+ };
353
+
354
+ // ────────────────────────────────────────────────────────────
355
+ // 2b. list_project_structure — 项目目录结构 (v10 Agent-Pull)
356
+ // ────────────────────────────────────────────────────────────
357
+ const listProjectStructure = {
358
+ name: 'list_project_structure',
359
+ description: '列出项目目录结构和文件统计信息。不读取文件内容,只返回目录树和元数据。' +
360
+ '适用场景:了解项目整体布局、识别关键目录、规划探索路径。',
361
+ parameters: {
362
+ type: 'object',
363
+ properties: {
364
+ directory: { type: 'string', description: '相对于项目根目录的子目录路径,默认根目录' },
365
+ depth: { type: 'number', description: '目录展开深度,默认 3' },
366
+ includeStats: { type: 'boolean', description: '是否包含文件统计(语言分布、行数),默认 true' },
367
+ },
368
+ },
369
+ handler: async (params, ctx) => {
370
+ const directory = params.directory || '';
371
+ const depth = Math.min(params.depth ?? 3, 5); // 最深 5 层
372
+ const includeStats = params.includeStats !== false;
373
+ const projectRoot = ctx.projectRoot || process.cwd();
374
+
375
+ // 安全检查
376
+ const normalized = path.normalize(directory);
377
+ if (normalized.startsWith('..') || path.isAbsolute(normalized)) {
378
+ return { error: 'Path traversal not allowed. Use relative paths within the project.' };
379
+ }
380
+ const targetDir = directory ? path.resolve(projectRoot, normalized) : projectRoot;
381
+ if (!targetDir.startsWith(projectRoot)) {
382
+ return { error: 'Path traversal not allowed.' };
383
+ }
384
+
385
+ const treeLines = [];
386
+ const stats = { totalFiles: 0, totalDirs: 0, byLanguage: {}, totalLines: 0 };
387
+
388
+ const LANG_MAP = {
389
+ '.m': 'Objective-C', '.mm': 'Objective-C++', '.h': 'Header',
390
+ '.swift': 'Swift', '.js': 'JavaScript', '.ts': 'TypeScript',
391
+ '.jsx': 'JSX', '.tsx': 'TSX', '.py': 'Python', '.java': 'Java',
392
+ '.kt': 'Kotlin', '.go': 'Go', '.rs': 'Rust', '.rb': 'Ruby',
393
+ '.c': 'C', '.cpp': 'C++',
394
+ };
395
+
396
+ const walk = (dir, relBase, currentDepth, prefix) => {
397
+ if (currentDepth > depth) return;
398
+ let entries;
399
+ try { entries = fs.readdirSync(dir, { withFileTypes: true }); }
400
+ catch { return; }
401
+
402
+ // 排序: 目录在前,文件在后
403
+ entries.sort((a, b) => {
404
+ const aIsDir = a.isDirectory();
405
+ const bIsDir = b.isDirectory();
406
+ if (aIsDir !== bIsDir) return aIsDir ? -1 : 1;
407
+ return a.name.localeCompare(b.name);
408
+ });
409
+
410
+ // 过滤隐藏和三方
411
+ entries = entries.filter(e => {
412
+ if (e.name.startsWith('.')) return false;
413
+ const rel = relBase ? `${relBase}/${e.name}` : e.name;
414
+ if (THIRD_PARTY_RE.test(rel + '/')) return false;
415
+ return true;
416
+ });
417
+
418
+ for (let i = 0; i < entries.length; i++) {
419
+ const entry = entries[i];
420
+ const isLast = i === entries.length - 1;
421
+ const connector = isLast ? '└── ' : '├── ';
422
+ const childPrefix = prefix + (isLast ? ' ' : '│ ');
423
+ const rel = relBase ? `${relBase}/${entry.name}` : entry.name;
424
+ const fullPath = path.join(dir, entry.name);
425
+
426
+ if (entry.isDirectory()) {
427
+ // 计算子文件数
428
+ let childCount = 0;
429
+ try { childCount = fs.readdirSync(fullPath).length; } catch { /* skip */ }
430
+ treeLines.push(`${prefix}${connector}${entry.name}/ (${childCount})`);
431
+ stats.totalDirs++;
432
+ walk(fullPath, rel, currentDepth + 1, childPrefix);
433
+ } else if (entry.isFile()) {
434
+ const ext = path.extname(entry.name).toLowerCase();
435
+ let lineCount = 0;
436
+ let size = 0;
437
+ if (includeStats) {
438
+ try {
439
+ const st = fs.statSync(fullPath);
440
+ size = st.size;
441
+ if (SOURCE_EXT_RE.test(entry.name) && size < 512 * 1024) {
442
+ const content = fs.readFileSync(fullPath, 'utf-8');
443
+ lineCount = content.split('\n').length;
444
+ stats.totalLines += lineCount;
445
+ }
446
+ } catch { /* skip */ }
447
+ }
448
+ const lang = LANG_MAP[ext];
449
+ if (lang) {
450
+ stats.byLanguage[lang] = (stats.byLanguage[lang] || 0) + 1;
451
+ }
452
+ const sizeLabel = size > 1024 ? `${(size / 1024).toFixed(0)}KB` : `${size}B`;
453
+ const lineLabel = lineCount > 0 ? `, ${lineCount}L` : '';
454
+ treeLines.push(`${prefix}${connector}${entry.name} (${sizeLabel}${lineLabel})`);
455
+ stats.totalFiles++;
456
+ }
457
+ }
458
+ };
459
+
460
+ walk(targetDir, directory, 1, '');
461
+
462
+ return {
463
+ directory: directory || '.',
464
+ tree: treeLines.join('\n'),
465
+ stats: includeStats ? stats : undefined,
466
+ };
467
+ },
468
+ };
469
+
470
+ // ────────────────────────────────────────────────────────────
471
+ // 2c. get_file_summary — 文件摘要 (v10 Agent-Pull)
472
+ // ────────────────────────────────────────────────────────────
473
+
474
+ /** 语言相关的声明提取正则 */
475
+ const SUMMARY_EXTRACTORS = {
476
+ objectivec: {
477
+ imports: /^\s*(#import\s+.+|#include\s+.+|@import\s+\w+;)/gm,
478
+ declarations: /^\s*(@interface\s+\w+[\s:(].*|@protocol\s+\w+[\s<(].*|@implementation\s+\w+|typedef\s+(?:NS_ENUM|NS_OPTIONS)\s*\([^)]+\)\s*\{?)/gm,
479
+ methods: /^\s*[-+]\s*\([^)]+\)\s*[^;{]+/gm,
480
+ properties: /^\s*@property\s*\([^)]*\)\s*[^;]+;/gm,
481
+ },
482
+ swift: {
483
+ imports: /^\s*import\s+\w+/gm,
484
+ declarations: /^\s*(?:open|public|internal|fileprivate|private|final)?\s*(?:class|struct|enum|protocol|actor|extension)\s+\w+[^{]*/gm,
485
+ methods: /^\s*(?:open|public|internal|fileprivate|private|override|static|class)?\s*func\s+\w+[^{]*/gm,
486
+ properties: /^\s*(?:open|public|internal|fileprivate|private|static|class|lazy)?\s*(?:var|let)\s+\w+\s*:\s*[^={\n]+/gm,
487
+ },
488
+ javascript: {
489
+ imports: /^\s*(?:import\s+.+from\s+['"].+['"]|const\s+\{?\s*\w+.*\}?\s*=\s*require\s*\(.+\))/gm,
490
+ declarations: /^\s*(?:export\s+)?(?:default\s+)?(?:class|function|const|let|var)\s+\w+/gm,
491
+ methods: /^\s*(?:async\s+)?(?:static\s+)?(?:get\s+|set\s+)?(?:#?\w+)\s*\([^)]*\)\s*\{/gm,
492
+ },
493
+ typescript: {
494
+ imports: /^\s*import\s+.+from\s+['"].+['"]/gm,
495
+ declarations: /^\s*(?:export\s+)?(?:default\s+)?(?:class|interface|type|enum|function|const|let|var|abstract\s+class)\s+\w+/gm,
496
+ methods: /^\s*(?:async\s+)?(?:static\s+)?(?:public|private|protected)?\s*(?:get\s+|set\s+)?(?:#?\w+)\s*\([^)]*\)\s*[:{]/gm,
497
+ },
498
+ python: {
499
+ imports: /^\s*(?:import\s+\w+|from\s+\w+\s+import\s+.+)/gm,
500
+ declarations: /^\s*class\s+\w+[^:]*:/gm,
501
+ methods: /^\s*(?:async\s+)?def\s+\w+\s*\([^)]*\)/gm,
502
+ },
503
+ };
504
+ // Alias variants
505
+ SUMMARY_EXTRACTORS['objectivec++'] = SUMMARY_EXTRACTORS.objectivec;
506
+ SUMMARY_EXTRACTORS.jsx = SUMMARY_EXTRACTORS.javascript;
507
+ SUMMARY_EXTRACTORS.tsx = SUMMARY_EXTRACTORS.typescript;
508
+
509
+ const getFileSummary = {
510
+ name: 'get_file_summary',
511
+ description: '获取文件的结构摘要(导入、声明、方法签名),不包含实现代码。' +
512
+ '比 read_project_file 更轻量,适合快速了解文件角色和 API。',
513
+ parameters: {
514
+ type: 'object',
515
+ properties: {
516
+ filePath: { type: 'string', description: '相对于项目根目录的文件路径' },
517
+ },
518
+ required: ['filePath'],
519
+ },
520
+ handler: async (params, ctx) => {
521
+ const filePath = params.filePath || params.file_path || params.path || params.file;
522
+ const projectRoot = ctx.projectRoot || process.cwd();
523
+
524
+ if (!filePath || typeof filePath !== 'string') {
525
+ return { error: '参数错误: 请提供 filePath' };
526
+ }
527
+
528
+ // 安全检查
529
+ const normalized = path.normalize(filePath);
530
+ if (normalized.startsWith('..') || path.isAbsolute(normalized)) {
531
+ return { error: 'Path traversal not allowed.' };
532
+ }
533
+
534
+ // 优先从内存缓存读取
535
+ const fileCache = ctx.fileCache || null;
536
+ let content = null;
537
+
538
+ if (fileCache && Array.isArray(fileCache)) {
539
+ const cached = fileCache.find(f =>
540
+ (f.relativePath || f.path || '') === filePath ||
541
+ (f.relativePath || f.path || '') === normalized
542
+ );
543
+ if (cached) content = cached.content;
544
+ }
545
+
546
+ if (content === null) {
547
+ const fullPath = path.resolve(projectRoot, normalized);
548
+ if (!fullPath.startsWith(projectRoot)) {
549
+ return { error: 'Path traversal not allowed.' };
550
+ }
551
+ try {
552
+ content = fs.readFileSync(fullPath, 'utf-8');
553
+ } catch (err) {
554
+ return { error: `File not found or unreadable: ${err.message}` };
555
+ }
556
+ }
557
+
558
+ // 推断语言
559
+ const ext = path.extname(filePath).toLowerCase();
560
+ const langMap = { '.m': 'objectivec', '.mm': 'objectivec++', '.h': 'objectivec', '.swift': 'swift', '.js': 'javascript', '.ts': 'typescript', '.jsx': 'jsx', '.tsx': 'tsx', '.py': 'python', '.java': 'unknown', '.kt': 'unknown', '.go': 'unknown', '.rs': 'unknown', '.rb': 'unknown' };
561
+ const language = langMap[ext] || 'unknown';
562
+ const extractor = SUMMARY_EXTRACTORS[language];
563
+
564
+ const result = {
565
+ filePath,
566
+ language,
567
+ lineCount: content.split('\n').length,
568
+ imports: [],
569
+ declarations: [],
570
+ methods: [],
571
+ properties: [],
572
+ };
573
+
574
+ if (!extractor) {
575
+ // 未知语言: 返回前 30 行作为概览
576
+ result.preview = content.split('\n').slice(0, 30).join('\n');
577
+ return result;
578
+ }
579
+
580
+ // 提取各类声明
581
+ const extract = (regex) => {
582
+ const matches = [];
583
+ let m;
584
+ regex.lastIndex = 0;
585
+ while ((m = regex.exec(content)) !== null) {
586
+ matches.push(m[0].trim());
587
+ }
588
+ return matches;
589
+ };
590
+
591
+ if (extractor.imports) result.imports = extract(extractor.imports);
592
+ if (extractor.declarations) result.declarations = extract(extractor.declarations);
593
+ if (extractor.methods) result.methods = extract(extractor.methods).slice(0, 50); // 限制数量
594
+ if (extractor.properties) result.properties = extract(extractor.properties).slice(0, 30);
595
+
596
+ return result;
597
+ },
598
+ };
599
+
77
600
  // ────────────────────────────────────────────────────────────
78
- // 1. search_recipes
601
+ // 2d. semantic_search_code — 语义搜索 (v10 Agent-Pull)
602
+ // ────────────────────────────────────────────────────────────
603
+ const semanticSearchCode = {
604
+ name: 'semantic_search_code',
605
+ description: '在知识库中进行语义搜索。使用自然语言描述你要查找的代码模式或概念,' +
606
+ '返回语义最相关的知识条目。比关键词搜索更适合模糊/概念性查询。' +
607
+ '示例: "网络请求的错误处理策略"、"线程安全的单例实现"',
608
+ parameters: {
609
+ type: 'object',
610
+ properties: {
611
+ query: { type: 'string', description: '自然语言搜索查询' },
612
+ topK: { type: 'number', description: '返回结果数量,默认 5' },
613
+ category: { type: 'string', description: '按分类过滤 (View/Service/Network/Model 等)' },
614
+ language: { type: 'string', description: '按语言过滤 (swift/objectivec 等)' },
615
+ },
616
+ required: ['query'],
617
+ },
618
+ handler: async (params, ctx) => {
619
+ const query = params.query || params.search || params.keyword;
620
+ const topK = Math.min(params.topK ?? 5, 20);
621
+ const { category, language } = params;
622
+
623
+ if (!query || typeof query !== 'string') {
624
+ return { error: '参数错误: 请提供 query (自然语言搜索查询)' };
625
+ }
626
+
627
+ // 尝试获取 SearchEngine
628
+ let searchEngine = null;
629
+ try {
630
+ searchEngine = ctx.container?.get('searchEngine');
631
+ } catch { /* not available */ }
632
+
633
+ if (!searchEngine) {
634
+ // 尝试获取 VectorStore 直接搜索
635
+ let vectorStore = null;
636
+ try {
637
+ vectorStore = ctx.container?.get('vectorStore');
638
+ } catch { /* not available */ }
639
+
640
+ if (!vectorStore) {
641
+ return {
642
+ error: '语义搜索不可用: SearchEngine 和 VectorStore 均未初始化。可使用 search_project_code 进行关键词搜索替代。',
643
+ fallbackTool: 'search_project_code',
644
+ };
645
+ }
646
+
647
+ // 直接使用 VectorStore — 需要 embedding
648
+ let aiProvider = null;
649
+ try {
650
+ aiProvider = ctx.container?.get('aiProvider');
651
+ } catch { /* not available */ }
652
+
653
+ if (!aiProvider || typeof aiProvider.generateEmbedding !== 'function') {
654
+ // 向量搜索需要 embedding,降级到关键词匹配
655
+ const filter = {};
656
+ if (category) filter.category = category;
657
+ if (language) filter.language = language;
658
+
659
+ const results = await vectorStore.hybridSearch([], query, { topK, filter });
660
+ return {
661
+ mode: 'keyword-fallback',
662
+ query,
663
+ message: 'AI Provider 不支持 embedding,已降级到关键词匹配',
664
+ results: results.map(r => ({
665
+ id: r.item.id,
666
+ content: (r.item.content || '').slice(0, 500),
667
+ score: Math.round(r.score * 100) / 100,
668
+ metadata: r.item.metadata || {},
669
+ })),
670
+ };
671
+ }
672
+
673
+ // 生成 embedding → 向量搜索
674
+ try {
675
+ const embedding = await aiProvider.generateEmbedding(query);
676
+ const filter = {};
677
+ if (category) filter.category = category;
678
+ if (language) filter.language = language;
679
+
680
+ const results = await vectorStore.hybridSearch(embedding, query, { topK, filter });
681
+ return {
682
+ mode: 'vector',
683
+ query,
684
+ results: results.map(r => ({
685
+ id: r.item.id,
686
+ content: (r.item.content || '').slice(0, 500),
687
+ score: Math.round(r.score * 100) / 100,
688
+ metadata: r.item.metadata || {},
689
+ })),
690
+ };
691
+ } catch (err) {
692
+ return { error: `向量搜索失败: ${err.message}`, fallbackTool: 'search_project_code' };
693
+ }
694
+ }
695
+
696
+ // 使用 SearchEngine (BM25 + 可选向量)
697
+ try {
698
+ const result = await searchEngine.search(query, {
699
+ mode: 'semantic',
700
+ limit: topK * 2,
701
+ groupByKind: true,
702
+ });
703
+
704
+ let items = result?.items || [];
705
+ const actualMode = result?.mode || 'bm25';
706
+
707
+ // 应用过滤
708
+ if (category) items = items.filter(i => (i.category || '').toLowerCase() === category.toLowerCase());
709
+ if (language) items = items.filter(i => (i.language || '').toLowerCase() === language.toLowerCase());
710
+ items = items.slice(0, topK);
711
+
712
+ return {
713
+ mode: actualMode,
714
+ query,
715
+ degraded: actualMode !== 'semantic',
716
+ totalResults: items.length,
717
+ results: items.map(item => ({
718
+ id: item.id,
719
+ title: item.title || '',
720
+ content: (item.content || item.description || '').slice(0, 500),
721
+ score: Math.round((item.score || 0) * 100) / 100,
722
+ knowledgeType: item.knowledgeType || item.kind || '',
723
+ category: item.category || '',
724
+ language: item.language || '',
725
+ })),
726
+ };
727
+ } catch (err) {
728
+ return { error: `搜索失败: ${err.message}`, fallbackTool: 'search_project_code' };
729
+ }
730
+ },
731
+ };
732
+
733
+ // ────────────────────────────────────────────────────────────
734
+ // 3. search_recipes
79
735
  // ────────────────────────────────────────────────────────────
80
736
  const searchRecipes = {
81
737
  name: 'search_recipes',
@@ -433,9 +1089,17 @@ const refineBootstrapCandidates = {
433
1089
  handler: async (params, ctx) => {
434
1090
  if (!ctx.aiProvider) return { error: 'AI provider not available' };
435
1091
  const candidateService = ctx.container.get('candidateService');
1092
+
1093
+ // 接入 BootstrapTaskManager 双通道推送 refine:* 事件到前端
1094
+ let onProgress = null;
1095
+ try {
1096
+ const taskManager = ctx.container.get('bootstrapTaskManager');
1097
+ onProgress = (eventName, data) => taskManager.emitProgress(eventName, data);
1098
+ } catch { /* optional: no realtime push */ }
1099
+
436
1100
  return candidateService.refineBootstrapCandidates(
437
1101
  ctx.aiProvider,
438
- { candidateIds: params.candidateIds, userPrompt: params.userPrompt, dryRun: params.dryRun },
1102
+ { candidateIds: params.candidateIds, userPrompt: params.userPrompt, dryRun: params.dryRun, onProgress },
439
1103
  { userId: 'agent' },
440
1104
  );
441
1105
  },
@@ -845,6 +1509,59 @@ Return ONLY valid JSON:
845
1509
  };
846
1510
 
847
1511
  // ────────────────────────────────────────────────────────────
1512
+ // ────────────────────────────────────────────────────────────
1513
+ // Bootstrap 维度展示分组 — 将 9 个细粒度维度合并为 4 个展示组
1514
+ // ────────────────────────────────────────────────────────────
1515
+
1516
+ const DIMENSION_DISPLAY_GROUP = {
1517
+ 'architecture': 'architecture', // → 架构与设计
1518
+ 'code-pattern': 'architecture', // → 架构与设计
1519
+ 'project-profile': 'architecture', // → 架构与设计
1520
+ 'best-practice': 'best-practice', // → 规范与实践
1521
+ 'code-standard': 'best-practice', // → 规范与实践
1522
+ 'event-and-data-flow': 'event-and-data-flow', // → 事件与数据流
1523
+ 'objc-deep-scan': 'objc-deep-scan', // → 深度扫描
1524
+ 'category-scan': 'objc-deep-scan', // → 深度扫描
1525
+ 'agent-guidelines': 'agent-guidelines', // skill-only
1526
+ };
1527
+
1528
+ // ────────────────────────────────────────────────────────────
1529
+ // Bootstrap 维度类型校验 — submit_candidate / submit_with_check 共用
1530
+ // 基于 dimensionMeta 类型标注系统,而非关键词模糊匹配
1531
+ // ────────────────────────────────────────────────────────────
1532
+
1533
+ /**
1534
+ * 基于维度元数据 (dimensionMeta) 检查提交是否合法
1535
+ * @param {{ id: string, outputType: 'candidate'|'skill'|'dual', allowedKnowledgeTypes: string[] }} dimensionMeta
1536
+ * @param {object} params - submit_candidate 的参数
1537
+ * @param {object} [logger]
1538
+ * @returns {{ status: string, reason: string } | null} 不合法返回 rejected,合法返回 null
1539
+ */
1540
+ function _checkDimensionType(dimensionMeta, params, logger) {
1541
+ // 1. Skill-only 维度不允许提交 Candidate
1542
+ if (dimensionMeta.outputType === 'skill') {
1543
+ logger?.info(`[submit_candidate] ✗ rejected — dimension "${dimensionMeta.id}" is skill-only, cannot submit candidates`);
1544
+ return {
1545
+ status: 'rejected',
1546
+ reason: `当前维度 "${dimensionMeta.id}" 的输出类型为 skill-only,不允许调用 submit_candidate。请只在最终回复中提供 dimensionDigest JSON。`,
1547
+ };
1548
+ }
1549
+
1550
+ // 2. knowledgeType 校验 — 必须是维度允许的类型之一
1551
+ const allowed = dimensionMeta.allowedKnowledgeTypes || [];
1552
+ if (allowed.length > 0 && params.knowledgeType) {
1553
+ if (!allowed.includes(params.knowledgeType)) {
1554
+ logger?.info(`[submit_candidate] ✗ rejected — knowledgeType "${params.knowledgeType}" not in allowed [${allowed}] for dimension "${dimensionMeta.id}"`);
1555
+ return {
1556
+ status: 'rejected',
1557
+ reason: `knowledgeType "${params.knowledgeType}" 不在维度 "${dimensionMeta.id}" 允许的类型列表中。允许的类型: ${allowed.join(', ')}`,
1558
+ };
1559
+ }
1560
+ }
1561
+
1562
+ return null;
1563
+ }
1564
+
848
1565
  // 16. submit_candidate
849
1566
  // ────────────────────────────────────────────────────────────
850
1567
  const submitCandidate = {
@@ -853,26 +1570,85 @@ const submitCandidate = {
853
1570
  parameters: {
854
1571
  type: 'object',
855
1572
  properties: {
856
- code: { type: 'string', description: '代码内容' },
857
- language: { type: 'string', description: '编程语言' },
858
- category: { type: 'string', description: '分类 (View/Service/Tool/Model)' },
859
- source: { type: 'string', description: '来源 (manual/agent/mcp),默认 agent' },
860
- reasoning: { type: 'object', description: '推理依据 { whyStandard, sources, confidence }' },
861
- metadata: { type: 'object', description: '元数据 { title, summary, ... }' },
1573
+ code: { type: 'string', description: '代码内容(项目特写风格 Markdown: 描述和代码交织)' },
1574
+ language: { type: 'string', description: '编程语言 (objectivec/swift/java/kotlin 等)' },
1575
+ category: { type: 'string', description: '分类 (View/Service/Tool/Model/Network/Storage/UI/Utility/Core)' },
1576
+ title: { type: 'string', description: '候选标题,如 "[Bootstrap] best-practice/单例模式"' },
1577
+ summary: { type: 'string', description: '≤80字精准摘要,引用真实类名和数字' },
1578
+ tags: { type: 'array', items: { type: 'string' }, description: '标签列表,如 ["bootstrap", "singleton"]' },
1579
+ knowledgeType: { type: 'string', description: '知识类型: best-practice / code-pattern / architecture / convention' },
1580
+ source: { type: 'string', description: '来源 (bootstrap/agent/mcp),默认 agent' },
1581
+ reasoning: { type: 'object', description: '推理依据 { whyStandard: string, sources: string[], confidence: number }' },
1582
+ metadata: { type: 'object', description: '其他元数据 (不常用,优先使用上面的顶层字段)' },
862
1583
  },
863
- required: ['code', 'language', 'category'],
1584
+ required: ['code', 'language', 'category', 'title'],
864
1585
  },
865
1586
  handler: async (params, ctx) => {
866
1587
  const candidateService = ctx.container.get('candidateService');
867
- // 统一走 createFromToolParams,与 MCP / Bootstrap 路径一致
1588
+
1589
+ // ── Bootstrap 维度类型校验 (基于 dimensionMeta 类型标注) ──
1590
+ const dimMeta = ctx._dimensionMeta;
1591
+ if (dimMeta && ctx.source === 'system') {
1592
+ const rejected = _checkDimensionType(dimMeta, params, ctx.logger);
1593
+ if (rejected) return rejected;
1594
+
1595
+ // 自动注入维度标签(确保可溯源)
1596
+ if (!params.tags) params.tags = [];
1597
+ if (!params.tags.includes(dimMeta.id)) params.tags.push(dimMeta.id);
1598
+ if (!params.tags.includes('bootstrap')) params.tags.push('bootstrap');
1599
+
1600
+ // 自动补充 knowledgeType(AI 未填时用维度默认值)
1601
+ if (!params.knowledgeType && dimMeta.allowedKnowledgeTypes?.length > 0) {
1602
+ params.knowledgeType = dimMeta.allowedKnowledgeTypes[0];
1603
+ }
1604
+
1605
+ // Bootstrap 模式: 将 category 覆盖为展示分组 ID,确保前端按合并后的分组展示
1606
+ // AI 可能填 "UI"/"Core" 等功能分类,但前端通过 BOOTSTRAP_DIM_LABELS 按展示分组分组
1607
+ params.category = DIMENSION_DISPLAY_GROUP[dimMeta.id] || dimMeta.id;
1608
+
1609
+ // ── CandidateGuardrail 质量验证 (Bootstrap 模式) ──
1610
+ const guardrail = new CandidateGuardrail(
1611
+ ctx._submittedTitles || new Set(),
1612
+ dimMeta,
1613
+ );
1614
+ const guardResult = guardrail.validate(params);
1615
+ if (!guardResult.valid) {
1616
+ ctx.logger?.info(`[submit_candidate] ✗ guardrail rejected: ${guardResult.error}`);
1617
+ return {
1618
+ status: 'rejected',
1619
+ error: guardResult.error,
1620
+ hint: '请根据错误信息调整内容后重新提交。候选的 code 字段必须是「项目特写」风格,包含代码片段和项目上下文。',
1621
+ };
1622
+ }
1623
+ }
1624
+
1625
+ // 将所有顶层字段展开到 item — LLM 可能把 title/summary/tags 等
1626
+ // 放在顶层而非 metadata 中(production prompt 指引)
1627
+ const { code, language, category, source, reasoning, metadata, ...rest } = params;
1628
+
1629
+ // 防御性修复: AI 可能提交 reasoning.sources = [] 空数组,自动从 filePaths 补充
1630
+ const finalReasoning = reasoning || { whyStandard: 'Submitted via ChatAgent', sources: ['agent'], confidence: 0.7 };
1631
+ if (Array.isArray(finalReasoning.sources) && finalReasoning.sources.length === 0) {
1632
+ const fallbackSources = params.filePaths || rest.filePaths;
1633
+ if (Array.isArray(fallbackSources) && fallbackSources.length > 0) {
1634
+ finalReasoning.sources = fallbackSources;
1635
+ } else {
1636
+ finalReasoning.sources = ['agent'];
1637
+ }
1638
+ }
1639
+
868
1640
  const item = {
869
- code: params.code,
870
- language: params.language,
871
- category: params.category,
872
- ...params.metadata, // 展开 metadata 到扁平字段
873
- reasoning: params.reasoning || { whyStandard: 'Submitted via ChatAgent', sources: ['agent'], confidence: 0.7 },
1641
+ code,
1642
+ language,
1643
+ category,
1644
+ ...rest, // 顶层扩展字段 (title, summary, knowledgeType, tags 等)
1645
+ ...metadata, // metadata 对象 (如有)
1646
+ reasoning: finalReasoning,
874
1647
  };
875
- return candidateService.createFromToolParams(item, params.source || 'agent', {}, { userId: 'agent' });
1648
+ // Bootstrap 模式额外注入 targetName 前端优先按 meta.targetName 分组
1649
+ const displayGroup = dimMeta ? (DIMENSION_DISPLAY_GROUP[dimMeta.id] || dimMeta.id) : null;
1650
+ const extraMeta = (dimMeta && ctx.source === 'system') ? { targetName: displayGroup } : {};
1651
+ return candidateService.createFromToolParams(item, source || 'agent', extraMeta, { userId: 'agent' });
876
1652
  },
877
1653
  };
878
1654
 
@@ -1167,7 +1943,52 @@ const loadSkill = {
1167
1943
  };
1168
1944
 
1169
1945
  // ────────────────────────────────────────────────────────────
1170
- // 33. bootstrap_knowledge冷启动知识库初始化
1946
+ // 33. create_skill创建项目级 Skill
1947
+ // ────────────────────────────────────────────────────────────
1948
+ const createSkillTool = {
1949
+ name: 'create_skill',
1950
+ description: '创建项目级 Skill 文档,写入 AutoSnippet/skills/<name>/SKILL.md。Skill 是 Agent 的领域知识增强文档。创建后自动更新编辑器索引。',
1951
+ parameters: {
1952
+ type: 'object',
1953
+ properties: {
1954
+ name: { type: 'string', description: 'Skill 名称(kebab-case,如 my-auth-guide),3-64 字符' },
1955
+ description: { type: 'string', description: 'Skill 一句话描述(写入 frontmatter)' },
1956
+ content: { type: 'string', description: 'Skill 正文内容(Markdown 格式,不含 frontmatter)' },
1957
+ overwrite: { type: 'boolean', description: '如果同名 Skill 已存在,是否覆盖(默认 false)' },
1958
+ },
1959
+ required: ['name', 'description', 'content'],
1960
+ },
1961
+ handler: async (params, ctx) => {
1962
+ const { createSkill } = await import('../../external/mcp/handlers/skill.js');
1963
+ // 根据 ChatAgent 的 source 推断 createdBy
1964
+ const createdBy = ctx?.source === 'system' ? 'system-ai' : 'user-ai';
1965
+ const raw = createSkill(null, { ...params, createdBy });
1966
+ try { return JSON.parse(raw); } catch { return { success: false, error: raw }; }
1967
+ },
1968
+ };
1969
+
1970
+ // ────────────────────────────────────────────────────────────
1971
+ // 34. suggest_skills — 基于使用模式推荐 Skill 创建
1972
+ // ────────────────────────────────────────────────────────────
1973
+ const suggestSkills = {
1974
+ name: 'suggest_skills',
1975
+ description: '基于项目使用模式分析,推荐创建 Skill。分析 Guard 违规频率、Memory 偏好积累、Recipe 分布缺口、候选积压率。返回推荐列表(含 name/description/rationale/priority),可据此直接调用 create_skill 创建。',
1976
+ parameters: {
1977
+ type: 'object',
1978
+ properties: {},
1979
+ required: [],
1980
+ },
1981
+ handler: async (_params, ctx) => {
1982
+ const { SkillAdvisor } = await import('../../service/skills/SkillAdvisor.js');
1983
+ const database = ctx?.container?.get?.('database') || null;
1984
+ const projectRoot = ctx?.projectRoot || process.cwd();
1985
+ const advisor = new SkillAdvisor(projectRoot, { database });
1986
+ return advisor.suggest();
1987
+ },
1988
+ };
1989
+
1990
+ // ────────────────────────────────────────────────────────────
1991
+ // 34. bootstrap_knowledge — 冷启动知识库初始化
1171
1992
  // ────────────────────────────────────────────────────────────
1172
1993
  const bootstrapKnowledgeTool = {
1173
1994
  name: 'bootstrap_knowledge',
@@ -1348,6 +2169,26 @@ const submitWithCheck = {
1348
2169
  const { code, language, category, title, summary, threshold = 0.7 } = params;
1349
2170
  const projectRoot = ctx.projectRoot;
1350
2171
 
2172
+ // ── Bootstrap 维度类型校验 (与 submit_candidate 共用逻辑) ──
2173
+ const dimMeta = ctx._dimensionMeta;
2174
+ if (dimMeta && ctx.source === 'system') {
2175
+ const rejected = _checkDimensionType(dimMeta, params, ctx.logger);
2176
+ if (rejected) return rejected;
2177
+
2178
+ // 自动注入维度标签
2179
+ if (!params.tags) params.tags = [];
2180
+ if (!params.tags.includes(dimMeta.id)) params.tags.push(dimMeta.id);
2181
+ if (!params.tags.includes('bootstrap')) params.tags.push('bootstrap');
2182
+
2183
+ // 自动补充 knowledgeType(AI 未填时用维度默认值)
2184
+ if (!params.knowledgeType && dimMeta.allowedKnowledgeTypes?.length > 0) {
2185
+ params.knowledgeType = dimMeta.allowedKnowledgeTypes[0];
2186
+ }
2187
+
2188
+ // 覆盖 category 为展示分组
2189
+ params.category = DIMENSION_DISPLAY_GROUP[dimMeta.id] || dimMeta.id;
2190
+ }
2191
+
1351
2192
  // Step 1: 查重
1352
2193
  const cand = { title: title || '', summary: summary || '', code };
1353
2194
  const similar = findSimilarRecipes(projectRoot, cand, { threshold: 0.5, topK: 5 });
@@ -1369,15 +2210,21 @@ const submitWithCheck = {
1369
2210
  // Step 2: 提交
1370
2211
  try {
1371
2212
  const candidateService = ctx.container.get('candidateService');
2213
+ const { code: _c, language: _l, category: _cat, title: _t, summary: _s, threshold: _th, source: paramSource, reasoning: userReasoning, metadata, ...rest } = params;
1372
2214
  const item = {
1373
2215
  code,
1374
2216
  language,
1375
2217
  category,
2218
+ ...rest, // 顶层扩展字段 (title, summary, knowledgeType, tags 等)
2219
+ ...metadata, // metadata 对象 (如有)
1376
2220
  title: title || '',
1377
- summary_cn: summary || '',
1378
- reasoning: { whyStandard: 'Submitted via submit_with_check', sources: ['agent'], confidence: 0.7 },
2221
+ summary: summary || '',
2222
+ reasoning: userReasoning || { whyStandard: 'Submitted via submit_with_check', sources: ['agent'], confidence: 0.7 },
1379
2223
  };
1380
- const created = await candidateService.createFromToolParams(item, 'agent', {}, { userId: 'agent' });
2224
+ // Bootstrap 模式注入展示分组 targetName
2225
+ const swcDisplayGroup = dimMeta ? (DIMENSION_DISPLAY_GROUP[dimMeta.id] || dimMeta.id) : null;
2226
+ const swcExtraMeta = (dimMeta && ctx.source === 'system') ? { targetName: swcDisplayGroup } : {};
2227
+ const created = await candidateService.createFromToolParams(item, paramSource || 'agent', swcExtraMeta, { userId: 'agent' });
1381
2228
 
1382
2229
  return {
1383
2230
  submitted: true,
@@ -1396,7 +2243,579 @@ const submitWithCheck = {
1396
2243
  },
1397
2244
  };
1398
2245
 
2246
+ // ═══════════════════════════════════════════════════════
2247
+ // 元工具: Lazy Tool Schema 按需加载
2248
+ // ═══════════════════════════════════════════════════════
2249
+
2250
+ /**
2251
+ * get_tool_details — 查询工具的完整参数 schema
2252
+ *
2253
+ * 与 Cline .clinerules 按需加载类似:
2254
+ * System Prompt 只包含工具名+一行描述,LLM 需要调用某个工具前
2255
+ * 先通过此元工具获取完整参数定义,避免 prompt 过长浪费 token。
2256
+ */
2257
+ const getToolDetails = {
2258
+ name: 'get_tool_details',
2259
+ description: '查询指定工具的完整参数 Schema。在调用不熟悉的工具之前,先用此工具获取参数详情。',
2260
+ parameters: {
2261
+ type: 'object',
2262
+ properties: {
2263
+ toolName: {
2264
+ type: 'string',
2265
+ description: '要查询的工具名称(snake_case)',
2266
+ },
2267
+ },
2268
+ required: ['toolName'],
2269
+ },
2270
+ handler: async ({ toolName }, context) => {
2271
+ const registry = context.container?.get('toolRegistry');
2272
+ if (!registry) return { error: 'ToolRegistry not available' };
2273
+
2274
+ const schemas = registry.getToolSchemas();
2275
+ const found = schemas.find(t => t.name === toolName);
2276
+ if (!found) {
2277
+ const allNames = schemas.map(t => t.name);
2278
+ return {
2279
+ error: `Tool "${toolName}" not found`,
2280
+ availableTools: allNames,
2281
+ };
2282
+ }
2283
+
2284
+ return {
2285
+ name: found.name,
2286
+ description: found.description,
2287
+ parameters: found.parameters,
2288
+ };
2289
+ },
2290
+ };
2291
+
2292
+ // ─── 元工具: 任务规划 ───────────────────────────────────
2293
+ const planTask = {
2294
+ name: 'plan_task',
2295
+ description: '分析当前任务并制定结构化执行计划。在开始复杂任务前调用此工具可提高执行效率和决策质量。输出将记录到日志供审计,但不会改变实际执行流程。',
2296
+ parameters: {
2297
+ type: 'object',
2298
+ properties: {
2299
+ steps: {
2300
+ type: 'array',
2301
+ description: '执行步骤列表',
2302
+ items: {
2303
+ type: 'object',
2304
+ properties: {
2305
+ id: { type: 'number', description: '步骤序号' },
2306
+ action: { type: 'string', description: '具体动作描述' },
2307
+ tool: { type: 'string', description: '计划使用的工具名' },
2308
+ depends_on: { type: 'array', items: { type: 'number' }, description: '依赖的步骤 ID' },
2309
+ },
2310
+ required: ['id', 'action'],
2311
+ },
2312
+ },
2313
+ strategy: {
2314
+ type: 'string',
2315
+ description: '执行策略说明(如: 先搜索补充示例再批量提交)',
2316
+ },
2317
+ estimated_iterations: {
2318
+ type: 'number',
2319
+ description: '预估需要的迭代轮数',
2320
+ },
2321
+ },
2322
+ required: ['steps', 'strategy'],
2323
+ },
2324
+ handler: async (params, context) => {
2325
+ const plan = {
2326
+ steps: params.steps || [],
2327
+ strategy: params.strategy || '',
2328
+ estimatedIterations: params.estimated_iterations || params.steps?.length || 1,
2329
+ };
2330
+ context.logger?.info('[plan_task] execution plan', plan);
2331
+ return {
2332
+ status: 'plan_recorded',
2333
+ stepCount: plan.steps.length,
2334
+ strategy: plan.strategy,
2335
+ message: `执行计划已记录 (${plan.steps.length} 步, 预估 ${plan.estimatedIterations} 轮迭代)。开始按计划执行。`,
2336
+ };
2337
+ },
2338
+ };
2339
+
2340
+ // ─── 元工具: 自我质量审查 ───────────────────────────────
2341
+ const reviewMyOutput = {
2342
+ name: 'review_my_output',
2343
+ description: '回查本次会话中已提交的候选,检查质量红线是否满足。包括: 项目特写风格、summary 泛化措辞、代码示例来源标注等。返回通过/问题列表。建议在提交完所有候选后调用一次进行自检。',
2344
+ parameters: {
2345
+ type: 'object',
2346
+ properties: {
2347
+ check_rules: {
2348
+ type: 'array',
2349
+ description: '要检查的质量规则(可选, 默认检查全部)',
2350
+ items: { type: 'string' },
2351
+ },
2352
+ },
2353
+ },
2354
+ handler: async (params, context) => {
2355
+ const submitted = (context._sessionToolCalls || []).filter(
2356
+ tc => tc.tool === 'submit_candidate' || tc.tool === 'submit_with_check',
2357
+ );
2358
+
2359
+ if (submitted.length === 0) {
2360
+ return { status: 'no_candidates', message: '本次会话尚未提交任何候选。' };
2361
+ }
2362
+
2363
+ const issues = [];
2364
+ const checked = [];
2365
+
2366
+ for (const tc of submitted) {
2367
+ const p = tc.params || {};
2368
+ const code = p.code || '';
2369
+ const title = p.title || '';
2370
+ const summary = p.summary || '';
2371
+ const candidateIssues = [];
2372
+
2373
+ // 检查 1: 项目特写后缀
2374
+ if (!title.includes('— 项目特写') && !code.includes('— 项目特写')) {
2375
+ candidateIssues.push('缺少 "— 项目特写" 后缀');
2376
+ }
2377
+
2378
+ // 检查 2: 项目特写融合叙事质量 — 必须同时包含代码和描述性文字
2379
+ const hasCodeBlock = /```[\s\S]*?```/.test(code);
2380
+ if (!hasCodeBlock) {
2381
+ candidateIssues.push('特写缺少代码示例,应包含基本用法代码');
2382
+ }
2383
+ // 去掉代码块后,剩余描述性文字应足够
2384
+ const proseLength = code.replace(/```[\s\S]*?```/g, '').replace(/[#>\-*`\n]/g, '').trim().length;
2385
+ if (proseLength < 50) {
2386
+ candidateIssues.push('特写缺少项目特点描述,应融合基本用法和项目特点');
2387
+ }
2388
+
2389
+ // 检查 3: summary 泛化措辞
2390
+ if (/本模块|该文件|这个类|该项目/.test(summary)) {
2391
+ candidateIssues.push('summary 使用了泛化措辞,应引用具体类名和数字');
2392
+ }
2393
+
2394
+ // 检查 4: summary 过短
2395
+ if (summary.length < 15) {
2396
+ candidateIssues.push(`summary 过短 (${summary.length} 字), 应≥15字并包含具体类名和数字`);
2397
+ }
2398
+
2399
+ // 检查 5: code 过短(可能是空壳)
2400
+ if (code.length < 200) {
2401
+ candidateIssues.push(`code 文档过短 (${code.length} 字), 可能缺少实质内容`);
2402
+ }
2403
+
2404
+ // 检查 6: 代码示例来源
2405
+ const hasSourceAnnotation = /\([^)]*\.\w+[^)]*:\d+\)|\([^)]*\.\w+[^)]*\)/.test(code);
2406
+ if (hasCodeBlock && !hasSourceAnnotation) {
2407
+ candidateIssues.push('代码示例可能缺少来源文件标注 (建议标注 "来源: FileName.m:行号")');
2408
+ }
2409
+
2410
+ if (candidateIssues.length > 0) {
2411
+ issues.push({ title, issues: candidateIssues });
2412
+ }
2413
+ checked.push({ title, passed: candidateIssues.length === 0, issueCount: candidateIssues.length });
2414
+ }
2415
+
2416
+ if (issues.length === 0) {
2417
+ return {
2418
+ status: 'all_passed',
2419
+ checkedCount: submitted.length,
2420
+ message: `✅ ${submitted.length} 条候选全部通过质量检查。`,
2421
+ };
2422
+ }
2423
+
2424
+ const issueLines = issues.flatMap(({ title, issues: iss }) =>
2425
+ iss.map(i => `• "${title}": ${i}`),
2426
+ );
2427
+
2428
+ return {
2429
+ status: 'issues_found',
2430
+ checkedCount: submitted.length,
2431
+ passedCount: submitted.length - issues.length,
2432
+ failedCount: issues.length,
2433
+ details: checked,
2434
+ message: `⚠️ ${issues.length}/${submitted.length} 条候选存在质量问题:\n${issueLines.join('\n')}\n\n请修正后重新提交。`,
2435
+ };
2436
+ },
2437
+ };
2438
+
2439
+ // ════════════════════════════════════════════════════════════
2440
+ // AST 结构化分析 (7) — v3.0 AI-First Bootstrap AST 工具
2441
+ // ════════════════════════════════════════════════════════════
2442
+
2443
+ /**
2444
+ * 辅助: 安全获取 ProjectGraph 实例
2445
+ * @param {object} ctx
2446
+ * @returns {import('../../core/ast/ProjectGraph.js').default|null}
2447
+ */
2448
+ function _getProjectGraph(ctx) {
2449
+ try {
2450
+ return ctx.container?.get('projectGraph') || null;
2451
+ } catch {
2452
+ return null;
2453
+ }
2454
+ }
2455
+
2456
+ // ────────────────────────────────────────────────────────────
2457
+ // 44. get_project_overview — 项目 AST 概览
2458
+ // ────────────────────────────────────────────────────────────
2459
+ const getProjectOverview = {
2460
+ name: 'get_project_overview',
2461
+ description: '获取项目的整体结构概览:文件统计、模块列表、入口点、类/协议/Category 数量。' +
2462
+ '适用场景:了解项目规模和架构布局,规划探索路径。',
2463
+ parameters: {
2464
+ type: 'object',
2465
+ properties: {},
2466
+ },
2467
+ handler: async (_params, ctx) => {
2468
+ const graph = _getProjectGraph(ctx);
2469
+ if (!graph) return 'AST 分析不可用 — ProjectGraph 未构建。请检查 tree-sitter 是否已安装。';
2470
+
2471
+ const o = graph.getOverview();
2472
+ const lines = [
2473
+ `📊 项目 AST 概览 (构建耗时 ${o.buildTimeMs}ms)`,
2474
+ ``,
2475
+ `文件: ${o.totalFiles} | 类: ${o.totalClasses} | 协议: ${o.totalProtocols} | Category: ${o.totalCategories} | 方法: ${o.totalMethods}`,
2476
+ ``,
2477
+ `── 模块 ──`,
2478
+ ];
2479
+ for (const mod of o.topLevelModules) {
2480
+ const count = o.classesPerModule[mod] || 0;
2481
+ lines.push(` ${mod}/ — ${count} 个类`);
2482
+ }
2483
+ if (o.entryPoints.length > 0) {
2484
+ lines.push(``, `── 入口点 ──`);
2485
+ for (const ep of o.entryPoints) {
2486
+ lines.push(` ${ep}`);
2487
+ }
2488
+ }
2489
+ return lines.join('\n');
2490
+ },
2491
+ };
2492
+
2493
+ // ────────────────────────────────────────────────────────────
2494
+ // 45. get_class_hierarchy — 类继承层级
2495
+ // ────────────────────────────────────────────────────────────
2496
+ const getClassHierarchy = {
2497
+ name: 'get_class_hierarchy',
2498
+ description: '查看指定类的继承链(向上到根类)和直接子类列表。' +
2499
+ '传入 className 查看指定类,不传则返回项目中所有根类及其子树。',
2500
+ parameters: {
2501
+ type: 'object',
2502
+ properties: {
2503
+ className: { type: 'string', description: '类名 (可选, 不填则返回完整层级)' },
2504
+ },
2505
+ },
2506
+ handler: async (params, ctx) => {
2507
+ const graph = _getProjectGraph(ctx);
2508
+ if (!graph) return 'AST 分析不可用 — ProjectGraph 未构建。';
2509
+
2510
+ const className = params.className || params.class_name;
2511
+ if (className) {
2512
+ const chain = graph.getInheritanceChain(className);
2513
+ const subs = graph.getSubclasses(className);
2514
+ if (chain.length === 0) return `未找到类 ${className}`;
2515
+
2516
+ const lines = [
2517
+ `🔗 ${className} 继承链:`,
2518
+ ` ${chain.join(' → ')}`,
2519
+ ];
2520
+ if (subs.length > 0) {
2521
+ lines.push(``, `直接子类 (${subs.length}):`);
2522
+ for (const s of subs) lines.push(` ├── ${s}`);
2523
+ }
2524
+ return lines.join('\n');
2525
+ }
2526
+
2527
+ // 全量: 找出所有根类 (没有父类或父类不在项目中的类)
2528
+ const allClasses = graph.getAllClassNames();
2529
+ const roots = allClasses.filter(c => {
2530
+ const chain = graph.getInheritanceChain(c);
2531
+ return chain.length <= 1 || !allClasses.includes(chain[1]);
2532
+ });
2533
+
2534
+ const lines = [`🌳 项目类层级 (${allClasses.length} 个类, ${roots.length} 棵树)`];
2535
+ for (const root of roots.slice(0, 30)) {
2536
+ const descendants = graph.getAllDescendants(root);
2537
+ lines.push(` ${root} (${descendants.length} 个后代)`);
2538
+ for (const d of descendants.slice(0, 5)) {
2539
+ lines.push(` └── ${d}`);
2540
+ }
2541
+ if (descendants.length > 5) lines.push(` ... 还有 ${descendants.length - 5} 个`);
2542
+ }
2543
+ if (roots.length > 30) lines.push(`... 还有 ${roots.length - 30} 棵树`);
2544
+ return lines.join('\n');
2545
+ },
2546
+ };
2547
+
2548
+ // ────────────────────────────────────────────────────────────
2549
+ // 46. get_class_info — 类详细信息
2550
+ // ────────────────────────────────────────────────────────────
2551
+ const getClassInfo = {
2552
+ name: 'get_class_info',
2553
+ description: '获取指定类的详细信息: 属性、方法签名、导入、继承关系、Category 扩展。',
2554
+ parameters: {
2555
+ type: 'object',
2556
+ properties: {
2557
+ className: { type: 'string', description: '类名 (必填)' },
2558
+ },
2559
+ required: ['className'],
2560
+ },
2561
+ handler: async (params, ctx) => {
2562
+ const graph = _getProjectGraph(ctx);
2563
+ if (!graph) return 'AST 分析不可用 — ProjectGraph 未构建。';
2564
+
2565
+ const className = params.className || params.class_name;
2566
+ const info = graph.getClassInfo(className);
2567
+ if (!info) return `未找到类 "${className}"。可以使用 get_project_overview 查看项目中的所有类。`;
2568
+
2569
+ const chain = graph.getInheritanceChain(className);
2570
+ const cats = graph.getCategoryExtensions(className);
2571
+ const subs = graph.getSubclasses(className);
2572
+
2573
+ const lines = [
2574
+ `📦 ${info.name}`,
2575
+ `文件: ${info.filePath}:${info.line}`,
2576
+ `继承: ${chain.join(' → ')}`,
2577
+ ];
2578
+
2579
+ if (info.protocols.length > 0) {
2580
+ lines.push(`遵循: <${info.protocols.join(', ')}>`);
2581
+ }
2582
+
2583
+ if (info.properties.length > 0) {
2584
+ lines.push(``, `── 属性 (${info.properties.length}) ──`);
2585
+ for (const p of info.properties) {
2586
+ const attrs = p.attributes.length > 0 ? ` (${p.attributes.join(', ')})` : '';
2587
+ lines.push(` ${p.name}: ${p.type}${attrs}`);
2588
+ }
2589
+ }
2590
+
2591
+ if (info.methods.length > 0) {
2592
+ lines.push(``, `── 方法 (${info.methods.length}) ──`);
2593
+ const classMethods = info.methods.filter(m => m.isClassMethod);
2594
+ const instanceMethods = info.methods.filter(m => !m.isClassMethod);
2595
+ for (const m of classMethods) {
2596
+ const cx = m.complexity > 3 ? ` [复杂度:${m.complexity}]` : '';
2597
+ lines.push(` + ${m.selector} → ${m.returnType}${cx}`);
2598
+ }
2599
+ for (const m of instanceMethods) {
2600
+ const cx = m.complexity > 3 ? ` [复杂度:${m.complexity}]` : '';
2601
+ lines.push(` - ${m.selector} → ${m.returnType}${cx}`);
2602
+ }
2603
+ }
2604
+
2605
+ if (cats.length > 0) {
2606
+ lines.push(``, `── Category 扩展 (${cats.length}) ──`);
2607
+ for (const cat of cats) {
2608
+ const methodNames = cat.methods.map(m => m.selector).join(', ');
2609
+ lines.push(` ${info.name}(${cat.categoryName}) — ${cat.filePath} — [${methodNames}]`);
2610
+ }
2611
+ }
2612
+
2613
+ if (subs.length > 0) {
2614
+ lines.push(``, `── 直接子类 (${subs.length}) ──`);
2615
+ for (const s of subs) lines.push(` ${s}`);
2616
+ }
2617
+
2618
+ return lines.join('\n');
2619
+ },
2620
+ };
2621
+
2622
+ // ────────────────────────────────────────────────────────────
2623
+ // 47. get_protocol_info — 协议详细信息
2624
+ // ────────────────────────────────────────────────────────────
2625
+ const getProtocolInfo = {
2626
+ name: 'get_protocol_info',
2627
+ description: '获取指定协议的定义(必选/可选方法)及所有遵循该协议的类。',
2628
+ parameters: {
2629
+ type: 'object',
2630
+ properties: {
2631
+ protocolName: { type: 'string', description: '协议名 (必填)' },
2632
+ },
2633
+ required: ['protocolName'],
2634
+ },
2635
+ handler: async (params, ctx) => {
2636
+ const graph = _getProjectGraph(ctx);
2637
+ if (!graph) return 'AST 分析不可用 — ProjectGraph 未构建。';
2638
+
2639
+ const protocolName = params.protocolName || params.protocol_name;
2640
+ const info = graph.getProtocolInfo(protocolName);
2641
+ if (!info) return `未找到协议 "${protocolName}"。可以使用 get_project_overview 查看项目中的所有协议。`;
2642
+
2643
+ const lines = [
2644
+ `📋 @protocol ${info.name}`,
2645
+ `文件: ${info.filePath}:${info.line}`,
2646
+ ];
2647
+
2648
+ if (info.inherits.length > 0) {
2649
+ lines.push(`继承: <${info.inherits.join(', ')}>`);
2650
+ }
2651
+
2652
+ if (info.requiredMethods.length > 0) {
2653
+ lines.push(``, `── @required (${info.requiredMethods.length}) ──`);
2654
+ for (const m of info.requiredMethods) {
2655
+ lines.push(` ${m.isClassMethod ? '+' : '-'} ${m.selector} → ${m.returnType}`);
2656
+ }
2657
+ }
2658
+
2659
+ if (info.optionalMethods.length > 0) {
2660
+ lines.push(``, `── @optional (${info.optionalMethods.length}) ──`);
2661
+ for (const m of info.optionalMethods) {
2662
+ lines.push(` ${m.isClassMethod ? '+' : '-'} ${m.selector} → ${m.returnType}`);
2663
+ }
2664
+ }
2665
+
2666
+ if (info.conformers.length > 0) {
2667
+ lines.push(``, `── 遵循者 (${info.conformers.length}) ──`);
2668
+ for (const c of info.conformers) lines.push(` ${c}`);
2669
+ } else {
2670
+ lines.push(``, `⚠️ 暂未发现遵循此协议的类`);
2671
+ }
2672
+
2673
+ return lines.join('\n');
2674
+ },
2675
+ };
2676
+
2677
+ // ────────────────────────────────────────────────────────────
2678
+ // 48. get_method_overrides — 方法覆写查询
2679
+ // ────────────────────────────────────────────────────────────
2680
+ const getMethodOverrides = {
2681
+ name: 'get_method_overrides',
2682
+ description: '查找覆写了指定方法的所有子类。适用于理解方法在继承树中的多态行为。',
2683
+ parameters: {
2684
+ type: 'object',
2685
+ properties: {
2686
+ className: { type: 'string', description: '定义该方法的基类名 (必填)' },
2687
+ methodName: { type: 'string', description: '方法名或 selector (必填)' },
2688
+ },
2689
+ required: ['className', 'methodName'],
2690
+ },
2691
+ handler: async (params, ctx) => {
2692
+ const graph = _getProjectGraph(ctx);
2693
+ if (!graph) return 'AST 分析不可用 — ProjectGraph 未构建。';
2694
+
2695
+ const className = params.className || params.class_name;
2696
+ const methodName = params.methodName || params.method_name;
2697
+ const overrides = graph.getMethodOverrides(className, methodName);
2698
+
2699
+ if (overrides.length === 0) {
2700
+ return `"${className}.${methodName}" 没有在任何子类中被覆写。`;
2701
+ }
2702
+
2703
+ const lines = [
2704
+ `🔀 ${className}.${methodName} 的覆写 (${overrides.length} 处):`,
2705
+ ];
2706
+ for (const o of overrides) {
2707
+ const cx = o.method.complexity > 3 ? ` [复杂度:${o.method.complexity}]` : '';
2708
+ lines.push(` ${o.className} — ${o.filePath}:${o.method.line}${cx}`);
2709
+ }
2710
+ return lines.join('\n');
2711
+ },
2712
+ };
2713
+
2714
+ // ────────────────────────────────────────────────────────────
2715
+ // 49. get_category_map — Category 扩展映射
2716
+ // ────────────────────────────────────────────────────────────
2717
+ const getCategoryMap = {
2718
+ name: 'get_category_map',
2719
+ description: '获取指定类或整个项目的 ObjC Category 扩展映射。Category 是 ObjC 的核心模式,了解它有助于发现功能划分。',
2720
+ parameters: {
2721
+ type: 'object',
2722
+ properties: {
2723
+ className: { type: 'string', description: '类名 — 可选, 不填则返回整个项目中有 Category 的类列表' },
2724
+ },
2725
+ },
2726
+ handler: async (params, ctx) => {
2727
+ const graph = _getProjectGraph(ctx);
2728
+ if (!graph) return 'AST 分析不可用 — ProjectGraph 未构建。';
2729
+
2730
+ const className = params.className || params.class_name;
2731
+ if (className) {
2732
+ const cats = graph.getCategoryExtensions(className);
2733
+ if (cats.length === 0) return `"${className}" 没有 Category 扩展。`;
2734
+
2735
+ const lines = [`📂 ${className} 的 Category 扩展 (${cats.length}):`];
2736
+ for (const cat of cats) {
2737
+ lines.push(` ${className}(${cat.categoryName}) — ${cat.filePath}:${cat.line}`);
2738
+ for (const m of cat.methods) {
2739
+ lines.push(` ${m.isClassMethod ? '+' : '-'} ${m.selector}`);
2740
+ }
2741
+ if (cat.protocols.length > 0) {
2742
+ lines.push(` 遵循: <${cat.protocols.join(', ')}>`);
2743
+ }
2744
+ }
2745
+ return lines.join('\n');
2746
+ }
2747
+
2748
+ // 全量概览
2749
+ const allClasses = graph.getAllClassNames();
2750
+ const withCats = allClasses
2751
+ .map(c => ({ name: c, cats: graph.getCategoryExtensions(c) }))
2752
+ .filter(x => x.cats.length > 0)
2753
+ .sort((a, b) => b.cats.length - a.cats.length);
2754
+
2755
+ if (withCats.length === 0) return '项目中没有发现 Category 扩展。';
2756
+
2757
+ const lines = [`📂 项目 Category 概览 (${withCats.length} 个类有 Category):`];
2758
+ for (const { name, cats } of withCats.slice(0, 30)) {
2759
+ const catNames = cats.map(c => c.categoryName).join(', ');
2760
+ lines.push(` ${name} — ${cats.length} 个: (${catNames})`);
2761
+ }
2762
+ if (withCats.length > 30) lines.push(`... 还有 ${withCats.length - 30} 个类`);
2763
+ return lines.join('\n');
2764
+ },
2765
+ };
2766
+
2767
+ // ────────────────────────────────────────────────────────────
2768
+ // 50. get_previous_analysis — 前序维度分析结果 (可选)
2769
+ // ────────────────────────────────────────────────────────────
2770
+ const getPreviousAnalysis = {
2771
+ name: 'get_previous_analysis',
2772
+ description: '获取前序维度的分析摘要。在 bootstrap 中,每个维度可能有前面维度的分析结果可用。' +
2773
+ '调用此工具可以获取之前维度产出的候选标题、设计决策等上下文,避免重复分析。' +
2774
+ '注意: 只有在你认为前序上下文对当前任务有帮助时才调用。',
2775
+ parameters: {
2776
+ type: 'object',
2777
+ properties: {},
2778
+ },
2779
+ handler: async (_params, ctx) => {
2780
+ // 从 ctx._dimensionMeta 读取前序分析
2781
+ const meta = ctx._dimensionMeta;
2782
+ if (!meta || !meta.previousAnalysis) {
2783
+ return '没有前序维度的分析结果可用。';
2784
+ }
2785
+
2786
+ const prev = meta.previousAnalysis;
2787
+ if (typeof prev === 'string') return prev;
2788
+
2789
+ // 格式化前序分析
2790
+ const lines = ['📋 前序维度分析摘要:'];
2791
+ if (Array.isArray(prev)) {
2792
+ for (const item of prev) {
2793
+ if (typeof item === 'string') {
2794
+ lines.push(` ${item}`);
2795
+ } else if (item.dimension && item.summary) {
2796
+ lines.push(``, `── ${item.dimension} ──`);
2797
+ lines.push(` ${item.summary}`);
2798
+ if (item.candidateTitles?.length > 0) {
2799
+ lines.push(` 已提交候选: ${item.candidateTitles.join(', ')}`);
2800
+ }
2801
+ }
2802
+ }
2803
+ } else if (typeof prev === 'object') {
2804
+ for (const [key, value] of Object.entries(prev)) {
2805
+ lines.push(` ${key}: ${typeof value === 'string' ? value : JSON.stringify(value)}`);
2806
+ }
2807
+ }
2808
+ return lines.join('\n');
2809
+ },
2810
+ };
2811
+
1399
2812
  export const ALL_TOOLS = [
2813
+ // 项目数据访问 (5) — 含 v10 Agent-Pull 工具
2814
+ searchProjectCode,
2815
+ readProjectFile,
2816
+ listProjectStructure,
2817
+ getFileSummary,
2818
+ semanticSearchCode,
1400
2819
  // 查询类 (8)
1401
2820
  searchRecipes,
1402
2821
  searchCandidates,
@@ -1436,13 +2855,27 @@ export const ALL_TOOLS = [
1436
2855
  graphImpactAnalysis,
1437
2856
  rebuildIndex,
1438
2857
  queryAuditLog,
1439
- // Skills & Bootstrap (2)
2858
+ // Skills & Bootstrap (4)
1440
2859
  loadSkill,
2860
+ createSkillTool,
2861
+ suggestSkills,
1441
2862
  bootstrapKnowledgeTool,
1442
2863
  // 组合工具 (3) — 减少 ReAct 轮次
1443
2864
  analyzeCode,
1444
2865
  knowledgeOverview,
1445
2866
  submitWithCheck,
2867
+ // 元工具 (3) — Agent 自主能力增强
2868
+ getToolDetails,
2869
+ planTask,
2870
+ reviewMyOutput,
2871
+ // AST 结构化分析 (7) — v3.0 AI-First Bootstrap
2872
+ getProjectOverview,
2873
+ getClassHierarchy,
2874
+ getClassInfo,
2875
+ getProtocolInfo,
2876
+ getMethodOverrides,
2877
+ getCategoryMap,
2878
+ getPreviousAnalysis,
1446
2879
  ];
1447
2880
 
1448
2881
  export default ALL_TOOLS;