@lorrylurui/code-intelligence-mcp 1.0.2 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -156,11 +156,11 @@ npm run index
156
156
 
157
157
  可选环境变量(见 `.env.example`):
158
158
 
159
- | 变量 | 含义 |
160
- | -------------- | --------------------------------------- |
161
- | `INDEX_ROOT` | 工程根目录,默认当前工作目录 |
162
- | `INDEX_GLOB` | 逗号分隔 glob,默认 `src/**/*.{ts,tsx}` |
163
- | `INDEX_IGNORE` | 额外忽略的 glob 片段(逗号分隔) |
159
+ | 变量 | 含义 |
160
+ | -------------- | ------------------------------------------------ |
161
+ | `INDEX_ROOT` | 工程根目录,默认当前工作目录 |
162
+ | `INDEX_GLOB` | 空格分隔 glob,默认 `src/**/*.{ts,tsx}` |
163
+ | `INDEX_IGNORE` | 额外忽略的 glob 片段(空格分隔) |
164
164
 
165
165
  **分类规则(首版启发式)**:`interface` / `type` → `type`;`.tsx` 且函数体含 JSX → `component`;路径或导出名含 `selector` → `selector`;其余导出函数 → `util`;`class` → `util`(可后续细化)。
166
166
 
@@ -2,9 +2,9 @@
2
2
  /**
3
3
  * Phase 2 CLI:扫描代码库并写入 MySQL `symbols`(需 `MYSQL_ENABLED=true`)。
4
4
  */
5
- import { resolve } from "node:path";
6
- import dotenv from "dotenv";
7
- import { runReindex } from "../services/reindex.js";
5
+ import { resolve } from 'node:path';
6
+ import dotenv from 'dotenv';
7
+ import { runReindex } from '../services/reindex.js';
8
8
  dotenv.config();
