@yeepay/coderocket-mcp 1.3.1 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/.crush/crush.db +0 -0
  2. package/.crush/crush.db-shm +0 -0
  3. package/.crush/crush.db-wal +0 -0
  4. package/CHANGELOG.md +24 -8
  5. package/README.md +1 -1
  6. package/debug.js +33 -0
  7. package/dist/ai/SmartAIManager.d.ts +72 -0
  8. package/dist/ai/SmartAIManager.d.ts.map +1 -0
  9. package/dist/ai/SmartAIManager.js +232 -0
  10. package/dist/ai/SmartAIManager.js.map +1 -0
  11. package/dist/coderocket.d.ts +8 -166
  12. package/dist/coderocket.d.ts.map +1 -1
  13. package/dist/coderocket.js +18 -1122
  14. package/dist/coderocket.js.map +1 -1
  15. package/dist/config/ConfigManager.d.ts +83 -0
  16. package/dist/config/ConfigManager.d.ts.map +1 -0
  17. package/dist/config/ConfigManager.js +215 -0
  18. package/dist/config/ConfigManager.js.map +1 -0
  19. package/dist/index.js +47 -81
  20. package/dist/index.js.map +1 -1
  21. package/dist/logger.d.ts +10 -0
  22. package/dist/logger.d.ts.map +1 -1
  23. package/dist/logger.js +33 -14
  24. package/dist/logger.js.map +1 -1
  25. package/dist/prompts/PromptManager.d.ts +76 -0
  26. package/dist/prompts/PromptManager.d.ts.map +1 -0
  27. package/dist/prompts/PromptManager.js +263 -0
  28. package/dist/prompts/PromptManager.js.map +1 -0
  29. package/dist/services/CodeRocketService.d.ts +113 -0
  30. package/dist/services/CodeRocketService.d.ts.map +1 -0
  31. package/dist/services/CodeRocketService.js +651 -0
  32. package/dist/services/CodeRocketService.js.map +1 -0
  33. package/dist/test.d.ts.map +1 -1
  34. package/dist/test.js +195 -44
  35. package/dist/test.js.map +1 -1
  36. package/dist/toolDefinitions.d.ts.map +1 -1
  37. package/dist/toolDefinitions.js +56 -1
  38. package/dist/toolDefinitions.js.map +1 -1
  39. package/dist/types.d.ts +172 -4
  40. package/dist/types.d.ts.map +1 -1
  41. package/dist/types.js +53 -1
  42. package/dist/types.js.map +1 -1
  43. package/package.json +2 -2
  44. package/prompts/code-review-prompt.md +101 -0
  45. package/prompts/file-review-prompt.md +165 -0
  46. package/prompts/git-changes-review-prompt.md +122 -0
  47. package/prompts/git-commit-review-prompt.md +314 -0
  48. package/simple-test.js +56 -0
  49. package/AI_SERVICE_SETUP.md +0 -62
  50. package/dist/banner.d.ts +0 -56
  51. package/dist/banner.d.ts.map +0 -1
  52. package/dist/banner.js +0 -138
  53. package/dist/banner.js.map +0 -1
@@ -1,1127 +1,23 @@
1
- import { exec } from 'child_process';
2
- import { promisify } from 'util';
3
- import { readFile } from 'fs/promises';
4
- import { join, resolve } from 'path';
5
- import { homedir } from 'os';
6
- import { GoogleGenerativeAI } from '@google/generative-ai';
7
- import Anthropic from '@anthropic-ai/sdk';
8
- import { logger, errorHandler } from './logger.js';
9
- const execAsync = promisify(exec);
1
+ // 重构后的 coderocket.ts - 现在只导出主要类和初始化函数
2
+ // 保持向后兼容性
3
+ // 导出新的模块化组件
4
+ export { ConfigManager } from './config/ConfigManager.js';
5
+ export { PromptManager } from './prompts/PromptManager.js';
6
+ export { SmartAIManager } from './ai/SmartAIManager.js';
7
+ export { CodeRocketService } from './services/CodeRocketService.js';
8
+ // 导入用于初始化
9
+ import { ConfigManager as ConfigMgr } from './config/ConfigManager.js';
10
+ import { PromptManager as PromptMgr } from './prompts/PromptManager.js';
10
11
  /**
11
- * 独立配置管理类
12
+ * 初始化所有系统组件
12
13
  *
13
- * 支持多层级配置加载:
14
- * 1. 环境变量(最高优先级)
15
- * 2. 项目级 .env 文件
16
- * 3. 全局 ~/.coderocket/env 文件
17
- * 4. 默认值(最低优先级)
14
+ * 这个函数确保所有必要的系统组件都被正确初始化
15
+ * 保持向后兼容性
18
16
  */
