@lorrylurui/code-intelligence-mcp 1.0.8 → 1.0.9
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/dist/indexer/extractMeta.js +95 -2
- package/dist/indexer/indexProject.js +107 -0
- package/dist/prompts/reusableCodeAdvisorPrompt.js +10 -2
- package/dist/repositories/symbolRepository.js +16 -0
- package/dist/server/createServer.js +3 -0
- package/dist/tools/incUsage.js +33 -0
- package/dist/tools/searchByStructure.js +2 -0
- package/dist/tools/searchSymbols.js +4 -0
- package/package.json +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 从 AST 抽取写入 `symbols.meta` 的结构化字段(props/params、hooks、返回值、类型成员等)。
|
|
3
3
|
*/
|
|
4
|
-
import { Node, } from 'ts-morph';
|
|
4
|
+
import { Node, SyntaxKind, } from 'ts-morph';
|
|
5
5
|
/**
|
|
6
6
|
* 将节点收窄为可调用的函数形态,便于统一抽取参数与函数体。
|
|
7
7
|
* @returns 若不是函数声明/表达式/箭头函数则为 `undefined`。
|
|
@@ -77,7 +77,7 @@ export function extractReturnTypeText(fn) {
|
|
|
77
77
|
return rt ? rt.getText() : null;
|
|
78
78
|
}
|
|
79
79
|
/**
|
|
80
|
-
* 汇总单个可调用代码块的元数据:`params`、可选 `hooks`、可选 `returnType`。
|
|
80
|
+
* 汇总单个可调用代码块的元数据:`params`、可选 `hooks`、可选 `returnType`、可选 `sideEffects`。
|
|
81
81
|
* @returns 扁平对象,由上层按 `component`/`util` 再映射为 `props` 或保留 `params`。
|
|
82
82
|
*/
|
|
83
83
|
export function extractFunctionMeta(fn) {
|
|
@@ -85,11 +85,13 @@ export function extractFunctionMeta(fn) {
|
|
|
85
85
|
const paramTypeFields = extractParamTypeFieldNames(fn);
|
|
86
86
|
const hooks = extractHooksFromBody(fn);
|
|
87
87
|
const returnType = extractReturnTypeText(fn);
|
|
88
|
+
const sideEffects = extractSideEffects(fn);
|
|
88
89
|
return {
|
|
89
90
|
params,
|
|
90
91
|
...(paramTypeFields.length ? { paramTypeFields } : {}),
|
|
91
92
|
...(hooks.length ? { hooks } : {}),
|
|
92
93
|
...(returnType ? { returnType } : {}),
|
|
94
|
+
...(sideEffects.length ? { sideEffects } : {}),
|
|
93
95
|
};
|
|
94
96
|
}
|
|
95
97
|
/**
|
|
@@ -116,3 +118,94 @@ export function extractInterfaceOrTypeMeta(node) {
|
|
|
116
118
|
}
|
|
117
119
|
return { kind: 'unknown' };
|
|
118
120
|
}
|
|
121
|
+
/**
|
|
122
|
+
* 静态分析函数体的副作用。
|
|
123
|
+
* @returns 副作用类型数组,去重排序后写入 `meta.sideEffects`。
|
|
124
|
+
*/
|
|
125
|
+
export function extractSideEffects(node) {
|
|
126
|
+
const effects = new Set();
|
|
127
|
+
// 1. 检测网络请求
|
|
128
|
+
node.forEachDescendant((n) => {
|
|
129
|
+
if (Node.isCallExpression(n)) {
|
|
130
|
+
const expr = n.getExpression();
|
|
131
|
+
const text = expr.getText().toLowerCase();
|
|
132
|
+
// fetch、axios、xhr、ajax、superagent 等
|
|
133
|
+
if (text === 'fetch' ||
|
|
134
|
+
text === 'axios' ||
|
|
135
|
+
text === 'xhr' ||
|
|
136
|
+
text === 'ajax' ||
|
|
137
|
+
text.startsWith('axios.') ||
|
|
138
|
+
text.includes('request')) {
|
|
139
|
+
effects.add('network');
|
|
140
|
+
}
|
|
141
|
+
// XMLHttpRequest
|
|
142
|
+
if (text.includes('xmlhttprequest')) {
|
|
143
|
+
effects.add('network');
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
// 2. 检测计时器
|
|
148
|
+
node.forEachDescendant((n) => {
|
|
149
|
+
if (Node.isCallExpression(n)) {
|
|
150
|
+
const text = n.getExpression().getText();
|
|
151
|
+
if (text === 'setTimeout' ||
|
|
152
|
+
text === 'setInterval' ||
|
|
153
|
+
text === 'requestAnimationFrame' ||
|
|
154
|
+
text === 'setImmediate') {
|
|
155
|
+
effects.add('timer');
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
// 3. 检测 DOM / 全局对象操作
|
|
160
|
+
node.forEachDescendant((n) => {
|
|
161
|
+
if (Node.isExpressionStatement(n)) {
|
|
162
|
+
const text = n.getText();
|
|
163
|
+
// document. / window. 开头的赋值或调用
|
|
164
|
+
if (/\bdocument\.\w+/.test(text) ||
|
|
165
|
+
/\bwindow\.\w+/.test(text) ||
|
|
166
|
+
/\bnavigator\.\w+/.test(text) ||
|
|
167
|
+
/\blocation\.\w+/.test(text)) {
|
|
168
|
+
// 区分读取和写入
|
|
169
|
+
if (/=/.test(text) && !text.includes('===') && !text.includes('==')) {
|
|
170
|
+
effects.add('dom');
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
// 4. 检测存储操作
|
|
176
|
+
node.forEachDescendant((n) => {
|
|
177
|
+
if (Node.isCallExpression(n)) {
|
|
178
|
+
const text = n.getExpression().getText();
|
|
179
|
+
if (text.includes('localStorage') ||
|
|
180
|
+
text.includes('sessionStorage') ||
|
|
181
|
+
text.includes('cookie')) {
|
|
182
|
+
effects.add('storage');
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
// 5. 检测入参修改(检测 mutation pattern)
|
|
187
|
+
// 如 props.items.push(...), obj.x = ...
|
|
188
|
+
const params = node.getKind() === SyntaxKind.FunctionDeclaration
|
|
189
|
+
? node.getParameters()
|
|
190
|
+
: node.getKind() === SyntaxKind.ArrowFunction
|
|
191
|
+
? node.getParameters()
|
|
192
|
+
: node.getKind() === SyntaxKind.FunctionExpression
|
|
193
|
+
? node.getParameters()
|
|
194
|
+
: [];
|
|
195
|
+
if (params.length > 0) {
|
|
196
|
+
const paramNames = new Set(params.map((p) => p.getName()));
|
|
197
|
+
node.forEachDescendant((n) => {
|
|
198
|
+
if (Node.isExpressionStatement(n)) {
|
|
199
|
+
const text = n.getText();
|
|
200
|
+
// 检测 param.x = ... 或 param.push/pop/splice 等
|
|
201
|
+
for (const param of paramNames) {
|
|
202
|
+
if (text.includes(`${param}.`) || text.startsWith(`${param} =`)) {
|
|
203
|
+
effects.add('mutation');
|
|
204
|
+
break;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
return [...effects].sort();
|
|
211
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 扫描源码目录,用 ts-morph 解析 TS/TSX,Babel 解析 JS/JSX,生成待写入 MySQL 的代码块行。
|
|
3
|
+
* 同时分析调用关系,填充 meta.callers / meta.callees。
|
|
3
4
|
*/
|
|
4
5
|
import fg from 'fast-glob';
|
|
5
6
|
import { join, resolve } from 'node:path';
|
|
@@ -159,6 +160,7 @@ const DEFAULT_IGNORE = [
|
|
|
159
160
|
];
|
|
160
161
|
/**
|
|
161
162
|
* 按 glob 收集文件,用 ts-morph 加载并遍历每个文件的导出,生成全部代码块行。
|
|
163
|
+
* 同时分析调用关系,填充 meta.callers / meta.callees。
|
|
162
164
|
* @returns 数组可交给 `upsertSymbols`;无匹配文件时返回空数组(不写库)。
|
|
163
165
|
*/
|
|
164
166
|
export async function indexProject(opts) {
|
|
@@ -181,6 +183,7 @@ export async function indexProject(opts) {
|
|
|
181
183
|
const tsFiles = files.filter(isTsFile);
|
|
182
184
|
const jsFiles = files.filter(isJsFile);
|
|
183
185
|
const out = [];
|
|
186
|
+
const symbolMap = new Map(); // name -> { name, path, exports }
|
|
184
187
|
// 处理 TS/TSX 文件(只有 tsconfig.json 存在时才处理)
|
|
185
188
|
const tsConfigPath = join(projectRoot, 'tsconfig.json');
|
|
186
189
|
const hasTsConfig = existsSync(tsConfigPath);
|
|
@@ -192,12 +195,19 @@ export async function indexProject(opts) {
|
|
|
192
195
|
});
|
|
193
196
|
project.addSourceFilesAtPaths(tsFiles);
|
|
194
197
|
for (const sf of project.getSourceFiles()) {
|
|
198
|
+
const relPath = getRelativePathForDisplay(projectRoot, sf.getFilePath());
|
|
195
199
|
const exported = sf.getExportedDeclarations();
|
|
196
200
|
for (const [exportName, decls] of exported) {
|
|
197
201
|
for (const decl of decls) {
|
|
198
202
|
const row = processDeclaration(exportName, decl, sf, projectRoot);
|
|
199
203
|
if (row) {
|
|
200
204
|
out.push(row);
|
|
205
|
+
// 建立符号映射
|
|
206
|
+
const key = `${row.name}|${row.path}`;
|
|
207
|
+
if (!symbolMap.has(key)) {
|
|
208
|
+
symbolMap.set(key, { name: row.name, path: row.path, exports: new Set() });
|
|
209
|
+
}
|
|
210
|
+
symbolMap.get(key).exports.add(exportName);
|
|
201
211
|
}
|
|
202
212
|
}
|
|
203
213
|
}
|
|
@@ -209,10 +219,107 @@ export async function indexProject(opts) {
|
|
|
209
219
|
const content = readFileSync(file, 'utf-8');
|
|
210
220
|
const rows = parseJsFile(file, content, projectRoot);
|
|
211
221
|
out.push(...rows);
|
|
222
|
+
// 建立符号映射
|
|
223
|
+
for (const row of rows) {
|
|
224
|
+
const key = `${row.name}|${row.path}`;
|
|
225
|
+
if (!symbolMap.has(key)) {
|
|
226
|
+
symbolMap.set(key, { name: row.name, path: row.path, exports: new Set() });
|
|
227
|
+
}
|
|
228
|
+
symbolMap.get(key).exports.add(row.name);
|
|
229
|
+
}
|
|
212
230
|
}
|
|
213
231
|
catch (e) {
|
|
214
232
|
console.error(`[indexProject] Failed to parse ${file}:`, e);
|
|
215
233
|
}
|
|
216
234
|
}
|
|
235
|
+
// 分析调用关系,填充 meta.callers / meta.callees
|
|
236
|
+
if (tsFiles.length > 0 && hasTsConfig) {
|
|
237
|
+
const project = new Project({
|
|
238
|
+
tsConfigFilePath: tsConfigPath,
|
|
239
|
+
skipAddingFilesFromTsConfig: true,
|
|
240
|
+
skipFileDependencyResolution: true,
|
|
241
|
+
});
|
|
242
|
+
project.addSourceFilesAtPaths(tsFiles);
|
|
243
|
+
analyzeRelations(project, symbolMap, projectRoot, out);
|
|
244
|
+
}
|
|
217
245
|
return out;
|
|
218
246
|
}
|
|
247
|
+
/**
|
|
248
|
+
* 分析调用关系,填充每个符号的 meta.callers 和 meta.callees
|
|
249
|
+
*/
|
|
250
|
+
function analyzeRelations(project, symbolMap, projectRoot, rows) {
|
|
251
|
+
// 构造快速查找:exportName -> [ {name, path} ]
|
|
252
|
+
const exportToSymbol = new Map();
|
|
253
|
+
for (const [key, value] of symbolMap) {
|
|
254
|
+
for (const exp of value.exports) {
|
|
255
|
+
const list = exportToSymbol.get(exp) || [];
|
|
256
|
+
list.push({ name: value.name, path: value.path });
|
|
257
|
+
exportToSymbol.set(exp, list);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
// 收集所有 callers 和 callees
|
|
261
|
+
const callersMap = new Map(); // key = "name|path" -> set of callers
|
|
262
|
+
const calleesMap = new Map(); // key = "name|path" -> set of callees
|
|
263
|
+
for (const sf of project.getSourceFiles()) {
|
|
264
|
+
const filePath = sf.getFilePath();
|
|
265
|
+
const relPath = getRelativePathForDisplay(projectRoot, filePath);
|
|
266
|
+
// 获取当前文件导出的符号
|
|
267
|
+
const exported = sf.getExportedDeclarations();
|
|
268
|
+
const fileExportNames = new Set();
|
|
269
|
+
for (const [name, decls] of exported) {
|
|
270
|
+
fileExportNames.add(name);
|
|
271
|
+
}
|
|
272
|
+
// 遍历 AST 查找调用
|
|
273
|
+
sf.forEachDescendant((node) => {
|
|
274
|
+
// 1. 函数调用
|
|
275
|
+
if (Node.isCallExpression(node)) {
|
|
276
|
+
const expr = node.getExpression();
|
|
277
|
+
const name = Node.isIdentifier(expr) ? expr.getText() : null;
|
|
278
|
+
if (name && exportToSymbol.has(name)) {
|
|
279
|
+
const targets = exportToSymbol.get(name);
|
|
280
|
+
// 当前文件是谁在调用
|
|
281
|
+
for (const [expName, decls] of exported) {
|
|
282
|
+
for (const decl of decls) {
|
|
283
|
+
const callerKey = `${expName}|${relPath}`;
|
|
284
|
+
for (const target of targets) {
|
|
285
|
+
// callees: 我调用了谁
|
|
286
|
+
const calleeSet = calleesMap.get(callerKey) || new Set();
|
|
287
|
+
calleeSet.add({ name: target.name, path: target.path });
|
|
288
|
+
calleesMap.set(callerKey, calleeSet);
|
|
289
|
+
// callers: 谁调用了我
|
|
290
|
+
const callerSet = callersMap.get(`${target.name}|${target.path}`) || new Set();
|
|
291
|
+
callerSet.add({ name: expName, path: relPath });
|
|
292
|
+
callersMap.set(`${target.name}|${target.path}`, callerSet);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
// 2. Import 导入
|
|
299
|
+
if (Node.isImportDeclaration(node)) {
|
|
300
|
+
const moduleSpec = node.getModuleSpecifier().getText();
|
|
301
|
+
// 简单处理:只处理从 ./ 或 ../ 开始的相对导入
|
|
302
|
+
if (moduleSpec.startsWith("'.") || moduleSpec.startsWith('".')) {
|
|
303
|
+
const importPath = moduleSpec.slice(1, -1); // 去掉引号
|
|
304
|
+
// 尝试匹配已索引的符号
|
|
305
|
+
// 这里简化处理,暂不展开
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
// 写入 rows 的 meta
|
|
311
|
+
for (const row of rows) {
|
|
312
|
+
const key = `${row.name}|${row.path}`;
|
|
313
|
+
const callers = callersMap.get(key);
|
|
314
|
+
const callees = calleesMap.get(key);
|
|
315
|
+
if (callers && callers.size > 0) {
|
|
316
|
+
row.meta = row.meta || {};
|
|
317
|
+
row.meta.callers = [...callers].slice(0, 20); // 限制数量
|
|
318
|
+
}
|
|
319
|
+
if (callees && callees.size > 0) {
|
|
320
|
+
row.meta = row.meta || {};
|
|
321
|
+
row.meta.callees = [...callees].slice(0, 20);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
console.error(`[analyzeRelations] processed ${rows.length} symbols`);
|
|
325
|
+
}
|
|
@@ -22,9 +22,11 @@ const REUSABLE_CODE_ADVISOR_MARKDOWN = `# 可复用代码推荐
|
|
|
22
22
|
|
|
23
23
|
按此结构输出(字段名可保留英文或改为中文小标题,二选一全文统一):
|
|
24
24
|
|
|
25
|
-
- **首选:**
|
|
25
|
+
- **首选:** <代码块名>
|
|
26
|
+
- **使用范围:** 展示 meta.callers(被哪些模块调用),用于评估稳定性和适用范围;若无调用记录则显示"新增"
|
|
27
|
+
- **副作用:** 展示 meta.sideEffects(network/timer/dom/storage/mutation),优先推荐无副作用的方案,标注有副作用的方案风险
|
|
26
28
|
- **理由:** 1~3 条要点
|
|
27
|
-
- **其他候选:**
|
|
29
|
+
- **其他候选:** 简要列出及取舍(同步标注副作用)
|
|
28
30
|
- **用法提示:** 结合用户场景的最小集成说明
|
|
29
31
|
|
|
30
32
|
## 约束
|
|
@@ -33,6 +35,12 @@ const REUSABLE_CODE_ADVISOR_MARKDOWN = `# 可复用代码推荐
|
|
|
33
35
|
- 若无合适代码块,明确说明,并给出最接近的选项及差距。
|
|
34
36
|
- 推理简洁,面向落地实现。
|
|
35
37
|
|
|
38
|
+
## 使用反馈
|
|
39
|
+
|
|
40
|
+
当最终采纳了某个推荐代码块后,必须调用 inc_usage 工具记录采纳行为:
|
|
41
|
+
“请调用 inc_usage({ symbolId: <选中的代码块 id> })”
|
|
42
|
+
其中 symbolId 从 search_symbols 或 search_by_structure 返回结果的 id 字段获取。这条记录会用于后续排序优化。
|
|
43
|
+
|
|
36
44
|
## 更多示例
|
|
37
45
|
|
|
38
46
|
与仓库内 \`.cursor/skills/reusable-code-advisor/examples.md\` 中的示例一致(在 Cursor 或本地打开该文件查看)。
|
|
@@ -167,6 +167,22 @@ export class SymbolRepository {
|
|
|
167
167
|
}
|
|
168
168
|
return mapRow(rows[0]);
|
|
169
169
|
}
|
|
170
|
+
/**
|
|
171
|
+
* 将指定代码块的 usage_count +1,用于用户采纳推荐后记录。
|
|
172
|
+
*/
|
|
173
|
+
async incUsage(symbolId) {
|
|
174
|
+
if (!this.pool) {
|
|
175
|
+
// 内存模式:找到并 +1
|
|
176
|
+
const idx = inMemorySymbols.findIndex((s) => s.id === symbolId);
|
|
177
|
+
if (idx >= 0) {
|
|
178
|
+
inMemorySymbols[idx].usageCount++;
|
|
179
|
+
return true;
|
|
180
|
+
}
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
const [result] = await this.pool.query(`UPDATE ${env.mysqlSymbolsTable} SET usage_count = usage_count + 1 WHERE id = ?`, [symbolId]);
|
|
184
|
+
return result.affectedRows > 0;
|
|
185
|
+
}
|
|
170
186
|
async searchByStructure(fields, opts) {
|
|
171
187
|
const normalized = fields.map((f) => f.trim()).filter(Boolean);
|
|
172
188
|
if (normalized.length === 0)
|
|
@@ -5,6 +5,7 @@ import { createSearchSymbolsTool } from "../tools/searchSymbols.js";
|
|
|
5
5
|
import { createGetSymbolDetailTool } from "../tools/getSymbolDetail.js";
|
|
6
6
|
import { createReindexTool } from "../tools/reindex.js";
|
|
7
7
|
import { createSearchByStructureTool } from "../tools/searchByStructure.js";
|
|
8
|
+
import { createIncUsageTool } from "../tools/incUsage.js";
|
|
8
9
|
export function createServer() {
|
|
9
10
|
const server = new McpServer({
|
|
10
11
|
name: "code-intelligence-mcp",
|
|
@@ -19,6 +20,8 @@ export function createServer() {
|
|
|
19
20
|
server.tool(structureTool.name, structureTool.description, structureTool.inputSchema, structureTool.handler);
|
|
20
21
|
const reindexTool = createReindexTool();
|
|
21
22
|
server.tool(reindexTool.name, reindexTool.description, reindexTool.inputSchema, reindexTool.handler);
|
|
23
|
+
const incUsageTool = createIncUsageTool(repository);
|
|
24
|
+
server.tool(incUsageTool.name, incUsageTool.description, incUsageTool.inputSchema, incUsageTool.handler);
|
|
22
25
|
registerReusableCodeAdvisorPrompt(server);
|
|
23
26
|
return server;
|
|
24
27
|
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export const incUsageInput = z.object({
|
|
3
|
+
/** 要增加使用计数的代码块 ID(从搜索结果中获取) */
|
|
4
|
+
symbolId: z.number().int().positive(),
|
|
5
|
+
});
|
|
6
|
+
export function createIncUsageTool(repository) {
|
|
7
|
+
return {
|
|
8
|
+
name: 'inc_usage',
|
|
9
|
+
description: '当开发者采纳了某个推荐代码块时,调用此工具记录。usage_count 会 +1,用于后续排序优化。',
|
|
10
|
+
inputSchema: incUsageInput.shape,
|
|
11
|
+
handler: async (input) => {
|
|
12
|
+
const success = await repository.incUsage(input.symbolId);
|
|
13
|
+
if (!success) {
|
|
14
|
+
return {
|
|
15
|
+
content: [
|
|
16
|
+
{
|
|
17
|
+
type: 'text',
|
|
18
|
+
text: JSON.stringify({ error: '未找到该代码块', symbolId: input.symbolId }, null, 2),
|
|
19
|
+
},
|
|
20
|
+
],
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
return {
|
|
24
|
+
content: [
|
|
25
|
+
{
|
|
26
|
+
type: 'text',
|
|
27
|
+
text: JSON.stringify({ ok: true, symbolId: input.symbolId, message: 'usage_count 已 +1' }, null, 2),
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
};
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
}
|
|
@@ -22,6 +22,7 @@ export function createSearchByStructureTool(repository) {
|
|
|
22
22
|
const query = input.fields.join(' ');
|
|
23
23
|
const resultRows = input.ranked
|
|
24
24
|
? rankSymbols(query, rows).map((item) => ({
|
|
25
|
+
id: item.symbol.id,
|
|
25
26
|
name: item.symbol.name,
|
|
26
27
|
type: item.symbol.type,
|
|
27
28
|
category: item.symbol.category,
|
|
@@ -34,6 +35,7 @@ export function createSearchByStructureTool(repository) {
|
|
|
34
35
|
meta: item.symbol.meta,
|
|
35
36
|
}))
|
|
36
37
|
: rows.map((r) => ({
|
|
38
|
+
id: r.id,
|
|
37
39
|
name: r.name,
|
|
38
40
|
type: r.type,
|
|
39
41
|
category: r.category,
|
|
@@ -22,6 +22,7 @@ export function createSearchSymbolsTool(repository) {
|
|
|
22
22
|
const simById = new Map(hits.map((h) => [h.symbol.id, h.similarity]));
|
|
23
23
|
const resultRows = input.ranked
|
|
24
24
|
? rankSemanticHits(hits).map((item) => ({
|
|
25
|
+
id: item.symbol.id,
|
|
25
26
|
name: item.symbol.name,
|
|
26
27
|
type: item.symbol.type,
|
|
27
28
|
path: item.symbol.path,
|
|
@@ -33,6 +34,7 @@ export function createSearchSymbolsTool(repository) {
|
|
|
33
34
|
semanticSimilarity: Number((simById.get(item.symbol.id) ?? 0).toFixed(4)),
|
|
34
35
|
}))
|
|
35
36
|
: hits.map((h) => ({
|
|
37
|
+
id: h.symbol.id,
|
|
36
38
|
name: h.symbol.name,
|
|
37
39
|
type: h.symbol.type,
|
|
38
40
|
path: h.symbol.path,
|
|
@@ -52,6 +54,7 @@ export function createSearchSymbolsTool(repository) {
|
|
|
52
54
|
const rows = await repository.search(input.query, input.type);
|
|
53
55
|
const resultRows = input.ranked
|
|
54
56
|
? rankSymbols(input.query, rows).map((item) => ({
|
|
57
|
+
id: item.symbol.id,
|
|
55
58
|
name: item.symbol.name,
|
|
56
59
|
type: item.symbol.type,
|
|
57
60
|
path: item.symbol.path,
|
|
@@ -62,6 +65,7 @@ export function createSearchSymbolsTool(repository) {
|
|
|
62
65
|
reasonDetail: item.reason,
|
|
63
66
|
}))
|
|
64
67
|
: rows.map((r) => ({
|
|
68
|
+
id: r.id,
|
|
65
69
|
name: r.name,
|
|
66
70
|
type: r.type,
|
|
67
71
|
path: r.path,
|