9
9
  /**
10
10
  * 入口:校验环境 → 连接池 → 按 `INDEX_*` 调用 `indexProject` → `upsertSymbols`。
@@ -14,30 +14,35 @@ dotenv.config();
14
14
  async function main() {
15
15
  const projectRoot = resolve(process.env.INDEX_ROOT ?? process.cwd());
16
16
  const globPatterns = process.env.INDEX_GLOB
17
- ? process.env.INDEX_GLOB.split(",").map((s) => s.trim())
17
+ ? process.env.INDEX_GLOB.split(/\s+/).map((s) => s.trim()).filter(Boolean)
18
18
  : undefined;
19
19
  const ignore = process.env.INDEX_IGNORE
20
- ? process.env.INDEX_IGNORE.split(",").map((s) => s.trim())
20
+ ? process.env.INDEX_IGNORE.split(',').map((s) => s.trim())
21
21
  : undefined;
22
22
  console.error(`[index] projectRoot=${projectRoot}`);
23
- const result = await runReindex({ projectRoot, globPatterns, ignore, dryRun: false });
23
+ const result = await runReindex({
24
+ projectRoot,
25
+ globPatterns,
26
+ ignore,
27
+ dryRun: false,
28
+ });
24
29
  console.error(`[index] extracted ${result.extractedCount} symbol(s)`);
25
30
  console.error(`[index] embeddings computed: ${result.embeddingsComputed}`);
26
- console.error("[index] upserted into MySQL");
31
+ console.error('[index] upserted into MySQL, success:', result.upserted);
27
32
  }
28
33
  main().catch((err) => {
29
- console.error("[index] failed:", err);
34
+ console.error('[index] failed:', err);
30
35
  const anyErr = err;
31
- if (anyErr.code === "ECONNREFUSED") {
32
- const host = process.env.MYSQL_HOST ?? "127.0.0.1";
33
- const port = process.env.MYSQL_PORT ?? "3306";
36
+ if (anyErr.code === 'ECONNREFUSED') {
37
+ const host = process.env.MYSQL_HOST ?? '127.0.0.1';
38
+ const port = process.env.MYSQL_PORT ?? '3306';
34
39
  console.error(`[index] 原因: 无法连接 ${host}:${port}(连接被拒绝)。请先在本机启动 MySQL/MariaDB,或把 .env 里的 MYSQL_HOST / MYSQL_PORT 改成实际地址。macOS 可用 brew services start mysql 等方式启动。`);
35
40
  }
36
- else if (anyErr.code === "ER_ACCESS_DENIED_ERROR") {
37
- console.error("[index] 原因: 用户名或密码错误,请检查 MYSQL_USER / MYSQL_PASSWORD。");
41
+ else if (anyErr.code === 'ER_ACCESS_DENIED_ERROR') {
42
+ console.error('[index] 原因: 用户名或密码错误,请检查 MYSQL_USER / MYSQL_PASSWORD。');
38
43
  }
39
- else if (anyErr.code === "ENOTFOUND" || anyErr.code === "ETIMEDOUT") {
40
- console.error("[index] 原因: 网络不可达或超时,请检查 MYSQL_HOST 是否可解析、防火墙与安全组。");
44
+ else if (anyErr.code === 'ENOTFOUND' || anyErr.code === 'ETIMEDOUT') {
45
+ console.error('[index] 原因: 网络不可达或超时,请检查 MYSQL_HOST 是否可解析、防火墙与安全组。');
41
46
  }
42
47
  process.exit(1);
43
48
  });
@@ -1,17 +1,57 @@
1
- import dotenv from "dotenv";
2
- dotenv.config();
3
- const requiredWhenEnabled = ["MYSQL_HOST", "MYSQL_USER", "MYSQL_DATABASE"];
1
+ import dotenv from 'dotenv';
2
+ import path from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
5
+ const projectRoot = path.resolve(__dirname, '../../');
6
+ // 解析命令行参数 --key=value 格式,注入到 process.env
7
+ for (const arg of process.argv) {
8
+ const match = arg.match(/^--([A-Z_][A-Z0-9_]*)=(.+)$/);
9
+ if (match) {
10
+ process.env[match[1]] = match[2];
11
+ }
12
+ }
13
+ // 加载本地 .env(外部传入的 env 已经在 process.env 中,override: false 不会覆盖它们)
14
+ dotenv.config({
15
+ path: path.resolve(projectRoot, '.env'),
16
+ override: false,
17
+ });
18
+ // 尝试从第三方项目目录加载 .env(INDEX_ROOT 或 cwd)
19
+ const clientProjectRoot = process.env.INDEX_ROOT || process.cwd();
20
+ console.error(`[Config] Loading .env from client project root: ${clientProjectRoot}`);
21
+ dotenv.config({
22
+ path: path.resolve(clientProjectRoot, '.env'),
23
+ override: true,
24
+ });
25
+ // 外部传入的 env 已在上一步保留,这里确保环境变量已正确设置
26
+ for (const arg of process.argv) {
27
+ const match = arg.match(/^--([A-Z_][A-Z0-9_]*)=(.+)$/);
28
+ if (match) {
29
+ process.env[match[1]] = match[2];
30
+ }
31
+ }
32
+ const requiredWhenEnabled = [
33
+ 'MYSQL_HOST',
34
+ 'MYSQL_USER',
35
+ 'MYSQL_DATABASE',
36
+ ];
37
+ console.error(`[Config] MYSQL_ENABLED: ${process.env.MYSQL_ENABLED},
38
+ MYSQL_HOST: ${process.env.MYSQL_HOST},
39
+ MYSQL_USER: ${process.env.MYSQL_USER},
40
+ MYSQL_DATABASE: ${process.env.MYSQL_DATABASE},
41
+ EMBEDDING_SERVICE_URL: ${process.env.EMBEDDING_SERVICE_URL},
42
+ MYSQL_SYMBOLS_TABLE: ${process.env.MYSQL_SYMBOLS_TABLE}
43
+ `);
4
44
  export const env = {
5
- mysqlEnabled: process.env.MYSQL_ENABLED === "true",
6
- mysqlHost: process.env.MYSQL_HOST ?? "127.0.0.1",
7
- mysqlPort: Number(process.env.MYSQL_PORT ?? "3306"),
8
- mysqlUser: process.env.MYSQL_USER ?? "root",
9
- mysqlPassword: process.env.MYSQL_PASSWORD ?? "",
10
- mysqlDatabase: process.env.MYSQL_DATABASE ?? "code_intelligence",
45
+ mysqlEnabled: process.env.MYSQL_ENABLED === 'true',
46
+ mysqlHost: process.env.MYSQL_HOST ?? '127.0.0.1',
47
+ mysqlPort: Number(process.env.MYSQL_PORT ?? '3306'),
48
+ mysqlUser: process.env.MYSQL_USER ?? 'root',
49
+ mysqlPassword: process.env.MYSQL_PASSWORD ?? '',
50
+ mysqlDatabase: process.env.MYSQL_DATABASE ?? 'code_intelligence',
11
51
  /** symbols 表名,可通过 MYSQL_SYMBOLS_TABLE 环境变量配置 */