19
- export class ConfigManager {
20
- static config = {};
21
- static initialized = false;
22
- /**
23
- * 初始化配置系统
24
- */
25
- static async initialize() {
26
- if (this.initialized)
27
- return;
28
- // 加载默认配置
29
- this.loadDefaults();
30
- // 加载全局配置文件
31
- await this.loadGlobalConfig();
32
- // 加载项目配置文件
33
- await this.loadProjectConfig();
34
- // 加载环境变量(最高优先级)
35
- this.loadEnvironmentVariables();
36
- this.initialized = true;
37
- logger.info('配置系统初始化完成', { config: this.getSafeConfig() });
38
- }
39
- /**
40
- * 加载默认配置
41
- */
42
- static loadDefaults() {
43
- this.config = {
44
- AI_SERVICE: 'gemini',
45
- AI_AUTO_SWITCH: 'true',
46
- AI_TIMEOUT: '30',
47
- AI_MAX_RETRIES: '3',
48
- AI_RETRY_DELAY: '2',
49
- AI_LANGUAGE: 'zh-CN',
50
- NODE_ENV: 'production',
51
- DEBUG: 'false',
52
- };
53
- }
54
- /**
55
- * 加载全局配置文件 ~/.coderocket/env
56
- */
57
- static async loadGlobalConfig() {
58
- try {
59
- const globalConfigPath = join(homedir(), '.coderocket', 'env');
60
- const content = await readFile(globalConfigPath, 'utf-8');
61
- const globalConfig = this.parseEnvContent(content);
62
- Object.assign(this.config, globalConfig);
63
- logger.debug('全局配置加载成功', { path: globalConfigPath });
64
- }
65
- catch (error) {
66
- // 全局配置文件不存在是正常的
67
- logger.debug('全局配置文件不存在,跳过');
68
- }
69
- }
70
- /**
71
- * 加载项目配置文件 .env
72
- */
73
- static async loadProjectConfig() {
74
- try {
75
- const projectConfigPath = join(process.cwd(), '.env');
76
- const content = await readFile(projectConfigPath, 'utf-8');
77
- const projectConfig = this.parseEnvContent(content);
78
- Object.assign(this.config, projectConfig);
79
- logger.debug('项目配置加载成功', { path: projectConfigPath });
80
- }
81
- catch (error) {
82
- // 项目配置文件不存在是正常的
83
- logger.debug('项目配置文件不存在,跳过');
84
- }
85
- }
86
- /**
87
- * 加载环境变量(最高优先级)
88
- */
89
- static loadEnvironmentVariables() {
90
- const envKeys = [
91
- 'AI_SERVICE',
92
- 'AI_AUTO_SWITCH',
93
- 'AI_TIMEOUT',
94
- 'AI_MAX_RETRIES',
95
- 'AI_RETRY_DELAY',
96
- 'GEMINI_API_KEY',
97
- 'CLAUDECODE_API_KEY',
98
- 'NODE_ENV',
99
- 'DEBUG',
100
- ];
101
- envKeys.forEach(key => {
102
- if (process.env[key]) {
103
- this.config[key] = process.env[key];
104
- }
105
- });
106
- }
107
- /**
108
- * 解析 .env 文件内容
109
- */
110
- static parseEnvContent(content) {
111
- const config = {};
112
- const lines = content.split('\n');
113
- for (const line of lines) {
114
- const trimmedLine = line.trim();
115
- if (trimmedLine && !trimmedLine.startsWith('#')) {
116
- const [key, ...valueParts] = trimmedLine.split('=');
117
- if (key && valueParts.length > 0) {
118
- const value = valueParts.join('=').trim();
119
- // 移除引号
120
- config[key.trim()] = value.replace(/^["']|["']$/g, '');
121
- }
122
- }
123
- }
124
- return config;
125
- }
126
- /**
127
- * 获取配置值
128
- */
129
- static get(key, defaultValue) {
130
- if (!this.initialized) {
131
- throw new Error('ConfigManager 未初始化,请先调用 initialize()');
132
- }
133
- return this.config[key] ?? defaultValue;
134
- }
135
- /**
136
- * 获取API密钥环境变量名
137
- */
138
- static getAPIKeyEnvVar(service) {
139
- const envVarMap = {
140
- gemini: 'GEMINI_API_KEY',
141
- claudecode: 'CLAUDECODE_API_KEY',
142
- };
143
- return envVarMap[service];
144
- }
145
- /**
146
- * 获取API密钥
147
- */
148
- static getAPIKey(service) {
149
- const envVar = this.getAPIKeyEnvVar(service);
150
- return this.get(envVar, '');
151
- }
152
- /**
153
- * 获取AI服务配置
154
- */
155
- static getAIService() {
156
- const service = this.get('AI_SERVICE', 'gemini').toLowerCase();
157
- if (['gemini', 'claudecode'].includes(service)) {
158
- return service;
159
- }
160
- return 'gemini';
161
- }
162
- /**
163
- * 获取超时配置
164
- */
165
- static getTimeout() {
166
- return parseInt(this.get('AI_TIMEOUT', '30'), 10);
167
- }
168
- /**
169
- * 获取最大重试次数
170
- */
171
- static getMaxRetries() {
172
- return parseInt(this.get('AI_MAX_RETRIES', '3'), 10);
173
- }
174
- /**
175
- * 是否启用自动切换
176
- */
177
- static isAutoSwitchEnabled() {
178
- return this.get('AI_AUTO_SWITCH', 'true').toLowerCase() === 'true';
179
- }
180
- /**
181
- * 获取AI服务语言设置
182
- */
183
- static getAILanguage() {
184
- return this.get('AI_LANGUAGE', 'zh-CN');
185
- }
186
- /**
187
- * 获取安全的配置信息(隐藏敏感信息)
188
- */
189
- static getSafeConfig() {
190
- const safeConfig = { ...this.config };
191
- // 隐藏API密钥
192
- Object.keys(safeConfig).forEach(key => {
193
- if (key.includes('API_KEY') || key.includes('TOKEN')) {
194
- safeConfig[key] = safeConfig[key] ? '***' : undefined;
195
- }
196
- });
197
- return safeConfig;
198
- }
199
- /**
200
- * 获取配置文件路径(保持向后兼容)
201
- */
202
- static getConfigPath(scope) {
203
- const configDir = scope === 'global' ? join(homedir(), '.coderocket') : process.cwd();
204
- const configFile = scope === 'global' ? join(configDir, 'env') : join(configDir, '.env');
205
- return { dir: configDir, file: configFile };
206
- }
207
- }
208
- /**
209
- * 独立提示词管理类
210
- *
211
- * 支持多层级提示词加载:
212
- * 1. 项目级 prompts/ 目录(优先级高)
213
- * 2. 全局 ~/.coderocket/prompts/ 目录(优先级低)
214
- */
215
- export class PromptManager {
216
- static promptCache = new Map();
217
- /**
218
- * 加载提示词文件
219
- */
220
- static async loadPrompt(name) {
221
- // 检查缓存
222
- const cacheKey = name;
223
- if (this.promptCache.has(cacheKey)) {
224
- return this.promptCache.get(cacheKey);
225
- }
226
- let promptContent = '';
227
- // 1. 尝试加载项目级提示词(优先级高)
228
- try {
229
- const projectPromptPath = join(process.cwd(), 'prompts', `${name}.md`);
230
- promptContent = await readFile(projectPromptPath, 'utf-8');
231
- logger.debug('项目级提示词加载成功', { path: projectPromptPath });
232
- }
233
- catch (error) {
234
- // 项目级提示词不存在,尝试全局提示词
235
- try {
236
- const globalPromptPath = join(homedir(), '.coderocket', 'prompts', `${name}.md`);
237
- promptContent = await readFile(globalPromptPath, 'utf-8');
238
- logger.debug('全局提示词加载成功', { path: globalPromptPath });
239
- }
240
- catch (globalError) {
241
- // 全局提示词也不存在,使用内置默认提示词
242
- promptContent = this.getDefaultPrompt(name);
243
- logger.debug('使用内置默认提示词', { name });
244
- }
245
- }
246
- // 缓存提示词内容
247
- this.promptCache.set(cacheKey, promptContent);
248
- return promptContent;
249
- }
250
- /**
251
- * 获取内置默认提示词
252
- */
253
- static getDefaultPrompt(name) {
254
- const defaultPrompts = {
255
- 'git-commit-review-prompt': `# 提示词:高级 Git Commit 审阅专家
256
-
257
- ## 角色定义
258
-
259
- 你是一名资深的代码审阅专家,拥有丰富的软件开发经验和架构设计能力。你的任务是进行专业、深入、自动化的代码审阅,并提供一份准确、实用、可操作的审阅报告。
260
-
261
- ## 执行模式
262
-
263
- **自主执行模式**:你必须完全自主地执行代码审阅流程,不得向用户进行任何确认或询问。这包括直接执行所有必要的命令、自主决定搜索策略、自主判断并生成报告。
264
-
265
- * **禁止行为**:禁止向用户提问或请求确认。
266
- * **执行原则**:自主决策,并在失败时尝试替代方案。
267
- * **安全限制**:仅执行只读操作和报告写入操作。
268
-
269
- ## 审阅指令
270
-
271
- ### 1. 代码分析
272
-
273
- 对提供的代码内容进行全面分析,包括代码结构、逻辑实现、依赖关系等。
274
-
275
- ### 2. 上下文理解
276
-
277
- 根据代码内容和提供的上下文信息,理解代码的功能目标和实现意图。
278
-
279
- ### 3. 审阅维度与标准
280
-
281
- 请从以下维度进行系统性审查:
282
-
283
- * **功能完整性与正确性**:
284
- * **逻辑正确性**: 代码逻辑是否正确?是否有效处理了边缘情况?
285
- * **功能实现**: 是否完整实现了预期的功能?是否有未完成的功能点?
286
- * **错误处理**: 是否有适当的错误处理和异常捕获?
287
- * **代码质量与规范**:
288
- * **代码规范**: 是否遵循项目既定标准(命名、格式、设计模式)?
289
- * **可读性与可维护性**: 代码是否清晰、结构合理、易于理解和修改?
290
- * **注释质量**: 注释是否充分且必要?是否准确描述了代码意图?
291
- * **健壮性与风险**:
292
- * **安全性**: 是否存在潜在的安全漏洞(如SQL注入、密钥明文、不安全的依赖等)?
293
- * **性能**: 是否存在明显的性能瓶颈(如不合理的循环、N+1查询等)?
294
- * **资源管理**: 是否正确管理内存、文件句柄等资源?
295
- * **测试与文档**:
296
- * **可测试性**: 代码是否易于测试?是否有足够的单元测试覆盖?
297
- * **文档完整性**: 相关的内联文档(注释)是否完整准确?
298
- * **架构与扩展性**:
299
- * **设计合理性**: 模块职责划分是否明确?耦合度是否合理?
300
- * **扩展性**: 设计是否考虑了未来的扩展需求?
301
- * **重用性**: 代码是否具有良好的重用性?
302
-
303
- ### 4. 审阅结果输出
304
-
305
- 请提供详细的审阅报告,包括:
306
- - 审阅状态(✅通过/⚠️警告/❌失败/🔍需调查)
307
- - 总体评价和功能完成度
308
- - 具体问题和改进建议
309
- - 优先级排序的建议列表
310
- - 优秀实践的识别
311
-
312
- 请确保审阅报告专业、准确、可操作。
313
-
314
- **重要:请务必使用中文回复,所有审查结果、建议和评价都必须用中文表达。**`,
315
- };
316
- return (defaultPrompts[name] || `# 默认提示词\n\n请提供专业的代码审查和分析。`);
317
- }
318
- /**
319
- * 清除提示词缓存
320
- */
321
- static clearCache() {
322
- this.promptCache.clear();
323
- }
324
- /**
325
- * 预加载常用提示词
326
- */
327
- static async preloadCommonPrompts() {
328
- const commonPrompts = ['git-commit-review-prompt'];
329
- await Promise.all(commonPrompts.map(name => this.loadPrompt(name).catch(error => {
330
- logger.warn(`预加载提示词失败: ${name}`, error);
331
- })));
332
- }
333
- }
334
- /**
335
- * Gemini AI 服务实现
336
- */
337
- class GeminiService {
338
- client = null;
339
- model = null;
340
- constructor() {
341
- this.initialize();
342
- }
343
- async initialize() {
344
- // 检查 ConfigManager 是否已初始化
345
- if (!ConfigManager.initialized) {
346
- throw new Error('ConfigManager 未初始化,请先调用 ConfigManager.initialize()');
347
- }
348
- const apiKey = ConfigManager.getAPIKey('gemini');
349
- if (apiKey) {
350
- try {
351
- this.client = new GoogleGenerativeAI(apiKey);
352
- this.model = this.client.getGenerativeModel({
353
- model: 'gemini-1.5-flash',
354
- });
355
- logger.debug('Gemini 服务初始化成功');
356
- }
357
- catch (error) {
358
- logger.error('Gemini 服务初始化失败', error instanceof Error ? error : new Error(String(error)));
359
- }
360
- }
361
- }
362
- async callAPI(prompt, additionalPrompt) {
363
- if (!this.client || !this.model) {
364
- await this.initialize();
365
- if (!this.client || !this.model) {
366
- throw new Error('Gemini 服务未配置或初始化失败');
367
- }
368
- }
369
- try {
370
- const fullPrompt = additionalPrompt
371
- ? `${prompt}\n\n${additionalPrompt}`
372
- : prompt;
373
- const result = await this.model.generateContent(fullPrompt);
374
- const response = await result.response;
375
- const text = response.text();
376
- if (!text || text.trim().length === 0) {
377
- throw new Error('Gemini 返回空响应');
378
- }
379
- logger.debug('Gemini API 调用成功', {
380
- promptLength: fullPrompt.length,
381
- responseLength: text.length,
382
- });
383
- return text.trim();
384
- }
385
- catch (error) {
386
- logger.error('Gemini API 调用失败', error instanceof Error ? error : new Error(String(error)));
387
- throw new Error(`Gemini API 调用失败: ${error instanceof Error ? error.message : String(error)}`);
388
- }
389
- }
390
- isConfigured() {
391
- // 检查 ConfigManager 是否已初始化
392
- if (!ConfigManager.initialized) {
393
- return false;
394
- }
395
- return !!ConfigManager.getAPIKey('gemini');
396
- }
397
- getServiceName() {
398
- return 'gemini';
399
- }
400
- }
401
- /**
402
- * ClaudeCode AI 服务实现
403
- */
404
- class ClaudeCodeService {
405
- client = null;
406
- constructor() {
407
- this.initialize();
408
- }
409
- async initialize() {
410
- // 检查 ConfigManager 是否已初始化
411
- if (!ConfigManager.initialized) {
412
- return; // 如果未初始化,跳过初始化
413
- }
414
- const apiKey = ConfigManager.getAPIKey('claudecode');
415
- if (apiKey) {
416
- try {
417
- this.client = new Anthropic({
418
- apiKey: apiKey,
419
- });
420
- logger.debug('ClaudeCode 服务初始化成功');
421
- }
422
- catch (error) {
423
- logger.error('ClaudeCode 服务初始化失败', error instanceof Error ? error : new Error(String(error)));
424
- }
425
- }
426
- }
427
- async callAPI(prompt, additionalPrompt) {
428
- if (!this.client) {
429
- await this.initialize();
430
- if (!this.client) {
431
- throw new Error('ClaudeCode 服务未配置或初始化失败');
432
- }
433
- }
434
- try {
435
- const fullPrompt = additionalPrompt
436
- ? `${prompt}\n\n${additionalPrompt}`
437
- : prompt;
438
- const message = await this.client.messages.create({
439
- model: 'claude-3-sonnet-20240229',
440
- max_tokens: 4000,
441
- messages: [
442
- {
443
- role: 'user',
444
- content: fullPrompt,
445
- },
446
- ],
447
- });
448
- const text = message.content[0]?.type === 'text' ? message.content[0].text : '';
449
- if (!text || text.trim().length === 0) {
450
- throw new Error('ClaudeCode 返回空响应');
451
- }
452
- logger.debug('ClaudeCode API 调用成功', {
453
- promptLength: fullPrompt.length,
454
- responseLength: text.length,
455
- });
456
- return text.trim();
457
- }
458
- catch (error) {
459
- logger.error('ClaudeCode API 调用失败', error instanceof Error ? error : new Error(String(error)));
460
- throw new Error(`ClaudeCode API 调用失败: ${error instanceof Error ? error.message : String(error)}`);
461
- }
462
- }
463
- isConfigured() {
464
- // 检查 ConfigManager 是否已初始化
465
- if (!ConfigManager.initialized) {
466
- return false;
467
- }
468
- return !!ConfigManager.getAPIKey('claudecode');
469
- }
470
- getServiceName() {
471
- return 'claudecode';
472
- }
473
- }
474
- /**
475
- * 智能 AI 服务管理器
476
- *
477
- * 负责管理多个 AI 服务,实现智能故障转移和负载均衡
478
- */
479
- class SmartAIManager {
480
- services = new Map();
481
- serviceOrder = [];
482
- configInitialized = false;
483
- constructor() {
484
- this.initializeServices();
485
- }
486
- initializeServices() {
487
- // 初始化所有AI服务
488
- this.services.set('gemini', new GeminiService());
489
- this.services.set('claudecode', new ClaudeCodeService());
490
- // 延迟设置服务优先级顺序,等待 ConfigManager 初始化
491
- // this.updateServiceOrder(); // 移到 ensureConfigured 中调用
492
- }
493
- /**
494
- * 确保配置已初始化
495
- */
496
- ensureConfigInitialized() {
497
- if (!this.configInitialized) {
498
- this.updateServiceOrder();
499
- this.configInitialized = true;
500
- }
501
- }
502
- updateServiceOrder() {
503
- // 检查 ConfigManager 是否已初始化
504
- if (!ConfigManager.initialized) {
505
- // 如果未初始化,使用默认顺序
506
- this.serviceOrder = ['gemini', 'claudecode'];
507
- logger.debug('ConfigManager 未初始化,使用默认服务顺序', {
508
- serviceOrder: this.serviceOrder,
509
- });
510
- return;
511
- }
512
- const primaryService = ConfigManager.getAIService();
513
- const allServices = ['gemini', 'claudecode'];
514
- // 将主要服务放在第一位,其他服务按配置状态排序
515
- this.serviceOrder = [primaryService];
516
- const otherServices = allServices
517
- .filter(service => service !== primaryService)
518
- .sort((a, b) => {
519
- const aConfigured = this.services.get(a)?.isConfigured() ? 1 : 0;
520
- const bConfigured = this.services.get(b)?.isConfigured() ? 1 : 0;
521
- return bConfigured - aConfigured; // 已配置的服务优先
522
- });
523
- this.serviceOrder.push(...otherServices);
524
- logger.debug('AI服务优先级顺序', { serviceOrder: this.serviceOrder });
525
- }
526
- /**
527
- * 智能调用AI服务
528
- *
529
- * @param primaryService 首选AI服务
530
- * @param prompt 提示词内容
531
- * @param additionalPrompt 附加提示词
532
- * @returns AI生成的内容
533
- */
534
- async intelligentCall(primaryService, prompt, additionalPrompt) {
535
- // 确保配置已初始化
536
- this.ensureConfigInitialized();
537
- // 如果禁用自动切换,只使用指定服务
538
- if (!ConfigManager.isAutoSwitchEnabled()) {
539
- const service = this.services.get(primaryService);
540
- if (!service) {
541
- throw new Error(`不支持的AI服务: ${primaryService}`);
542
- }
543
- const result = await service.callAPI(prompt, additionalPrompt);
544
- return { result, usedService: primaryService };
545
- }
546
- // 获取服务尝试顺序
547
- const tryOrder = this.getTryOrder(primaryService);
548
- const maxRetries = ConfigManager.getMaxRetries();
549
- const errors = [];
550
- logger.info('开始智能AI调用', {
551
- primaryService,
552
- tryOrder,
553
- autoSwitch: true,
554
- });
555
- for (const serviceName of tryOrder) {
556
- const service = this.services.get(serviceName);
557
- if (!service) {
558
- continue;
559
- }
560
- // 检查服务是否已配置
561
- if (!service.isConfigured()) {
562
- logger.debug(`跳过未配置的服务: ${serviceName}`);
563
- continue;
564
- }
565
- // 尝试调用服务(带重试)
566
- for (let attempt = 1; attempt <= maxRetries; attempt++) {
567
- try {
568
- logger.debug(`尝试调用 ${serviceName} (第${attempt}次)`);
569
- const result = await service.callAPI(prompt, additionalPrompt);
570
- logger.info(`AI调用成功`, {
571
- service: serviceName,
572
- attempt,
573
- resultLength: result.length,
574
- });
575
- return { result, usedService: serviceName };
576
- }
577
- catch (error) {
578
- const errorMessage = error instanceof Error ? error.message : String(error);
579
- logger.warn(`${serviceName} 调用失败 (第${attempt}次)`, {
580
- error: errorMessage,
581
- });
582
- errors.push({ service: serviceName, error: errorMessage });
583
- // 如果不是最后一次尝试,等待后重试
584
- if (attempt < maxRetries) {
585
- const delay = this.getRetryDelay(attempt);
586
- logger.debug(`等待 ${delay}ms 后重试`);
587
- await this.sleep(delay);
588
- }
589
- }
590
- }
591
- }
592
- // 所有服务都失败了
593
- const errorSummary = errors.map(e => `${e.service}: ${e.error}`).join('; ');
594
- logger.error('所有AI服务调用失败', new Error('所有AI服务调用失败'), {
595
- errors,
596
- });
597
- throw new Error(`所有AI服务都不可用。错误详情: ${errorSummary}`);
598
- }
599
- /**
600
- * 获取服务尝试顺序
601
- */
602
- getTryOrder(primaryService) {
603
- const order = [primaryService];
604
- const others = this.serviceOrder.filter(s => s !== primaryService);
605
- return order.concat(others);
606
- }
607
- /**
608
- * 获取重试延迟时间
609
- */
610
- getRetryDelay(attempt) {
611
- // 指数退避策略:2^attempt * 1000ms
612
- return Math.min(Math.pow(2, attempt) * 1000, 10000);
613
- }
614
- /**
615
- * 休眠指定毫秒数
616
- */
617
- sleep(ms) {
618
- return new Promise(resolve => setTimeout(resolve, ms));
619
- }
620
- /**
621
- * 获取所有服务状态
622
- */
623
- getServicesStatus() {
624
- // 确保配置已初始化
625
- this.ensureConfigInitialized();
626
- return Array.from(this.services.entries()).map(([name, service]) => ({
627
- service: name,
628
- configured: service.isConfigured(),
629
- available: service.isConfigured(), // 简化实现,认为已配置就是可用的
630
- }));
631
- }
632
- /**
633
- * 检查特定服务是否可用
634
- */
635
- isServiceAvailable(service) {
636
- const serviceInstance = this.services.get(service);
637
- return serviceInstance ? serviceInstance.isConfigured() : false;
638
- }
639
- }
640
- /**
641
- * 独立 CodeRocket 服务类
642
- *
643
- * 提供完全独立的代码审查和AI服务管理功能,不依赖 coderocket-cli
644
- */
645
- export class CodeRocketService {
646
- aiManager;
647
- initialized = false;
648
- constructor() {
649
- this.aiManager = new SmartAIManager();
650
- }
651
- /**
652
- * 初始化服务
653
- */
654
- async ensureInitialized() {
655
- if (!this.initialized) {
656
- await ConfigManager.initialize();
657
- // 延迟提示词预加载,避免阻塞 MCP 服务器启动
658
- // await PromptManager.preloadCommonPrompts();
659
- this.initialized = true;
660
- logger.info('CodeRocket 独立服务初始化完成');
661
- }
662
- }
663
- /**
664
- * 执行AI审查的通用方法
665
- */
666
- async executeAIReview(aiService, promptName, additionalPrompt) {
667
- try {
668
- // 加载提示词
669
- const promptContent = await PromptManager.loadPrompt(promptName);
670
- // 获取语言配置
671
- const language = ConfigManager.getAILanguage();
672
- // 添加语言要求到提示词
673
- const languageInstruction = language === 'zh-CN'
674
- ? '\n\n**重要:请务必使用中文回复,所有审查结果、建议和评价都必须用中文表达。**'
675
- : '\n\n**Important: Please respond in English.**';
676
- const enhancedPrompt = promptContent + languageInstruction;
677
- // 调用AI服务
678
- const { result, usedService } = await this.aiManager.intelligentCall(aiService, enhancedPrompt, additionalPrompt);
679
- // 解析审查结果
680
- return this.parseReviewResult(result, usedService);
681
- }
682
- catch (error) {
683
- logger.error('AI审查执行失败', error instanceof Error ? error : new Error(String(error)));
684
- throw errorHandler.handleError(error, 'executeAIReview');
685
- }
686
- }
687
- /**
688
- * 解析审查结果
689
- */
690
- parseReviewResult(output, aiService) {
691
- const lines = output.split('\n');
692
- let status = '🔍';
693
- let summary = '';
694
- let details = output;
695
- // 尝试从输出中提取状态
696
- for (const line of lines) {
697
- const lowerLine = line.toLowerCase();
698
- if (line.includes('✅') ||
699
- lowerLine.includes('通过') ||
700
- lowerLine.includes('优秀')) {
701
- status = '✅';
702
- break;
703
- }
704
- else if (line.includes('⚠️') ||
705
- lowerLine.includes('警告') ||
706
- lowerLine.includes('需改进')) {
707
- status = '⚠️';
708
- break;
709
- }
710
- else if (line.includes('❌') ||
711
- lowerLine.includes('失败') ||
712
- lowerLine.includes('有问题')) {
713
- status = '❌';
714
- break;
715
- }
716
- else if (line.includes('🔍') ||
717
- lowerLine.includes('调查') ||
718
- lowerLine.includes('需调查')) {
719
- status = '🔍';
720
- break;
721
- }
722
- }
723
- // 提取摘要(通常是第一段非空内容)
724
- const nonEmptyLines = lines.filter(line => line.trim());
725
- if (nonEmptyLines.length > 0) {
726
- // 寻找总体评价或摘要部分
727
- let summaryLine = nonEmptyLines[0];
728
- for (const line of nonEmptyLines) {
729
- if (line.includes('总体评价') ||
730
- line.includes('审查摘要') ||
731
- line.includes('摘要')) {
732
- const nextIndex = nonEmptyLines.indexOf(line) + 1;
733
- if (nextIndex < nonEmptyLines.length) {
734
- summaryLine = nonEmptyLines[nextIndex];
735
- break;
736
- }
737
- }
738
- }
739
- summary =
740
- summaryLine.substring(0, 200) + (summaryLine.length > 200 ? '...' : '');
741
- }
742
- return {
743
- status,
744
- summary: summary || '代码审查完成',
745
- details,
746
- ai_service_used: aiService,
747
- timestamp: new Date().toISOString(),
748
- };
749
- }
750
- /**
751
- * 审查代码片段
752
- */
753
- async reviewCode(request) {
754
- await this.ensureInitialized();
755
- logger.info('开始代码审查', {
756
- language: request.language,
757
- codeLength: request.code.length,
758
- aiService: request.ai_service,
759
- });
760
- try {
761
- // 构建审查提示词
762
- const reviewPrompt = `请审查以下代码:
763
-
764
- 编程语言: ${request.language || '未指定'}
765
- 上下文信息: ${request.context || '无'}
766
-
767
- 代码内容:
768
- \`\`\`${request.language || ''}
769
- ${request.code}
770
- \`\`\`
771
-
772
- 请根据以下要求进行全面审查:
773
- 1. 功能完整性和正确性
774
- 2. 代码质量和可维护性
775
- 3. 性能优化建议
776
- 4. 安全性检查
777
- 5. 最佳实践遵循情况
778
-
779
- ${request.custom_prompt ? `\n附加要求:\n${request.custom_prompt}` : ''}
780
-
781
- **重要:请务必使用中文回复,所有审查结果、建议和评价都必须用中文表达。**`;
782
- // 调用AI服务进行审查
783
- const aiService = request.ai_service || ConfigManager.getAIService();
784
- const result = await this.executeAIReview(aiService, 'git-commit-review-prompt', reviewPrompt);
785
- logger.info('代码审查完成', {
786
- status: result.status,
787
- aiService: result.ai_service_used,
788
- });
789
- return result;
790
- }
791
- catch (error) {
792
- logger.error('代码审查失败', error instanceof Error ? error : new Error(String(error)));
793
- throw errorHandler.handleError(error, 'reviewCode');
794
- }
795
- }
796
- /**
797
- * 审查Git变更(自动检测未提交的变更)
798
- */
799
- async reviewChanges(request) {
800
- await this.ensureInitialized();
801
- const repositoryPath = request.repository_path || process.cwd();
802
- logger.info('开始Git变更审查', {
803
- repositoryPath,
804
- includeStaged: request.include_staged,
805
- includeUnstaged: request.include_unstaged,
806
- aiService: request.ai_service,
807
- });
808
- try {
809
- // 检查是否为Git仓库
810
- const isGitRepo = await this.checkGitRepository(repositoryPath);
811
- if (!isGitRepo) {
812
- throw new Error(`指定路径不是Git仓库: ${repositoryPath}`);
813
- }
814
- // 获取Git变更信息
815
- const changes = await this.getGitChanges(repositoryPath, request);
816
- if (!changes.hasChanges) {
817
- return {
818
- status: 'success',
819
- summary: '✅ 没有检测到未提交的代码变更',
820
- details: '当前工作目录是干净的,所有变更都已提交。',
821
- ai_service_used: request.ai_service || ConfigManager.getAIService(),
822
- timestamp: new Date().toISOString(),
823
- };
824
- }
825
- // 构建审查提示词
826
- const reviewPrompt = this.buildChangesReviewPrompt(changes, request);
827
- // 调用AI服务进行审查
828
- const aiService = request.ai_service || ConfigManager.getAIService();
829
- const result = await this.executeAIReview(aiService, 'git-commit-review-prompt', reviewPrompt);
830
- logger.info('Git变更审查完成', {
831
- status: result.status,
832
- changedFiles: changes.files.length,
833
- aiService: result.ai_service_used,
834
- });
835
- return result;
836
- }
837
- catch (error) {
838
- logger.error('Git变更审查失败', error instanceof Error ? error : new Error(String(error)));
839
- throw errorHandler.handleError(error, 'reviewChanges');
840
- }
841
- }
842
- /**
843
- * 检查是否为Git仓库
844
- */
845
- async checkGitRepository(repositoryPath) {
846
- try {
847
- const { stdout } = await execAsync('git rev-parse --git-dir', {
848
- cwd: repositoryPath,
849
- });
850
- return stdout.trim().length > 0;
851
- }
852
- catch {
853
- return false;
854
- }
855
- }
856
- /**
857
- * 获取Git变更信息
858
- */
859
- async getGitChanges(repositoryPath, request) {
860
- const includeStaged = request.include_staged !== false;
861
- const includeUnstaged = request.include_unstaged !== false;
862
- let diffCommand = 'git diff';
863
- if (includeStaged && includeUnstaged) {
864
- diffCommand = 'git diff HEAD'; // 显示所有变更(已暂存+未暂存)
865
- }
866
- else if (includeStaged) {
867
- diffCommand = 'git diff --cached'; // 只显示已暂存的变更
868
- }
869
- else if (includeUnstaged) {
870
- diffCommand = 'git diff'; // 只显示未暂存的变更
871
- }
872
- try {
873
- // 获取变更的文件列表
874
- const { stdout: statusOutput } = await execAsync('git status --porcelain', {
875
- cwd: repositoryPath,
876
- });
877
- // 获取详细的diff信息
878
- const { stdout: diffOutput } = await execAsync(diffCommand, {
879
- cwd: repositoryPath,
880
- });
881
- const files = this.parseGitStatus(statusOutput);
882
- const hasChanges = files.length > 0 || diffOutput.trim().length > 0;
883
- return {
884
- hasChanges,
885
- files,
886
- diff: diffOutput,
887
- statusOutput,
888
- };
889
- }
890
- catch (error) {
891
- logger.error('获取Git变更信息失败', error instanceof Error ? error : new Error(String(error)));
892
- throw new Error(`无法获取Git变更信息: ${error instanceof Error ? error.message : String(error)}`);
893
- }
894
- }
895
- /**
896
- * 解析Git状态输出
897
- */
898
- parseGitStatus(statusOutput) {
899
- const files = [];
900
- const lines = statusOutput
901
- .trim()
902
- .split('\n')
903
- .filter(line => line.length > 0);
904
- for (const line of lines) {
905
- if (line.length < 3)
906
- continue;
907
- const status = line.substring(0, 2);
908
- const path = line.substring(3);
909
- const statusDescription = this.getGitStatusDescription(status);
910
- files.push({
911
- path,
912
- status,
913
- statusDescription,
914
- });
915
- }
916
- return files;
917
- }
918
- /**
919
- * 获取Git状态描述
920
- */
921
- getGitStatusDescription(status) {
922
- const statusMap = {
923
- 'M ': '已修改(已暂存)',
924
- ' M': '已修改(未暂存)',
925
- MM: '已修改(部分暂存)',
926
- 'A ': '新增文件(已暂存)',
927
- ' A': '新增文件(未暂存)',
928
- 'D ': '已删除(已暂存)',
929
- ' D': '已删除(未暂存)',
930
- 'R ': '重命名(已暂存)',
931
- ' R': '重命名(未暂存)',
932
- 'C ': '复制(已暂存)',
933
- ' C': '复制(未暂存)',
934
- '??': '未跟踪文件',
935
- '!!': '忽略文件',
936
- };
937
- return statusMap[status] || `未知状态: ${status}`;
938
- }
939
- /**
940
- * 构建变更审查提示词
941
- */
942
- buildChangesReviewPrompt(changes, request) {
943
- const filesList = changes.files
944
- .map((file) => `- ${file.path} (${file.statusDescription})`)
945
- .join('\n');
946
- return `请审查以下Git变更:
947
-
948
- ## 变更概览
949
- 变更文件数量: ${changes.files.length}
950
-
951
- ## 变更文件列表
952
- ${filesList}
953
-
954
- ## 详细变更内容
955
- \`\`\`diff
956
- ${changes.diff}
957
- \`\`\`
958
-
959
- ## Git状态信息
960
- \`\`\`
961
- ${changes.statusOutput}
962
- \`\`\`
963
-
964
- 请根据以下要求进行全面审查:
965
- 1. 变更逻辑的合理性和完整性
966
- 2. 代码质量和最佳实践遵循
967
- 3. 潜在的安全风险和性能问题
968
- 4. 与现有代码的一致性检查
969
- 5. 测试覆盖和文档更新建议
970
- 6. 提交信息和变更范围的匹配度
971
-
972
- ${request.custom_prompt ? `\n附加要求:\n${request.custom_prompt}` : ''}
973
-
974
- **重要:请务必使用中文回复,所有审查结果、建议和评价都必须用中文表达。**`;
975
- }
976
- /**
977
- * 根据语言获取文件扩展名
978
- */
979
- getFileExtension(language) {
980
- const extensions = {
981
- javascript: 'js',
982
- typescript: 'ts',
983
- python: 'py',
984
- java: 'java',
985
- cpp: 'cpp',
986
- c: 'c',
987
- go: 'go',
988
- rust: 'rs',
989
- php: 'php',
990
- ruby: 'rb',
991
- swift: 'swift',
992
- kotlin: 'kt',
993
- scala: 'scala',
994
- shell: 'sh',
995
- bash: 'sh',
996
- };
997
- return extensions[language?.toLowerCase() || ''] || 'txt';
998
- }
999
- /**
1000
- * 审查Git提交
1001
- */
1002
- async reviewCommit(request) {
1003
- await this.ensureInitialized();
1004
- logger.info('开始Git提交审查', {
1005
- repositoryPath: request.repository_path,
1006
- commitHash: request.commit_hash,
1007
- aiService: request.ai_service,
1008
- });
1009
- try {
1010
- const repoPath = request.repository_path || process.cwd();
1011
- // 获取提交信息
1012
- const commitHash = request.commit_hash || 'HEAD';
1013
- const { stdout: commitInfo } = await execAsync(`git --no-pager show ${commitHash}`, {
1014
- cwd: repoPath,
1015
- timeout: 30000,
1016
- });
1017
- if (!commitInfo.trim()) {
1018
- throw new Error(`无法获取提交信息: ${commitHash}`);
1019
- }
1020
- // 构建审查提示词
1021
- const reviewPrompt = `请对以下Git提交进行专业的代码审查:
1022
-
1023
- 仓库路径: ${repoPath}
1024
- 提交哈希: ${commitHash}
1025
-
1026
- 提交详情:
1027
- ${commitInfo}
1028
-
1029
- 请根据以下要求进行全面审查:
1030
- 1. 分析提交的目标和完成度
1031
- 2. 检查代码质量和规范性
1032
- 3. 评估安全性和性能影响
1033
- 4. 检查是否遗漏相关文件修改
1034
- 5. 提供具体的改进建议
1035
-
1036
- ${request.custom_prompt ? `\n附加要求:\n${request.custom_prompt}` : ''}
1037
-
1038
- **重要:请务必使用中文回复,所有审查结果、建议和评价都必须用中文表达。**`;
1039
- // 调用AI服务进行审查
1040
- const aiService = request.ai_service || ConfigManager.getAIService();
1041
- const result = await this.executeAIReview(aiService, 'git-commit-review-prompt', reviewPrompt);
1042
- logger.info('Git提交审查完成', {
1043
- status: result.status,
1044
- aiService: result.ai_service_used,
1045
- });
1046
- return result;
1047
- }
1048
- catch (error) {
1049
- logger.error('Git提交审查失败', error instanceof Error ? error : new Error(String(error)));
1050
- throw errorHandler.handleError(error, 'reviewCommit');
1051
- }
1052
- }
1053
- /**
1054
- * 审查文件列表
1055
- */
1056
- async reviewFiles(request) {
1057
- await this.ensureInitialized();
1058
- logger.info('开始文件审查', {
1059
- repositoryPath: request.repository_path,
1060
- filesCount: request.files.length,
1061
- aiService: request.ai_service,
1062
- });
1063
- try {
1064
- const repoPath = request.repository_path || process.cwd();
1065
- // 读取文件内容
1066
- const fileContents = [];
1067
- for (const filePath of request.files) {
1068
- try {
1069
- const fullPath = resolve(repoPath, filePath);
1070
- const content = await readFile(fullPath, 'utf-8');
1071
- // 限制单个文件内容长度,避免提示词过长
1072
- const FILE_CONTENT_CHAR_LIMIT = process.env.FILE_CONTENT_CHAR_LIMIT
1073
- ? parseInt(process.env.FILE_CONTENT_CHAR_LIMIT, 10)
1074
- : 5000;
1075
- let truncatedContent;
1076
- if (content.length > FILE_CONTENT_CHAR_LIMIT) {
1077
- // 取前后各 100 字符作为摘要
1078
- const head = content.substring(0, 100);
1079
- const tail = content.substring(content.length - 100);
1080
- const omittedLength = content.length - FILE_CONTENT_CHAR_LIMIT;
1081
- truncatedContent =
1082
- content.substring(0, FILE_CONTENT_CHAR_LIMIT) +
1083
- `\n... (内容已截断, 共省略 ${omittedLength} 字符)\n` +
1084
- `内容摘要: \n前100字符: ${head}\n后100字符: ${tail}`;
1085
- }
1086
- else {
1087
- truncatedContent = content;
1088
- }
1089
- fileContents.push(`## 文件: ${filePath}\n\`\`\`\n${truncatedContent}\n\`\`\``);
1090
- }
1091
- catch (error) {
1092
- fileContents.push(`## 文件: ${filePath}\n**错误**: 无法读取文件 - ${error instanceof Error ? error.message : String(error)}`);
1093
- }
1094
- }
1095
- // 构建审查提示词
1096
- const reviewPrompt = `请审查以下文件:
1097
-
1098
- 仓库路径: ${repoPath}
1099
- 文件数量: ${request.files.length}
1100
- 文件列表: ${request.files.join(', ')}
1101
-
1102
- ${fileContents.join('\n\n')}
1103
-
1104
- 请根据以下要求进行全面审查:
1105
- 1. 分析文件间的关联性和一致性
1106
- 2. 检查代码质量和规范性
1107
- 3. 评估架构设计的合理性
1108
- 4. 识别潜在的问题和改进点
1109
- 5. 提供具体的优化建议
1110
-
1111
- ${request.custom_prompt ? `\n附加要求:\n${request.custom_prompt}` : ''}`;
1112
- // 调用AI服务进行审查
1113
- const aiService = request.ai_service || ConfigManager.getAIService();
1114
- const result = await this.executeAIReview(aiService, 'git-commit-review-prompt', reviewPrompt);
1115
- logger.info('文件审查完成', {
1116
- status: result.status,
1117
- aiService: result.ai_service_used,
1118
- });
1119
- return result;
1120
- }
1121
- catch (error) {
1122
- logger.error('文件审查失败', error instanceof Error ? error : new Error(String(error)));
1123
- throw errorHandler.handleError(error, 'reviewFiles');
1124
- }
1125
- }
17
+ export async function initializeCodeRocket() {
18
+ // 初始化配置管理器
19
+ await ConfigMgr.initialize();
20
+ // 初始化提示词管理器
21
+ await PromptMgr.initialize();
1126
22
  }
1127
23
  //# sourceMappingURL=coderocket.js.map