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.
- package/bin/cli.js +35 -0
- package/dashboard/dist/assets/{icons-Dtm0E6DS.js → icons-Cq4-iQhP.js} +152 -87
- package/dashboard/dist/assets/index-DBxH7pVn.css +1 -0
- package/dashboard/dist/assets/index-Dw2F6qAS.js +197 -0
- package/dashboard/dist/assets/{react-markdown-CWxUbOf4.js → react-markdown-BA6FB2NP.js} +1 -1
- package/dashboard/dist/assets/{syntax-highlighter-CJ2drQQb.js → syntax-highlighter-CVLHn9O5.js} +1 -1
- package/dashboard/dist/assets/{vendor-f83ah6cm.js → vendor-BotF760a.js} +61 -61
- package/dashboard/dist/index.html +6 -6
- package/lib/bootstrap.js +1 -1
- package/lib/cli/SetupService.js +33 -8
- package/lib/cli/UpgradeService.js +139 -2
- package/lib/core/ast/ProjectGraph.js +599 -0
- package/lib/core/gateway/Gateway.js +19 -4
- package/lib/core/gateway/GatewayActionRegistry.js +2 -2
- package/lib/domain/recipe/Recipe.js +3 -0
- package/lib/external/ai/AiProvider.js +117 -10
- package/lib/external/ai/providers/ClaudeProvider.js +197 -0
- package/lib/external/ai/providers/GoogleGeminiProvider.js +235 -1
- package/lib/external/ai/providers/OpenAiProvider.js +131 -0
- package/lib/external/mcp/McpServer.js +2 -1
- package/lib/external/mcp/handlers/bootstrap/pipeline/dimension-context.js +216 -0
- package/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +468 -0
- package/lib/external/mcp/handlers/bootstrap/pipeline/tier-scheduler.js +162 -0
- package/lib/external/mcp/handlers/bootstrap/skills.js +225 -0
- package/lib/external/mcp/handlers/bootstrap.js +151 -1634
- package/lib/external/mcp/handlers/browse.js +1 -1
- package/lib/external/mcp/handlers/candidate.js +1 -33
- package/lib/external/mcp/handlers/skill.js +126 -31
- package/lib/external/mcp/tools.js +25 -3
- package/lib/http/middleware/requestLogger.js +23 -4
- package/lib/http/routes/ai.js +3 -1
- package/lib/http/routes/auth.js +3 -2
- package/lib/http/routes/candidates.js +49 -25
- package/lib/http/routes/commands.js +0 -8
- package/lib/http/routes/guardRules.js +1 -16
- package/lib/http/routes/recipes.js +4 -17
- package/lib/http/routes/search.js +16 -22
- package/lib/http/routes/skills.js +40 -3
- package/lib/http/routes/snippets.js +0 -33
- package/lib/http/routes/spm.js +37 -63
- package/lib/http/utils/routeHelpers.js +31 -0
- package/lib/infrastructure/audit/AuditStore.js +18 -0
- package/lib/infrastructure/config/Paths.js +9 -0
- package/lib/infrastructure/logging/Logger.js +86 -3
- package/lib/infrastructure/realtime/RealtimeService.js +2 -5
- package/lib/infrastructure/vector/JsonVectorAdapter.js +24 -1
- package/lib/injection/ServiceContainer.js +62 -3
- package/lib/service/bootstrap/BootstrapTaskManager.js +400 -0
- package/lib/service/candidate/CandidateFileWriter.js +68 -27
- package/lib/service/candidate/CandidateService.js +156 -10
- package/lib/service/chat/AnalystAgent.js +216 -0
- package/lib/service/chat/CandidateGuardrail.js +134 -0
- package/lib/service/chat/ChatAgent.js +1272 -155
- package/lib/service/chat/ContextWindow.js +730 -0
- package/lib/service/chat/ConversationStore.js +377 -0
- package/lib/service/chat/HandoffProtocol.js +180 -0
- package/lib/service/chat/Memory.js +40 -10
- package/lib/service/chat/ProducerAgent.js +240 -0
- package/lib/service/chat/ToolRegistry.js +149 -5
- package/lib/service/chat/tools.js +1493 -60
- package/lib/service/recipe/RecipeFileWriter.js +12 -1
- package/lib/service/skills/EventAggregator.js +187 -0
- package/lib/service/skills/SignalCollector.js +549 -0
- package/lib/service/skills/SkillAdvisor.js +324 -0
- package/lib/service/skills/SkillHooks.js +13 -5
- package/lib/service/spm/SpmService.js +2 -2
- package/package.json +1 -1
- package/templates/copilot-instructions.md +20 -3
- package/templates/cursor-rules/autosnippet-conventions.mdc +21 -4
- package/templates/cursor-rules/autosnippet-skills.mdc +45 -0
- package/dashboard/dist/assets/index-B7VpZOCz.css +0 -1
- package/dashboard/dist/assets/index-D87IZTmZ.js +0 -187
|
@@ -1,69 +1,89 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* tools.js — ChatAgent 全部工具定义
|
|
3
3
|
*
|
|
4
|
-
*
|
|
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
|
-
* │
|
|
8
|
-
* │
|
|
9
|
-
* │
|
|
10
|
-
* │
|
|
11
|
-
* │
|
|
12
|
-
* │
|
|
13
|
-
* │
|
|
14
|
-
* │
|
|
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
|
-
* │
|
|
18
|
-
* │
|
|
19
|
-
* │
|
|
20
|
-
* │
|
|
21
|
-
* │
|
|
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
|
-
* │
|
|
25
|
-
* │
|
|
26
|
-
* │
|
|
31
|
+
* │ 15. guard_check_code Guard 规则代码检查 │
|
|
32
|
+
* │ 16. query_violations 查询 Guard 违规记录 │
|
|
33
|
+
* │ 17. generate_guard_rule AI 生成 Guard 规则 │
|
|
27
34
|
* └─────────────────────────────────────────────────────┘
|
|
28
35
|
* ┌─── 生命周期操作类 (7) ─────────────────────────────┐
|
|
29
|
-
* │
|
|
30
|
-
* │
|
|
31
|
-
* │
|
|
32
|
-
* │
|
|
33
|
-
* │
|
|
34
|
-
* │
|
|
35
|
-
* │
|
|
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
|
-
* │
|
|
39
|
-
* │
|
|
40
|
-
* │
|
|
45
|
+
* │ 25. quality_score Recipe 质量评分 │
|
|
46
|
+
* │ 26. validate_candidate 候选校验 │
|
|
47
|
+
* │ 27. get_feedback_stats 获取反馈统计 │
|
|
41
48
|
* └─────────────────────────────────────────────────────┘
|
|
42
49
|
* ┌─── 知识图谱类 (3) ─────────────────────────────────┐
|
|
43
|
-
* │
|
|
44
|
-
* │
|
|
45
|
-
* │
|
|
50
|
+
* │ 28. check_duplicate 候选查重 │
|
|
51
|
+
* │ 29. discover_relations 知识图谱关系发现 │
|
|
52
|
+
* │ 30. add_graph_edge 添加知识图谱关系 │
|
|
46
53
|
* └─────────────────────────────────────────────────────┘
|
|
47
54
|
* ┌─── 基础设施类 (3) ─────────────────────────────────┐
|
|
48
|
-
* │
|
|
49
|
-
* │
|
|
50
|
-
* │
|
|
55
|
+
* │ 31. graph_impact_analysis 影响范围分析 │
|
|
56
|
+
* │ 32. rebuild_index 向量索引重建 │
|
|
57
|
+
* │ 33. query_audit_log 审计日志查询 │
|
|
51
58
|
* └─────────────────────────────────────────────────────┘
|
|
52
|
-
* ┌─── Skills & Bootstrap (
|
|
53
|
-
* │
|
|
54
|
-
* │
|
|
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
|
-
* │
|
|
58
|
-
* │
|
|
59
|
-
* │
|
|
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
|
-
//
|
|
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:
|
|
857
|
-
language:
|
|
858
|
-
category:
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
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
|
-
|
|
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
|
|
870
|
-
language
|
|
871
|
-
category
|
|
872
|
-
...
|
|
873
|
-
|
|
1641
|
+
code,
|
|
1642
|
+
language,
|
|
1643
|
+
category,
|
|
1644
|
+
...rest, // 顶层扩展字段 (title, summary, knowledgeType, tags 等)
|
|
1645
|
+
...metadata, // metadata 对象 (如有)
|
|
1646
|
+
reasoning: finalReasoning,
|
|
874
1647
|
};
|
|
875
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
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;
|