12
- mysqlSymbolsTable: process.env.MYSQL_SYMBOLS_TABLE ?? "symbols",
52
+ mysqlSymbolsTable: process.env.MYSQL_SYMBOLS_TABLE ?? 'symbols',
13
53
  /** Phase 5:指向 Python FastAPI 嵌入服务根 URL,如 http://127.0.0.1:8765 */
14
- embeddingServiceUrl: (process.env.EMBEDDING_SERVICE_URL ?? "").trim()
54
+ embeddingServiceUrl: (process.env.EMBEDDING_SERVICE_URL ?? '').trim(),
15
55
  };
16
56
  export function validateEnv() {
17
57
  if (!env.mysqlEnabled) {
@@ -0,0 +1,296 @@
1
+ /**
2
+ * 使用 Babel 解析 JS/JSX 文件,提取导出声明以及无导出文件中的所有函数/类
3
+ */
4
+ import * as babelParser from '@babel/parser';
5
+ import * as bt from '@babel/types';
6
+ import { getRelativePathForDisplay, inferCategoryFromPath } from './heuristics.js';
7
+ /** 从 JS 文件内容解析导出的代码块 */
8
+ export function parseJsFile(filePath, content, projectRoot) {
9
+ const out = [];
10
+ const isJsx = filePath.endsWith('.jsx') || filePath.endsWith('.tsx');
11
+ let ast;
12
+ try {
13
+ ast = babelParser.parse(content, {
14
+ sourceType: 'module',
15
+ plugins: [
16
+ 'jsx',
17
+ 'typescript',
18
+ 'classPrivateMethods',
19
+ 'classPrivateProperties',
20
+ 'decorators-legacy',
21
+ 'doExpressions',
22
+ 'exportDefaultFrom',
23
+ 'functionBind',
24
+ 'logicalAssignment',
25
+ 'nullishCoalescingOperator',
26
+ 'objectRestSpread',
27
+ 'optionalChaining',
28
+ 'optionalCatchBinding',
29
+ ],
30
+ strictMode: false,
31
+ });
32
+ }
33
+ catch (e) {
34
+ console.error(`[babelParser] Failed to parse ${filePath}:`, e);
35
+ return [];
36
+ }
37
+ // 第一轮:处理所有导出声明
38
+ for (const stmt of ast.program.body) {
39
+ const rows = processStatement(stmt, filePath, isJsx, projectRoot);
40
+ out.push(...rows);
41
+ }
42
+ // 第二轮:如果没有任何导出,则扫描所有函数/类
43
+ if (out.length === 0) {
44
+ for (const stmt of ast.program.body) {
45
+ const rows = scanAllDeclarations(stmt, filePath, isJsx, projectRoot);
46
+ out.push(...rows);
47
+ }
48
+ }
49
+ return out;
50
+ }
51
+ /** 处理导出的声明 */
52
+ function processStatement(stmt, filePath, isJsx, projectRoot) {
53
+ const out = [];
54
+ // 处理命名导出: export function Foo() {}
55
+ if (bt.isExportNamedDeclaration(stmt)) {
56
+ const decl = stmt.declaration;
57
+ if (!decl)
58
+ return out;
59
+ if (bt.isFunctionDeclaration(decl)) {
60
+ const name = decl.id?.name;
61
+ if (name) {
62
+ out.push(createRowFromFunction(name, decl, filePath, projectRoot, isJsx));
63
+ }
64
+ }
65
+ else if (bt.isClassDeclaration(decl)) {
66
+ const name = decl.id?.name;
67
+ if (name) {
68
+ out.push(createRowFromClass(name, decl, filePath, projectRoot));
69
+ }
70
+ }
71
+ else if (bt.isVariableDeclaration(decl)) {
72
+ for (const declarator of decl.declarations) {
73
+ if (bt.isVariableDeclarator(declarator) && declarator.id && bt.isIdentifier(declarator.id)) {
74
+ const name = declarator.id.name;
75
+ const init = declarator.init;
76
+ if (name && (bt.isArrowFunctionExpression(init) || bt.isFunctionExpression(init))) {
77
+ const fnDecl = arrowToFunction(name, init);
78
+ out.push(createRowFromFunction(name, fnDecl, filePath, projectRoot, isJsx));
79
+ }
80
+ }
81
+ }
82
+ }
83
+ }
84
+ // 处理 module.exports = xxx
85
+ else if (bt.isExpressionStatement(stmt)) {
86
+ const expr = stmt.expression;
87
+ if (bt.isAssignmentExpression(expr) && bt.isMemberExpression(expr.left)) {
88
+ const left = expr.left;
89
+ // module.exports = xxx
90
+ if (bt.isIdentifier(left.object) && left.object.name === 'module' &&
91
+ bt.isIdentifier(left.property) && left.property.name === 'exports') {
92
+ const right = expr.right;
93
+ if (bt.isObjectExpression(right)) {
94
+ for (const prop of right.properties) {
95
+ if (bt.isObjectProperty(prop) && bt.isIdentifier(prop.key)) {
96
+ const name = prop.key.name;
97
+ const value = prop.value;
98
+ if (bt.isFunctionExpression(value) || bt.isArrowFunctionExpression(value)) {
99
+ const fnDecl = arrowToFunction(name, bt.isArrowFunctionExpression(value) ? value : value);
100
+ out.push(createRowFromFunction(name, fnDecl, filePath, projectRoot, isJsx));
101
+ }
102
+ }
103
+ }
104
+ }
105
+ else if (bt.isFunctionExpression(right)) {
106
+ const fnDecl = arrowToFunction('default', right);
107
+ out.push(createRowFromFunction('default', fnDecl, filePath, projectRoot, isJsx));
108
+ }
109
+ else if (bt.isArrowFunctionExpression(right)) {
110
+ const fnDecl = arrowToFunction('default', right);
111
+ out.push(createRowFromFunction('default', fnDecl, filePath, projectRoot, isJsx));
112
+ }
113
+ }
114
+ // exports.xxx = xxx
115
+ else if (bt.isIdentifier(left.object) && left.object.name === 'exports') {
116
+ const name = bt.isIdentifier(left.property) ? left.property.name : null;
117
+ if (name) {
118
+ const right = expr.right;
119
+ if (bt.isFunctionExpression(right) || bt.isArrowFunctionExpression(right)) {
120
+ const fnDecl = arrowToFunction(name, bt.isArrowFunctionExpression(right) ? right : right);
121
+ out.push(createRowFromFunction(name, fnDecl, filePath, projectRoot, isJsx));
122
+ }
123
+ }
124
+ }
125
+ }
126
+ }
127
+ // 处理默认导出
128
+ else if (bt.isExportDefaultDeclaration(stmt)) {
129
+ const decl = stmt.declaration;
130
+ if (bt.isFunctionDeclaration(decl)) {
131
+ const name = decl.id?.name || 'default';
132
+ out.push(createRowFromFunction(name, decl, filePath, projectRoot, isJsx));
133
+ }
134
+ else if (bt.isClassDeclaration(decl)) {
135
+ const name = decl.id?.name || 'default';
136
+ out.push(createRowFromClass(name, decl, filePath, projectRoot));
137
+ }
138
+ else if (bt.isArrowFunctionExpression(decl)) {
139
+ const fnDecl = arrowToFunction('default', decl);
140
+ out.push(createRowFromFunction('default', fnDecl, filePath, projectRoot, isJsx));
141
+ }
142
+ else if (bt.isFunctionExpression(decl)) {
143
+ const fnDecl = arrowToFunction('default', decl);
144
+ out.push(createRowFromFunction('default', fnDecl, filePath, projectRoot, isJsx));
145
+ }
146
+ }
147
+ return out;
148
+ }
149
+ /** 扫描无导出文件中的所有声明 */
150
+ function scanAllDeclarations(stmt, filePath, isJsx, projectRoot) {
151
+ const out = [];
152
+ // 函数声明: function foo() {}
153
+ if (bt.isFunctionDeclaration(stmt)) {
154
+ const name = stmt.id?.name;
155
+ if (name) {
156
+ out.push(createRowFromFunction(name, stmt, filePath, projectRoot, isJsx));
157
+ }
158
+ }
159
+ // 类声明: class Foo {}
160
+ else if (bt.isClassDeclaration(stmt)) {
161
+ const name = stmt.id?.name;
162
+ if (name) {
163
+ out.push(createRowFromClass(name, stmt, filePath, projectRoot));
164
+ }
165
+ }
166
+ // 变量声明: const foo = () => {}, const bar = function() {}
167
+ else if (bt.isVariableDeclaration(stmt)) {
168
+ for (const declarator of stmt.declarations) {
169
+ if (bt.isVariableDeclarator(declarator) && declarator.id && bt.isIdentifier(declarator.id)) {
170
+ const name = declarator.id.name;
171
+ const init = declarator.init;
172
+ if (name && (bt.isArrowFunctionExpression(init) || bt.isFunctionExpression(init))) {
173
+ const fnDecl = arrowToFunction(name, init);
174
+ out.push(createRowFromFunction(name, fnDecl, filePath, projectRoot, isJsx));
175
+ }
176
+ }
177
+ }
178
+ }
179
+ return out;
180
+ }
181
+ /** 将 ArrowFunctionExpression 或 FunctionExpression 转换为 FunctionDeclaration */
182
+ function arrowToFunction(name, arrow) {
183
+ const body = arrow.body;
184
+ const blockBody = bt.isBlockStatement(body)
185
+ ? body
186
+ : bt.blockStatement([bt.returnStatement(body)]);
187
+ return {
188
+ type: 'FunctionDeclaration',
189
+ id: { type: 'Identifier', name },
190
+ params: arrow.params,
191
+ body: blockBody,
192
+ generator: false,
193
+ async: arrow.async,
194
+ returnType: arrow.returnType,
195
+ typeParameters: arrow.typeParameters,
196
+ };
197
+ }
198
+ function createRowFromFunction(name, decl, filePath, projectRoot, isJsx) {
199
+ const relPath = getRelativePathForDisplay(projectRoot, filePath);
200
+ const category = inferCategoryFromPath(filePath);
201
+ // 检测是否有 JSX
202
+ const hasJsx = isJsx || containsJsx(decl);
203
+ // 判断类型:
204
+ // 1. 有 JSX = component
205
+ // 2. 名字包含 selector = selector
206
+ // 3. 大写开头(JSX 组件约定)= component
207
+ // 4. 其他 = util
208
+ const type = hasJsx
209
+ ? 'component'
210
+ : name.toLowerCase().includes('selector')
211
+ ? 'selector'
212
+ : isJsx && /^[A-Z]/.test(name)
213
+ ? 'component'
214
+ : 'util';
215
+ const params = decl.params
216
+ .filter((p) => bt.isIdentifier(p))
217
+ .map((p) => p.name);
218
+ return {
219
+ name,
220
+ type,
221
+ category,
222
+ path: relPath,
223
+ description: null,
224
+ content: `function ${decl.id?.name || 'anonymous'}(${params.join(', ')}) { ... }`,
225
+ meta: {
226
+ params,
227
+ returnType: getReturnType(decl),
228
+ },
229
+ };
230
+ }
231
+ function createRowFromClass(name, _decl, filePath, projectRoot) {
232
+ const relPath = getRelativePathForDisplay(projectRoot, filePath);
233
+ const category = inferCategoryFromPath(filePath);
234
+ // 大写开头的类视为组件
235
+ const type = /^[A-Z]/.test(name) ? 'component' : 'util';
236
+ return {
237
+ name,
238
+ type,
239
+ category,
240
+ path: relPath,
241
+ description: null,
242
+ content: null,
243
+ meta: { kind: 'class' },
244
+ };
245
+ }
246
+ /** 简单检测是否包含 JSX */
247
+ function containsJsx(node) {
248
+ const jsxTags = ['JSXElement', 'JSXFragment'];
249
+ let found = false;
250
+ const visit = (n) => {
251
+ if (jsxTags.includes(n.type)) {
252
+ found = true;
253
+ return;
254
+ }
255
+ // 只遍历常见的包含子节点的属性
256
+ const keys = ['body', 'declarations', 'arguments', 'callee', 'init', 'left', 'right', 'consequent', 'alternate'];
257
+ for (const key of keys) {
258
+ const val = n[key];
259
+ if (Array.isArray(val)) {
260
+ for (const v of val) {
261
+ if (v && typeof v === 'object' && 'type' in v) {
262
+ visit(v);
263
+ }
264
+ }
265
+ }
266
+ else if (val && typeof val === 'object' && 'type' in val) {
267
+ visit(val);
268
+ }
269
+ }
270
+ };
271
+ visit(node);
272
+ return found;
273
+ }
274
+ function getReturnType(fn) {
275
+ if (!fn.returnType)
276
+ return undefined;
277
+ const rt = fn.returnType;
278
+ if (bt.isTSTypeAnnotation(rt)) {
279
+ const inner = rt.typeAnnotation;
280
+ if (bt.isTSStringKeyword(inner))
281
+ return 'string';
282
+ if (bt.isTSNumberKeyword(inner))
283
+ return 'number';
284
+ if (bt.isTSBooleanKeyword(inner))
285
+ return 'boolean';
286
+ if (bt.isTSVoidKeyword(inner))
287
+ return 'void';
288
+ if (bt.isTSAnyKeyword(inner))
289
+ return 'any';
290
+ if (bt.isTSUnknownKeyword(inner))
291
+ return 'unknown';
292
+ if (bt.isTSNullKeyword(inner))
293
+ return 'null';
294
+ }
295
+ return undefined;
296
+ }
@@ -1,11 +1,20 @@
1
1
  /**
2
- * 扫描源码目录,用 ts-morph 解析导出并生成待写入 MySQL 的代码块行。
2
+ * 扫描源码目录,用 ts-morph 解析 TS/TSX,Babel 解析 JS/JSX,生成待写入 MySQL 的代码块行。
3
3
  */
4
4
  import fg from 'fast-glob';
5
5
  import { join, resolve } from 'node:path';
6
6
  import { Node, Project } from 'ts-morph';
7
+ import { readFileSync } from 'node:fs';
7
8
  import { extractInterfaceOrTypeMeta, extractMetaFromCallable, } from './extractMeta.js';
8
9
  import { getLeadingDocDescription, getRelativePathForDisplay, hasJsxInNode, inferCategoryFromPath, isSelectorLike, isTsxFile, snippetForNode, } from './heuristics.js';
10
+ import { parseJsFile } from './babelParser.js';
11
+ /** 判断文件类型 */
12
+ function isJsFile(filePath) {
13
+ return filePath.endsWith('.js') || filePath.endsWith('.jsx');
14
+ }
15
+ function isTsFile(filePath) {
16
+ return filePath.endsWith('.ts') || filePath.endsWith('.tsx');
17
+ }
9
18
  /**
10
19
  * 将 `extractFunctionMeta` 的 `params` 转为组件用的 `props` 名,或保留为 `params`。
11
20
  * @returns 供 `IndexedSymbolRow.meta` 序列化入库。
@@ -147,32 +156,51 @@ export async function indexProject(opts) {
147
156
  const projectRoot = resolve(opts.projectRoot);
148
157
  const patterns = (opts.globPatterns ?? ['src/**/*.{ts,tsx}']).map((p) => p.startsWith('/') ? p : join(projectRoot, p).replace(/\\/g, '/'));
149
158
  const ignore = [...DEFAULT_IGNORE, ...(opts.ignore ?? [])];
159
+ console.error(`[indexProject] patterns: ${patterns.join(', ')}`);
150
160
  const files = await fg(patterns, {
151
161
  absolute: true,
152
162
  ignore,
153
163
  onlyFiles: true,
154
164
  dot: false,
155
165
  });
166
+ console.error(`[indexProject] found ${files.length} file(s)`);
156
167
  if (files.length === 0) {
157
168
  return [];
158
169
  }
159
- const project = new Project({
160
- tsConfigFilePath: join(projectRoot, 'tsconfig.json'),
161
- skipAddingFilesFromTsConfig: true,
162
- skipFileDependencyResolution: true,
163
- });
164
- project.addSourceFilesAtPaths(files);
170
+ // 分离 TS/TSX 文件和 JS/JSX 文件
171
+ const tsFiles = files.filter(isTsFile);
172
+ const jsFiles = files.filter(isJsFile);
165
173
  const out = [];
166
- for (const sf of project.getSourceFiles()) {
167
- const exported = sf.getExportedDeclarations();
168
- for (const [exportName, decls] of exported) {
169
- for (const decl of decls) {
170
- const row = processDeclaration(exportName, decl, sf, projectRoot);
171
- if (row) {
172
- out.push(row);
174
+ // 处理 TS/TSX 文件
175
+ if (tsFiles.length > 0) {
176
+ const project = new Project({
177
+ tsConfigFilePath: join(projectRoot, 'tsconfig.json'),
178
+ skipAddingFilesFromTsConfig: true,
179
+ skipFileDependencyResolution: true,
180
+ });
181
+ project.addSourceFilesAtPaths(tsFiles);
182
+ for (const sf of project.getSourceFiles()) {
183
+ const exported = sf.getExportedDeclarations();
184
+ for (const [exportName, decls] of exported) {
185
+ for (const decl of decls) {
186
+ const row = processDeclaration(exportName, decl, sf, projectRoot);
187
+ if (row) {
188
+ out.push(row);
189
+ }
173
190
  }
174
191
  }
175
192
  }
176
193
  }
194
+ // 处理 JS/JSX 文件
195
+ for (const file of jsFiles) {
196
+ try {
197
+ const content = readFileSync(file, 'utf-8');
198
+ const rows = parseJsFile(file, content, projectRoot);
199
+ out.push(...rows);
200
+ }
201
+ catch (e) {
202
+ console.error(`[indexProject] Failed to parse ${file}:`, e);
203
+ }
204
+ }
177
205
  return out;
178
206
  }
@@ -1,23 +1,27 @@
1
- import { resolve } from "node:path";
2
- import { env, validateEnv } from "../config/env.js";
3
- import { getMySqlPool } from "../db/mysql.js";
4
- import { indexedRowToEmbedText } from "../indexer/embedText.js";
5
- import { indexProject } from "../indexer/indexProject.js";
6
- import { upsertSymbols } from "../indexer/persistSymbols.js";
7
- import { createEmbeddingClient, embedAll } from "../services/embeddingClient.js";
1
+ import { resolve } from 'node:path';
2
+ import { env, validateEnv } from '../config/env.js';
3
+ import { getMySqlPool } from '../db/mysql.js';
4
+ import { indexedRowToEmbedText } from '../indexer/embedText.js';
5
+ import { indexProject } from '../indexer/indexProject.js';
6
+ import { upsertSymbols } from '../indexer/persistSymbols.js';
7
+ import { createEmbeddingClient, embedAll, } from '../services/embeddingClient.js';
8
8
  export async function runReindex(options = {}) {
9
9
  validateEnv();
10
10
  const pool = getMySqlPool();
11
+ console.error('[reindex] pool', 'options:', JSON.stringify(options));
11
12
  if (!pool || !env.mysqlEnabled) {
12
- throw new Error("执行 reindex 前必须开启 MYSQL_ENABLED=true。");
13
+ console.error('[reindex] pool', pool, env.mysqlEnabled);
14
+ throw new Error('执行 reindex 前必须开启 MYSQL_ENABLED=true。');
13
15
  }
14
- await pool.query("SELECT 1");
16
+ await pool.query('SELECT 1'); // 测试连接,提前捕获常见的连接错误(如拒绝、认证失败、超时等),并给出更友好的提示。
17
+ console.error('[reindex] MySQL connection successful');
15
18
  const projectRoot = resolve(options.projectRoot ?? process.cwd());
16
19
  const rows = await indexProject({
17
20
  projectRoot,
18
21
  globPatterns: options.globPatterns,
19
- ignore: options.ignore
22
+ ignore: options.ignore,
20
23
  });
24
+ console.error(`[reindex] extracted ${rows.length} symbol(s) from ${projectRoot}`);
21
25
  let embeddingsComputed = false;
22
26
  let embeddingPayload;
23
27
  if (!options.dryRun && rows.length > 0 && env.embeddingServiceUrl) {
@@ -29,7 +33,7 @@ export async function runReindex(options = {}) {
29
33
  embeddingsComputed = true;
30
34
  }
31
35
  catch (err) {
32
- console.error("[reindex] embedding skipped (service error):", err);
36
+ console.error('[reindex] embedding skipped (service error):', err);
33
37
  embeddingPayload = rows.map(() => null);
34
38
  }
35
39
  }
@@ -40,6 +44,6 @@ export async function runReindex(options = {}) {
40
44
  projectRoot,
41
45
  extractedCount: rows.length,
42
46
  upserted: !options.dryRun,
43
- embeddingsComputed
47
+ embeddingsComputed,
44
48
  };
45
49
  }
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@lorrylurui/code-intelligence-mcp",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "private": false,
5
- "description": "MCP server for code intelligence with symbol search and reusable code recommendation",
5
+ "description": "MCP server 提供仓库内可复用代码块(ts/tsx/js/jsx/css/less)的索引和查询能力,支持基于代码上下文的智能推荐。",
6
6
  "type": "module",
7
7
  "main": "dist/index.js",
8
8
  "files": [
@@ -25,6 +25,8 @@
25
25
  "docker:logs": "docker compose logs -f mysql"
26
26
  },
27
27
  "dependencies": {
28
+ "@babel/parser": "^7.29.2",
29
+ "@babel/types": "^7.29.0",
28
30
  "@modelcontextprotocol/sdk": "^1.12.3",
29
31
  "@types/react": "^19.2.14",
30
32
  "dotenv": "^16.4.5",