code-search-mcp 0.2.0__py3-none-any.whl
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.
- code_index_mcp/__init__.py +1 -0
- code_index_mcp/__main__.py +13 -0
- code_index_mcp/cli.py +340 -0
- code_index_mcp/database.py +407 -0
- code_index_mcp/indexer.py +267 -0
- code_index_mcp/logger.py +265 -0
- code_index_mcp/server.py +423 -0
- code_index_mcp/strategies.py +298 -0
- code_search_mcp-0.2.0.dist-info/METADATA +224 -0
- code_search_mcp-0.2.0.dist-info/RECORD +13 -0
- code_search_mcp-0.2.0.dist-info/WHEEL +5 -0
- code_search_mcp-0.2.0.dist-info/entry_points.txt +3 -0
- code_search_mcp-0.2.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Code Index MCP - 将本地代码库转换为SQLite索引,提供高效的混合搜索能力"""
|
code_index_mcp/cli.py
ADDED
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
"""Code Index CLI - 命令行工具
|
|
2
|
+
|
|
3
|
+
使用方法:
|
|
4
|
+
code-index index <path> 索引代码仓库
|
|
5
|
+
code-index rebuild <path> 重建索引
|
|
6
|
+
code-index search <query> 搜索文件/符号/内容
|
|
7
|
+
code-index files <query> 搜索文件
|
|
8
|
+
code-index symbols <query> 搜索符号
|
|
9
|
+
code-index content <query> 搜索内容
|
|
10
|
+
code-index logs 查看日志
|
|
11
|
+
code-index stats 查看统计
|
|
12
|
+
code-index status 查看索引状态
|
|
13
|
+
code-index clean 清理索引/日志
|
|
14
|
+
"""
|
|
15
|
+
import argparse
|
|
16
|
+
import os
|
|
17
|
+
import sys
|
|
18
|
+
|
|
19
|
+
# 添加项目路径
|
|
20
|
+
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
21
|
+
|
|
22
|
+
from code_index_mcp.database import Database
|
|
23
|
+
from code_index_mcp.indexer import Indexer
|
|
24
|
+
from code_index_mcp.logger import Logger
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def cmd_index(args):
|
|
28
|
+
"""索引命令(增量)"""
|
|
29
|
+
db = Database()
|
|
30
|
+
logger = Logger()
|
|
31
|
+
indexer = Indexer(db)
|
|
32
|
+
|
|
33
|
+
print(f"索引仓库: {args.path}")
|
|
34
|
+
result = indexer.index_repository(args.path, force_rebuild=False, verbose=True)
|
|
35
|
+
|
|
36
|
+
stats = result.get("stats", {})
|
|
37
|
+
logger.log("index_cli", stats.get("elapsed_seconds", 0) * 1000,
|
|
38
|
+
stats.get("files_indexed", 0),
|
|
39
|
+
f'{{"path": "{args.path}", "incremental": true}}', True)
|
|
40
|
+
|
|
41
|
+
# 显示增量状态
|
|
42
|
+
if result.get("is_incremental"):
|
|
43
|
+
print("\n✓ 增量索引完成")
|
|
44
|
+
print(f" 新增/更新: {stats.get('files_indexed', 0)}")
|
|
45
|
+
print(f" 跳过(未变更): {stats.get('files_skipped', 0)}")
|
|
46
|
+
print(f" 删除: {stats.get('files_deleted', 0)}")
|
|
47
|
+
else:
|
|
48
|
+
print("\n✓ 索引完成")
|
|
49
|
+
print(f" 文件: {stats.get('files_indexed', 0)}")
|
|
50
|
+
print(f" 符号: {stats.get('symbols', 0)}")
|
|
51
|
+
print(f" 耗时: {stats.get('elapsed_seconds', 0)}s")
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def cmd_rebuild(args):
|
|
55
|
+
"""重建索引命令"""
|
|
56
|
+
db = Database()
|
|
57
|
+
logger = Logger()
|
|
58
|
+
indexer = Indexer(db)
|
|
59
|
+
|
|
60
|
+
print(f"重建索引: {args.path}")
|
|
61
|
+
result = indexer.index_repository(args.path, force_rebuild=True, verbose=True)
|
|
62
|
+
|
|
63
|
+
stats = result.get("stats", {})
|
|
64
|
+
logger.log("rebuild_cli", stats.get("elapsed_seconds", 0) * 1000,
|
|
65
|
+
stats.get("files_indexed", 0),
|
|
66
|
+
f'{{"path": "{args.path}", "force": true}}', True)
|
|
67
|
+
|
|
68
|
+
print("\n✓ 全量索引完成")
|
|
69
|
+
print(f" 文件: {stats.get('files_indexed', 0)}")
|
|
70
|
+
print(f" 符号: {stats.get('symbols', 0)}")
|
|
71
|
+
print(f" 耗时: {stats.get('elapsed_seconds', 0)}s")
|
|
72
|
+
logger = Logger()
|
|
73
|
+
indexer = Indexer(db)
|
|
74
|
+
|
|
75
|
+
print(f"重建索引: {args.path}")
|
|
76
|
+
result = indexer.index_repository(args.path, force_rebuild=True)
|
|
77
|
+
logger.log("rebuild_cli", 0, result.get("stats", {}).get("files", 0),
|
|
78
|
+
f'{{"path": "{args.path}", "force": true}}', True)
|
|
79
|
+
|
|
80
|
+
print("✓ 重建完成")
|
|
81
|
+
print(f" 文件: {result['stats']['files']}")
|
|
82
|
+
print(f" 符号: {result['stats']['symbols']}")
|
|
83
|
+
print(f" 耗时: {result['stats']['elapsed_seconds']}s")
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def cmd_files(args):
|
|
87
|
+
"""搜索文件命令"""
|
|
88
|
+
db = Database()
|
|
89
|
+
|
|
90
|
+
results = db.find_files(query=args.query, limit=args.limit or 20)
|
|
91
|
+
|
|
92
|
+
print(f"搜索文件: '{args.query}'")
|
|
93
|
+
print(f"找到 {len(results)} 个结果:\n")
|
|
94
|
+
|
|
95
|
+
for f in results:
|
|
96
|
+
print(f" {f.path}")
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def cmd_symbols(args):
|
|
100
|
+
"""搜索符号命令"""
|
|
101
|
+
db = Database()
|
|
102
|
+
|
|
103
|
+
results = db.find_symbols(query=args.query, kind=args.kind, limit=args.limit or 20)
|
|
104
|
+
|
|
105
|
+
print(f"搜索符号: '{args.query}'" + (f" (类型: {args.kind})" if args.kind else ""))
|
|
106
|
+
print(f"找到 {len(results)} 个结果:\n")
|
|
107
|
+
|
|
108
|
+
for s in results:
|
|
109
|
+
print(f" {s['name']} ({s['kind']}) @ {s['file_path']}:{s['line_start']}")
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def cmd_content(args):
|
|
113
|
+
"""搜索内容命令"""
|
|
114
|
+
db = Database()
|
|
115
|
+
|
|
116
|
+
results = db.find_content(query=args.query, regex=args.regex, limit=args.limit or 20)
|
|
117
|
+
|
|
118
|
+
print(f"搜索内容: '{args.query}'" + (" (正则)" if args.regex else ""))
|
|
119
|
+
print(f"找到 {len(results)} 个结果:\n")
|
|
120
|
+
|
|
121
|
+
for m in results:
|
|
122
|
+
preview = m['content'][:60].replace('\n', ' ')
|
|
123
|
+
print(f" {m['file_path']}:{m['line_number']}")
|
|
124
|
+
print(f" {preview}...")
|
|
125
|
+
print()
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def cmd_search(args):
|
|
129
|
+
"""综合搜索命令"""
|
|
130
|
+
db = Database()
|
|
131
|
+
query = args.query
|
|
132
|
+
limit = args.limit or 10
|
|
133
|
+
|
|
134
|
+
print(f"综合搜索: '{query}'")
|
|
135
|
+
print("=" * 50)
|
|
136
|
+
|
|
137
|
+
# 搜索文件
|
|
138
|
+
files = db.find_files(query=query, limit=limit)
|
|
139
|
+
if files:
|
|
140
|
+
print(f"\n📁 文件 ({len(files)}):")
|
|
141
|
+
for f in files[:5]:
|
|
142
|
+
print(f" {f.path}")
|
|
143
|
+
|
|
144
|
+
# 搜索符号
|
|
145
|
+
symbols = db.find_symbols(query=query, limit=limit)
|
|
146
|
+
if symbols:
|
|
147
|
+
print(f"\n🔍 符号 ({len(symbols)}):")
|
|
148
|
+
for s in symbols[:5]:
|
|
149
|
+
print(f" {s['name']} @ {s['file_path']}:{s['line_start']}")
|
|
150
|
+
|
|
151
|
+
# 搜索内容
|
|
152
|
+
content = db.find_content(query=query, limit=limit)
|
|
153
|
+
if content:
|
|
154
|
+
print(f"\n📝 内容 ({len(content)}):")
|
|
155
|
+
for c in content[:5]:
|
|
156
|
+
print(f" {c['file_path']}:{c['line_number']}")
|
|
157
|
+
print(f" {c['content'][:50]}...")
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def cmd_logs(args):
|
|
161
|
+
"""查看日志命令"""
|
|
162
|
+
logger = Logger()
|
|
163
|
+
|
|
164
|
+
# 参数
|
|
165
|
+
tool_name = args.tool or None
|
|
166
|
+
success = args.errors_only if args.errors_only else (None if args.success_only is None else True)
|
|
167
|
+
limit = args.limit or 20
|
|
168
|
+
|
|
169
|
+
logs = logger.get_logs(tool_name=tool_name, success=success, limit=limit)
|
|
170
|
+
|
|
171
|
+
print(f"日志 ({len(logs)} 条)")
|
|
172
|
+
print("=" * 70)
|
|
173
|
+
|
|
174
|
+
for log in logs:
|
|
175
|
+
status = "✓" if log['success'] else "✗"
|
|
176
|
+
level = log['level']
|
|
177
|
+
print(f"{status} [{level:7}] {log['tool_name']:20} {log['duration_ms']:8.2f}ms")
|
|
178
|
+
|
|
179
|
+
if not log['success']:
|
|
180
|
+
print(f" 错误: {log['error_message'][:50]}")
|
|
181
|
+
elif args.verbose:
|
|
182
|
+
print(f" 查询: {log['query'][:50]}")
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def cmd_stats(args):
|
|
186
|
+
"""查看统计命令"""
|
|
187
|
+
logger = Logger()
|
|
188
|
+
|
|
189
|
+
stats = logger.get_stats()
|
|
190
|
+
|
|
191
|
+
print("调用统计")
|
|
192
|
+
print("=" * 40)
|
|
193
|
+
print(f"总调用: {stats['total_calls']}")
|
|
194
|
+
print(f"成功: {stats['success_count']}")
|
|
195
|
+
print(f"失败: {stats['error_count']}")
|
|
196
|
+
print(f"成功率: {stats['success_rate']}%")
|
|
197
|
+
print(f"平均耗时: {stats['avg_duration_ms']:.2f}ms")
|
|
198
|
+
|
|
199
|
+
if stats['by_tool']:
|
|
200
|
+
print("\n按工具统计:")
|
|
201
|
+
print("-" * 60)
|
|
202
|
+
print(f"{'工具':<20} {'次数':>6} {'平均(ms)':>12} {'最大(ms)':>12}")
|
|
203
|
+
for t in stats['by_tool']:
|
|
204
|
+
print(f"{t['tool_name']:<20} {t['count']:>6} {t['avg_duration']:>12.2f} {t['max_duration']:>12.2f}")
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def cmd_status(args):
|
|
208
|
+
"""查看状态命令"""
|
|
209
|
+
db = Database()
|
|
210
|
+
logger = Logger()
|
|
211
|
+
|
|
212
|
+
# 索引统计
|
|
213
|
+
idx_stats = db.get_stats()
|
|
214
|
+
|
|
215
|
+
print("索引状态")
|
|
216
|
+
print("=" * 40)
|
|
217
|
+
print(f"文件: {idx_stats['files']}")
|
|
218
|
+
print(f"符号: {idx_stats['symbols']}")
|
|
219
|
+
print(f"内容块: {idx_stats['content_blocks']}")
|
|
220
|
+
|
|
221
|
+
# 日志统计
|
|
222
|
+
log_stats = logger.get_stats()
|
|
223
|
+
print("\n调用统计")
|
|
224
|
+
print("-" * 20)
|
|
225
|
+
print(f"总调用: {log_stats['total_calls']}")
|
|
226
|
+
print(f"错误: {log_stats['error_count']}")
|
|
227
|
+
|
|
228
|
+
# 数据库路径
|
|
229
|
+
print("\n数据库路径")
|
|
230
|
+
print("-" * 20)
|
|
231
|
+
print(f"索引: {db.db_path}")
|
|
232
|
+
print(f"日志: {logger.db_path}")
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def cmd_clean(args):
|
|
236
|
+
"""清理命令"""
|
|
237
|
+
db = Database()
|
|
238
|
+
logger = Logger()
|
|
239
|
+
|
|
240
|
+
if args.all:
|
|
241
|
+
# 清理所有
|
|
242
|
+
confirm = input("确认清理所有索引和日志? (y/n): ")
|
|
243
|
+
if confirm.lower() == 'y':
|
|
244
|
+
db.clear_all()
|
|
245
|
+
logger.clear_old_logs(days=0)
|
|
246
|
+
print("✓ 已清理所有索引和日志")
|
|
247
|
+
elif args.logs:
|
|
248
|
+
# 清理日志
|
|
249
|
+
days = args.logs if args.logs > 0 else 0
|
|
250
|
+
logger.clear_old_logs(days=days)
|
|
251
|
+
print(f"✓ 已清理 {days} 天前的日志")
|
|
252
|
+
elif args.index:
|
|
253
|
+
# 清理索引
|
|
254
|
+
confirm = input("确认清理所有索引? (y/n): ")
|
|
255
|
+
if confirm.lower() == 'y':
|
|
256
|
+
db.clear_all()
|
|
257
|
+
print("✓ 已清理所有索引")
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def main():
|
|
261
|
+
"""主入口"""
|
|
262
|
+
parser = argparse.ArgumentParser(
|
|
263
|
+
description="Code Index CLI - 代码索引命令行工具",
|
|
264
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
265
|
+
epilog=__doc__
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
subparsers = parser.add_subparsers(dest="command", help="可用命令")
|
|
269
|
+
|
|
270
|
+
# index 命令
|
|
271
|
+
p_index = subparsers.add_parser("index", help="索引代码仓库")
|
|
272
|
+
p_index.add_argument("path", help="代码仓库路径")
|
|
273
|
+
p_index.set_defaults(func=cmd_index)
|
|
274
|
+
|
|
275
|
+
# rebuild 命令
|
|
276
|
+
p_rebuild = subparsers.add_parser("rebuild", help="重建索引")
|
|
277
|
+
p_rebuild.add_argument("path", help="代码仓库路径")
|
|
278
|
+
p_rebuild.set_defaults(func=cmd_rebuild)
|
|
279
|
+
|
|
280
|
+
# search 命令
|
|
281
|
+
p_search = subparsers.add_parser("search", help="综合搜索")
|
|
282
|
+
p_search.add_argument("query", help="搜索关键词")
|
|
283
|
+
p_search.add_argument("-l", "--limit", type=int, help="结果数量限制")
|
|
284
|
+
p_search.set_defaults(func=cmd_search)
|
|
285
|
+
|
|
286
|
+
# files 命令
|
|
287
|
+
p_files = subparsers.add_parser("files", help="搜索文件")
|
|
288
|
+
p_files.add_argument("query", help="搜索关键词")
|
|
289
|
+
p_files.add_argument("-l", "--limit", type=int, help="结果数量限制")
|
|
290
|
+
p_files.set_defaults(func=cmd_files)
|
|
291
|
+
|
|
292
|
+
# symbols 命令
|
|
293
|
+
p_symbols = subparsers.add_parser("symbols", help="搜索符号")
|
|
294
|
+
p_symbols.add_argument("query", help="搜索关键词")
|
|
295
|
+
p_symbols.add_argument("-k", "--kind", help="符号类型 (function/class/method)")
|
|
296
|
+
p_symbols.add_argument("-l", "--limit", type=int, help="结果数量限制")
|
|
297
|
+
p_symbols.set_defaults(func=cmd_symbols)
|
|
298
|
+
|
|
299
|
+
# content 命令
|
|
300
|
+
p_content = subparsers.add_parser("content", help="搜索内容")
|
|
301
|
+
p_content.add_argument("query", help="搜索关键词")
|
|
302
|
+
p_content.add_argument("-r", "--regex", action="store_true", help="使用正则表达式")
|
|
303
|
+
p_content.add_argument("-l", "--limit", type=int, help="结果数量限制")
|
|
304
|
+
p_content.set_defaults(func=cmd_content)
|
|
305
|
+
|
|
306
|
+
# logs 命令
|
|
307
|
+
p_logs = subparsers.add_parser("logs", help="查看日志")
|
|
308
|
+
p_logs.add_argument("-t", "--tool", help="工具名称过滤")
|
|
309
|
+
p_logs.add_argument("-e", "--errors-only", action="store_true", help="只显示错误")
|
|
310
|
+
p_logs.add_argument("-s", "--success-only", action="store_true", help="只显示成功")
|
|
311
|
+
p_logs.add_argument("-l", "--limit", type=int, help="结果数量限制")
|
|
312
|
+
p_logs.add_argument("-v", "--verbose", action="store_true", help="显示详细信息")
|
|
313
|
+
p_logs.set_defaults(func=cmd_logs)
|
|
314
|
+
|
|
315
|
+
# stats 命令
|
|
316
|
+
p_stats = subparsers.add_parser("stats", help="查看统计")
|
|
317
|
+
p_stats.set_defaults(func=cmd_stats)
|
|
318
|
+
|
|
319
|
+
# status 命令
|
|
320
|
+
p_status = subparsers.add_parser("status", help="查看状态")
|
|
321
|
+
p_status.set_defaults(func=cmd_status)
|
|
322
|
+
|
|
323
|
+
# clean 命令
|
|
324
|
+
p_clean = subparsers.add_parser("clean", help="清理数据")
|
|
325
|
+
p_clean.add_argument("-a", "--all", action="store_true", help="清理所有索引和日志")
|
|
326
|
+
p_clean.add_argument("-i", "--index", action="store_true", help="清理索引")
|
|
327
|
+
p_clean.add_argument("-l", "--logs", type=int, nargs="?", const=7, default=0, help="清理日志 (天数,0=全部)")
|
|
328
|
+
p_clean.set_defaults(func=cmd_clean)
|
|
329
|
+
|
|
330
|
+
args = parser.parse_args()
|
|
331
|
+
|
|
332
|
+
if args.command is None:
|
|
333
|
+
parser.print_help()
|
|
334
|
+
return
|
|
335
|
+
|
|
336
|
+
args.func(args)
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
if __name__ == "__main__":
|
|
340
|
+
main()
|