cfix 1.0.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 (52) hide show
  1. package/.env.example +69 -0
  2. package/README.md +1590 -0
  3. package/bin/cfix +14 -0
  4. package/bin/cfix.cmd +6 -0
  5. package/cli/commands/config.js +58 -0
  6. package/cli/commands/doctor.js +240 -0
  7. package/cli/commands/fix.js +211 -0
  8. package/cli/commands/help.js +62 -0
  9. package/cli/commands/init.js +226 -0
  10. package/cli/commands/logs.js +161 -0
  11. package/cli/commands/monitor.js +151 -0
  12. package/cli/commands/project.js +331 -0
  13. package/cli/commands/service.js +133 -0
  14. package/cli/commands/status.js +115 -0
  15. package/cli/commands/task.js +412 -0
  16. package/cli/commands/version.js +19 -0
  17. package/cli/index.js +269 -0
  18. package/cli/lib/config-manager.js +612 -0
  19. package/cli/lib/formatter.js +224 -0
  20. package/cli/lib/process-manager.js +233 -0
  21. package/cli/lib/service-client.js +271 -0
  22. package/cli/scripts/install-completion.js +133 -0
  23. package/package.json +85 -0
  24. package/public/monitor.html +1096 -0
  25. package/scripts/completion.bash +87 -0
  26. package/scripts/completion.zsh +102 -0
  27. package/src/assets/README.md +32 -0
  28. package/src/assets/error.png +0 -0
  29. package/src/assets/icon.png +0 -0
  30. package/src/assets/success.png +0 -0
  31. package/src/claude-cli-service.js +216 -0
  32. package/src/config/index.js +69 -0
  33. package/src/database/manager.js +391 -0
  34. package/src/database/migration.js +252 -0
  35. package/src/git-service.js +1278 -0
  36. package/src/index.js +1658 -0
  37. package/src/logger.js +139 -0
  38. package/src/metrics/collector.js +184 -0
  39. package/src/middleware/auth.js +86 -0
  40. package/src/middleware/rate-limit.js +85 -0
  41. package/src/queue/integration-example.js +283 -0
  42. package/src/queue/task-queue.js +333 -0
  43. package/src/services/notification-limiter.js +48 -0
  44. package/src/services/notification-service.js +115 -0
  45. package/src/services/system-notifier.js +130 -0
  46. package/src/task-manager.js +289 -0
  47. package/src/utils/exec.js +87 -0
  48. package/src/utils/project-lock.js +246 -0
  49. package/src/utils/retry.js +110 -0
  50. package/src/utils/sanitizer.js +174 -0
  51. package/src/websocket/notifier.js +363 -0
  52. package/src/wechat-notifier.js +97 -0
package/src/index.js ADDED
@@ -0,0 +1,1658 @@
1
+ require("dotenv").config();
2
+ const Koa = require("koa");
3
+ const Router = require("koa-router");
4
+ const bodyParser = require("koa-bodyparser");
5
+ const cors = require("@koa/cors");
6
+ const path = require("path");
7
+ const koaStatic = require("koa-static");
8
+ const ClaudeCLIService = require("./claude-cli-service");
9
+ const GitService = require("./git-service");
10
+ const WeChatNotifier = require("./wechat-notifier");
11
+ const NotificationService = require("./services/notification-service");
12
+ const ExecUtil = require("./utils/exec");
13
+ const taskManager = require("./task-manager");
14
+ const { logger, generateRequestId, createContextLogger } = require("./logger");
15
+ const InputSanitizer = require("./utils/sanitizer");
16
+ const { apiLimiter, strictLimiter } = require("./middleware/rate-limit");
17
+ const { authenticateAPI, ipWhitelist } = require("./middleware/auth");
18
+ const metrics = require("./metrics/collector");
19
+ const config = require("./config");
20
+ const projectLock = require("./utils/project-lock");
21
+
22
+ // 尝试加载任务队列(可选依赖)
23
+ let taskQueue = null;
24
+ try {
25
+ taskQueue = require("./queue/task-queue");
26
+ logger.info("📋 任务队列模块已加载");
27
+ } catch (error) {
28
+ logger.warn("⚠️ 任务队列模块加载失败(将使用直接执行模式)", {
29
+ error: error.message,
30
+ });
31
+ }
32
+
33
+ // 尝试加载 WebSocket 通知服务(可选依赖)
34
+ let wsNotifier = null;
35
+ try {
36
+ wsNotifier = require("./websocket/notifier");
37
+ logger.info("📡 WebSocket 通知模块已加载");
38
+ } catch (error) {
39
+ logger.warn("⚠️ WebSocket 通知模块未安装(可选功能)", {
40
+ suggestion: "运行 npm run install:ws 安装 WebSocket 支持",
41
+ });
42
+ }
43
+
44
+ const app = new Koa();
45
+ const router = new Router();
46
+
47
+ // CORS 配置
48
+ const corsOptions = {
49
+ origin: process.env.CORS_ORIGIN || "*", // 允许的来源,默认允许所有
50
+ allowMethods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
51
+ allowHeaders: ["Content-Type", "Authorization"],
52
+ credentials: true,
53
+ maxAge: 86400, // 预检请求缓存时间(24小时)
54
+ };
55
+
56
+ // 应用中间件
57
+ app.use(cors(corsOptions));
58
+ app.use(bodyParser());
59
+
60
+ // 静态文件服务(用于 WebSocket 监控面板)
61
+ app.use(koaStatic(path.join(__dirname, "../public")));
62
+
63
+ // 请求追踪中间件
64
+ app.use(async (ctx, next) => {
65
+ const requestId = generateRequestId();
66
+ const startTime = Date.now();
67
+
68
+ ctx.requestId = requestId;
69
+ ctx.logger = createContextLogger({ requestId });
70
+
71
+ ctx.logger.info("收到请求", {
72
+ method: ctx.method,
73
+ path: ctx.path,
74
+ ip: ctx.ip,
75
+ });
76
+
77
+ await next();
78
+
79
+ // 记录 HTTP 请求指标
80
+ const duration = Date.now() - startTime;
81
+ const isError = ctx.status >= 400;
82
+ metrics.recordHTTPRequest(duration, isError);
83
+
84
+ ctx.logger.info("请求完成", {
85
+ status: ctx.status,
86
+ duration: `${duration}ms`,
87
+ });
88
+ });
89
+
90
+ // 对所有 API 路由应用通用频率限制
91
+ app.use(apiLimiter);
92
+
93
+ // 全局错误处理
94
+ app.use(async (ctx, next) => {
95
+ try {
96
+ await next();
97
+ } catch (err) {
98
+ ctx.status = err.status || 500;
99
+ ctx.body = {
100
+ success: false,
101
+ error: err.message,
102
+ };
103
+ logger.error(`请求错误: ${err.message}`, {
104
+ path: ctx.path,
105
+ method: ctx.method,
106
+ error: err.stack,
107
+ requestId: ctx.requestId,
108
+ });
109
+ }
110
+ });
111
+
112
+ // 初始化服务
113
+ const claudeCLIService = new ClaudeCLIService(config.get("ai.claudeCLI"));
114
+ const wechatNotifier = new WeChatNotifier(
115
+ config.get("notification.wecomWebhook")
116
+ );
117
+
118
+ // 初始化统一通知服务
119
+ const notificationService = new NotificationService(
120
+ config.get("notification"),
121
+ wechatNotifier,
122
+ wsNotifier
123
+ );
124
+
125
+ // 选择 AI 引擎
126
+ function getAIService(engine) {
127
+ engine = engine || config.get("ai.defaultEngine");
128
+ if (engine === "claude-cli") {
129
+ return { service: claudeCLIService, name: "Claude CLI" };
130
+ } else {
131
+ throw new Error(`未知的 AI 引擎: ${engine}`);
132
+ }
133
+ }
134
+
135
+ // 🔑 设置任务队列处理器(如果启用了队列)
136
+ if (taskQueue) {
137
+ taskQueue.process(async (job) => {
138
+ const { taskId, params } = job.data;
139
+
140
+ logger.info(`📋 队列任务开始: ${taskId}`, {
141
+ taskId,
142
+ queueJobId: job.id,
143
+ });
144
+
145
+ try {
146
+ // 执行任务
147
+ await executeFixTask(taskId, params);
148
+
149
+ logger.info(`✅ 队列任务完成: ${taskId}`, {
150
+ taskId,
151
+ queueJobId: job.id,
152
+ });
153
+
154
+ return { success: true, taskId };
155
+ } catch (error) {
156
+ logger.error(`❌ 队列任务失败: ${taskId}`, {
157
+ taskId,
158
+ queueJobId: job.id,
159
+ error: error.message,
160
+ });
161
+
162
+ throw error; // 让队列处理重试逻辑
163
+ }
164
+ });
165
+
166
+ logger.info("✅ 任务队列处理器已设置");
167
+ }
168
+
169
+ /**
170
+ * 执行 Git 操作(提交、推送、合并)
171
+ * @param {string} taskId - 任务ID
172
+ * @param {string} workingDir - 工作目录
173
+ * @param {string} branchName - 分支名称
174
+ * @param {string} projectName - 项目名称
175
+ * @param {string} aiName - AI 引擎名称
176
+ * @param {object} fixResult - 修复结果
177
+ * @param {object} options - 选项
178
+ */
179
+ async function performGitOperations(
180
+ taskId,
181
+ workingDir,
182
+ branchName,
183
+ projectName,
184
+ aiName,
185
+ fixResult,
186
+ options
187
+ ) {
188
+ const {
189
+ runTests = false,
190
+ autoPush = true,
191
+ requirement,
192
+ mergeToAlpha = false,
193
+ mergeToBeta = false,
194
+ mergeToMaster = false,
195
+ } = options;
196
+
197
+ // 1. 运行测试(可选)
198
+ let testsPassed = true;
199
+ if (runTests) {
200
+ taskManager.updateProgress(taskId, "running_tests", "运行测试");
201
+ if (wsNotifier) {
202
+ wsNotifier.notifyTaskProgress(taskId, "running_tests", "运行测试", 60);
203
+ }
204
+ testsPassed = GitService.runTests(workingDir);
205
+ if (!testsPassed) {
206
+ logger.warn("⚠️ 测试未通过,但继续提交流程", { taskId });
207
+ }
208
+ }
209
+
210
+ // 2. 提交更改
211
+ taskManager.updateProgress(taskId, "committing", "提交更改");
212
+ if (wsNotifier) {
213
+ wsNotifier.notifyTaskProgress(taskId, "committing", "提交更改", 70);
214
+ }
215
+ const commitMessage = `fix: ${requirement.substring(0, 50)}${
216
+ requirement.length > 50 ? "..." : ""
217
+ }
218
+
219
+ 🤖 由 ${aiName} 自动生成
220
+
221
+ 修改文件:
222
+ ${fixResult.changedFiles.map((f) => `- ${f}`).join("\n")}`;
223
+
224
+ const hasCommit = GitService.commit(workingDir, commitMessage);
225
+
226
+ // 3. 推送到远程(可选)
227
+ let pushed = false;
228
+ if (hasCommit && autoPush) {
229
+ taskManager.updateProgress(taskId, "pushing", "推送到远程仓库");
230
+ if (wsNotifier) {
231
+ wsNotifier.notifyTaskProgress(taskId, "pushing", "推送到远程仓库", 80);
232
+ }
233
+ pushed = await GitService.push(workingDir, branchName);
234
+ }
235
+
236
+ // 4. 合并到目标分支(可选)
237
+ let mergeResults = {};
238
+ const targetBranches = [];
239
+
240
+ if (mergeToAlpha) targetBranches.push("alpha");
241
+ if (mergeToBeta) targetBranches.push("beta");
242
+ if (mergeToMaster) targetBranches.push("master");
243
+
244
+ if (targetBranches.length > 0) {
245
+ taskManager.updateProgress(
246
+ taskId,
247
+ "merging",
248
+ `合并到 ${targetBranches.join(", ")}`
249
+ );
250
+ if (wsNotifier) {
251
+ wsNotifier.notifyTaskProgress(
252
+ taskId,
253
+ "merging",
254
+ `合并到 ${targetBranches.join(", ")}`,
255
+ 90
256
+ );
257
+ }
258
+ mergeResults = await GitService.mergeToMultipleBranches(
259
+ workingDir,
260
+ branchName,
261
+ targetBranches
262
+ );
263
+
264
+ logger.info("📊 合并结果:", { taskId });
265
+ for (const [branch, success] of Object.entries(mergeResults)) {
266
+ logger.info(` - ${branch}: ${success ? "✅ 成功" : "❌ 失败"}`, {
267
+ taskId,
268
+ });
269
+ }
270
+ }
271
+
272
+ // 5. 发送成功通知
273
+ taskManager.updateProgress(
274
+ taskId,
275
+ "sending_success_notification",
276
+ "发送成功通知"
277
+ );
278
+
279
+ // 6. 完成任务
280
+ const result = {
281
+ success: true,
282
+ aiEngine: aiName,
283
+ projectName,
284
+ projectPath: workingDir,
285
+ branch: branchName,
286
+ branchName,
287
+ changedFiles: fixResult.changedFiles,
288
+ summary: fixResult.summary,
289
+ fullOutput: fixResult.fullOutput,
290
+ conversationTurns: fixResult.conversationTurns,
291
+ testsPassed,
292
+ pushed,
293
+ mergeResults,
294
+ };
295
+
296
+ taskManager.completeTask(taskId, result);
297
+
298
+ // 发送任务完成通知
299
+ await notificationService.notifyTaskComplete(taskId, result);
300
+
301
+ // 注销进程
302
+ taskManager.unregisterProcess(taskId);
303
+
304
+ // 🔑 释放项目锁
305
+ projectLock.releaseLock(workingDir, taskId);
306
+ logger.info(`🔓 任务完成,释放项目锁: ${workingDir}`, { taskId });
307
+
308
+ logger.info("=".repeat(60));
309
+ logger.info(`✅ 任务 ${taskId} 完成!`, { taskId });
310
+ logger.info("=".repeat(60));
311
+ logger.info(JSON.stringify(result, null, 2), { taskId });
312
+
313
+ return result;
314
+ }
315
+
316
+ /**
317
+ * 异步执行修复任务
318
+ */
319
+ async function executeFixTask(taskId, params) {
320
+ const {
321
+ projectPath,
322
+ repoUrl,
323
+ requirement,
324
+ runTests = false,
325
+ autoPush = true,
326
+ aiEngine,
327
+ baseBranch = null, // 基准分支
328
+ createNewBranch = true, // 是否创建新分支
329
+ username = "system", // 用户名
330
+ mergeToAlpha = false, // 是否合并到 alpha
331
+ mergeToBeta = false, // 是否合并到 beta
332
+ mergeToMaster = false, // 是否合并到 master
333
+ } = params;
334
+
335
+ let workingDir = null;
336
+ let branchName = null;
337
+ let cancelled = false;
338
+ let childProcessCancel = null; // 子进程取消函数
339
+
340
+ // 注册取消函数(初始版本,只设置标志位)
341
+ taskManager.registerProcess(taskId, {
342
+ cancel: () => {
343
+ cancelled = true;
344
+ logger.warn(`⚠️ 任务 ${taskId} 收到取消请求`, { taskId });
345
+
346
+ // 如果子进程已经启动,也调用子进程的取消函数
347
+ if (childProcessCancel) {
348
+ logger.info("🛑 同时终止子进程...", { taskId });
349
+ childProcessCancel();
350
+ }
351
+ },
352
+ });
353
+
354
+ try {
355
+ taskManager.startTask(taskId);
356
+
357
+ // WebSocket 通知:任务开始
358
+ if (wsNotifier) {
359
+ wsNotifier.notifyTaskStatus(taskId, "running", {
360
+ message: "任务已开始",
361
+ requirement,
362
+ projectPath: workingDir,
363
+ });
364
+ }
365
+
366
+ // 检查是否已取消
367
+ if (cancelled) {
368
+ throw new Error("任务已取消");
369
+ }
370
+
371
+ taskManager.updateProgress(taskId, "selecting_engine", "选择 AI 引擎");
372
+
373
+ // WebSocket 通知:进度更新
374
+ if (wsNotifier) {
375
+ wsNotifier.notifyTaskProgress(
376
+ taskId,
377
+ "selecting_engine",
378
+ "选择 AI 引擎",
379
+ 5
380
+ );
381
+ }
382
+
383
+ const { service: aiService, name: aiName } = getAIService(aiEngine);
384
+
385
+ logger.info("=".repeat(60));
386
+ logger.info(`🚀 开始任务 ${taskId}`, { taskId });
387
+ logger.info("=".repeat(60));
388
+ logger.info(`📋 需求: ${requirement}`, { taskId });
389
+ logger.info(`🤖 AI 引擎: ${aiName}`, { taskId });
390
+
391
+ // 1. 确定工作目录
392
+ taskManager.updateProgress(taskId, "preparing_project", "准备项目目录");
393
+ if (wsNotifier) {
394
+ wsNotifier.notifyTaskProgress(
395
+ taskId,
396
+ "preparing_project",
397
+ "准备项目目录",
398
+ 10
399
+ );
400
+ }
401
+
402
+ if (projectPath && GitService.projectExists(projectPath)) {
403
+ workingDir = projectPath;
404
+ logger.info(`📂 使用现有项目: ${workingDir}`, { taskId });
405
+
406
+ // 🔑 获取项目锁(如果项目已被其他任务锁定,当前任务会等待)
407
+ await projectLock.acquireLock(workingDir, taskId);
408
+
409
+ // 🔑 使用智能准备方法(处理各种分支状态)
410
+ await GitService.prepareExistingProject(workingDir, baseBranch);
411
+ } else if (repoUrl) {
412
+ const repoName = path.basename(repoUrl, ".git");
413
+ workingDir = path.join(config.get("server.workDir"), repoName);
414
+
415
+ if (GitService.projectExists(workingDir)) {
416
+ logger.info(`📂 项目已存在: ${workingDir}`, { taskId });
417
+
418
+ // 🔑 获取项目锁(如果项目已被其他任务锁定,当前任务会等待)
419
+ await projectLock.acquireLock(workingDir, taskId);
420
+
421
+ // 🔑 使用智能准备方法(处理各种分支状态)
422
+ await GitService.prepareExistingProject(workingDir, baseBranch);
423
+ } else {
424
+ logger.info(`📦 项目不存在,克隆仓库...`, { taskId });
425
+ GitService.cloneRepo(repoUrl, workingDir);
426
+
427
+ // 🔑 获取项目锁(新克隆的项目也需要锁定)
428
+ await projectLock.acquireLock(workingDir, taskId);
429
+ }
430
+ } else {
431
+ throw new Error("项目路径不存在且未提供仓库地址");
432
+ }
433
+
434
+ const projectName = GitService.getProjectName(workingDir);
435
+
436
+ // 发送开始通知
437
+ taskManager.updateProgress(taskId, "sending_notification", "发送开始通知");
438
+ await notificationService.notifyTaskStart(taskId, projectName, requirement);
439
+
440
+ // 2. 创建功能分支或切换到基准分支
441
+ if (createNewBranch) {
442
+ taskManager.updateProgress(taskId, "creating_branch", "创建功能分支");
443
+ if (wsNotifier) {
444
+ wsNotifier.notifyTaskProgress(
445
+ taskId,
446
+ "creating_branch",
447
+ "创建功能分支",
448
+ 20
449
+ );
450
+ }
451
+ branchName = GitService.createBranch(
452
+ workingDir,
453
+ username,
454
+ taskId,
455
+ baseBranch
456
+ );
457
+ } else {
458
+ taskManager.updateProgress(taskId, "checkout_branch", "切换到基准分支");
459
+ if (wsNotifier) {
460
+ wsNotifier.notifyTaskProgress(
461
+ taskId,
462
+ "checkout_branch",
463
+ "切换到基准分支",
464
+ 20
465
+ );
466
+ }
467
+ branchName = GitService.checkoutBranch(workingDir, baseBranch);
468
+ logger.info(`📌 直接在分支 ${branchName} 上进行开发`, { taskId });
469
+ }
470
+
471
+ // 3. 使用 AI 修复代码
472
+ taskManager.updateProgress(
473
+ taskId,
474
+ "fixing_code",
475
+ `使用 ${aiName} 修复代码`
476
+ );
477
+ if (wsNotifier) {
478
+ wsNotifier.notifyTaskProgress(
479
+ taskId,
480
+ "fixing_code",
481
+ `使用 ${aiName} 修复代码`,
482
+ 30
483
+ );
484
+ }
485
+ logger.info(`🤖 启动 ${aiName} 进行代码修复...`, { taskId });
486
+ const fixResult = await aiService.fixCode(workingDir, requirement, {
487
+ onProcessCreated: (processInfo) => {
488
+ // 保存子进程的取消函数
489
+ if (processInfo.cancel) {
490
+ childProcessCancel = processInfo.cancel;
491
+ logger.debug(`已保存任务 ${taskId} 的子进程取消函数`, { taskId });
492
+ }
493
+ },
494
+ });
495
+
496
+ if (!fixResult.success) {
497
+ throw new Error(`${aiName} 修复失败`);
498
+ }
499
+
500
+ logger.info("-".repeat(60));
501
+ logger.info("📊 修复结果:", { taskId });
502
+ logger.info(` - 修改文件数: ${fixResult.changedFiles.length}`, {
503
+ taskId,
504
+ });
505
+ logger.info(` - 对话轮次: ${fixResult.conversationTurns || 1}`, {
506
+ taskId,
507
+ });
508
+ logger.info("-".repeat(60));
509
+
510
+ // 4. 检查是否需要等待确认
511
+ const needsApproval = params.needsApproval !== false; // 默认需要确认
512
+
513
+ if (needsApproval) {
514
+ // 🔑 等待开发者确认
515
+ logger.info("⏸️ AI 修复完成,等待开发者确认...", { taskId });
516
+ logger.info(` - 修改文件数: ${fixResult.changedFiles.length}`, { taskId });
517
+ logger.info(` - 查看修改: GET /api/tasks/${taskId}/changes`, { taskId });
518
+ logger.info(` - 确认修改: POST /api/tasks/${taskId}/approve`, { taskId });
519
+ logger.info(` - 拒绝修改: POST /api/tasks/${taskId}/reject`, { taskId });
520
+
521
+ // 保存修改信息到任务中,同时更新状态为 waiting_approval
522
+ taskManager.updateTask(taskId, {
523
+ status: "waiting_approval",
524
+ progress: {
525
+ step: "waiting_approval",
526
+ message: "等待开发者确认修改",
527
+ },
528
+ fixResult,
529
+ workingDir,
530
+ branchName,
531
+ projectName,
532
+ aiEngine: aiName,
533
+ // 保存后续需要的参数
534
+ pendingGitOperations: {
535
+ runTests,
536
+ autoPush,
537
+ requirement,
538
+ mergeToAlpha,
539
+ mergeToBeta,
540
+ mergeToMaster,
541
+ },
542
+ });
543
+
544
+ // 🔑 通知等待确认
545
+ notificationService.notifyWaitingApproval(taskId, fixResult.changedFiles);
546
+
547
+ if (wsNotifier) {
548
+ wsNotifier.notifyTaskProgress(
549
+ taskId,
550
+ "waiting_approval",
551
+ "AI 修复完成,等待开发者确认",
552
+ 50
553
+ );
554
+ }
555
+
556
+ // 🔑 任务暂停,等待确认(不执行后续 Git 操作)
557
+ return;
558
+ }
559
+
560
+ // 如果不需要确认,继续执行 Git 操作
561
+ await performGitOperations(
562
+ taskId,
563
+ workingDir,
564
+ branchName,
565
+ projectName,
566
+ aiName,
567
+ fixResult,
568
+ {
569
+ runTests,
570
+ autoPush,
571
+ requirement,
572
+ mergeToAlpha,
573
+ mergeToBeta,
574
+ mergeToMaster,
575
+ }
576
+ );
577
+ } catch (error) {
578
+ logger.error(`❌ 任务 ${taskId} 失败: ${error.message}`, {
579
+ taskId,
580
+ error: error.stack,
581
+ });
582
+
583
+ // 注销进程
584
+ taskManager.unregisterProcess(taskId);
585
+
586
+ // WebSocket 通知:任务失败
587
+ if (wsNotifier) {
588
+ wsNotifier.notifyTaskFailed(taskId, error);
589
+ }
590
+
591
+ // 发送失败通知
592
+ if (workingDir) {
593
+ const projectName = GitService.getProjectName(workingDir);
594
+ await notificationService.notifyTaskFailure(
595
+ taskId,
596
+ projectName,
597
+ requirement,
598
+ error
599
+ );
600
+ }
601
+
602
+ taskManager.failTask(taskId, error);
603
+ } finally {
604
+ // 🔑 只在非等待确认状态时释放锁
605
+ // 等待确认状态需要在 approve/reject 后才释放锁
606
+ const task = taskManager.getTask(taskId);
607
+ if (workingDir && task?.status !== "waiting_approval") {
608
+ projectLock.releaseLock(workingDir, taskId);
609
+ logger.info(`🔓 释放项目锁: ${workingDir}`, { taskId });
610
+ } else if (task?.status === "waiting_approval") {
611
+ logger.info(`🔒 任务等待确认,保持项目锁: ${workingDir}`, { taskId });
612
+ }
613
+ }
614
+
615
+ // 清理旧任务
616
+ taskManager.cleanup();
617
+ }
618
+
619
+ /**
620
+ * 健康检查接口(增强版)
621
+ * GET /health
622
+ */
623
+ router.get("/health", async (ctx) => {
624
+ const healthStatus = {
625
+ status: "ok",
626
+ service: "claude-auto-fix",
627
+ timestamp: new Date().toISOString(),
628
+ uptime: process.uptime(),
629
+ memory: process.memoryUsage(),
630
+ metrics: metrics.getMetrics(),
631
+ };
632
+
633
+ ctx.body = healthStatus;
634
+ });
635
+
636
+ /**
637
+ * 性能指标接口
638
+ * GET /api/metrics
639
+ */
640
+ router.get("/api/metrics", async (ctx) => {
641
+ ctx.body = {
642
+ success: true,
643
+ metrics: metrics.getMetrics(),
644
+ timestamp: new Date().toISOString(),
645
+ };
646
+ });
647
+
648
+ /**
649
+ * 配置信息接口(敏感信息已脱敏)
650
+ * GET /api/config
651
+ */
652
+ router.get("/api/config", authenticateAPI, async (ctx) => {
653
+ const configInfo = config.getAll();
654
+
655
+ // 脱敏处理
656
+ const safeConfig = {
657
+ server: configInfo.server,
658
+ ai: {
659
+ defaultEngine: configInfo.ai.defaultEngine,
660
+ claudeCLI: {
661
+ permissionMode: configInfo.ai.claudeCLI.permissionMode,
662
+ maxTurns: configInfo.ai.claudeCLI.maxTurns,
663
+ },
664
+ },
665
+ security: {
666
+ corsOrigin: configInfo.security.corsOrigin,
667
+ rateLimit: configInfo.security.rateLimit,
668
+ apiKeyConfigured: !!configInfo.security.apiKey,
669
+ },
670
+ notification: {
671
+ wecomConfigured: !!configInfo.notification.wecomWebhook,
672
+ },
673
+ };
674
+
675
+ ctx.body = {
676
+ success: true,
677
+ config: safeConfig,
678
+ };
679
+ });
680
+
681
+ /**
682
+ * 异步修复接口(推荐)
683
+ * POST /api/fix-bug
684
+ *
685
+ * Body:
686
+ * {
687
+ * "projectPath": "e:\\demo\\my-project", // 可选,本地项目路径
688
+ * "repoUrl": "https://github.com/user/repo.git", // 可选,远程仓库地址
689
+ * "requirement": "修复登录按钮点击无反应的问题", // 必填,修复要求
690
+ * "runTests": false, // 可选,是否运行测试,默认 false
691
+ * "autoPush": true, // 可选,是否自动推送,默认 true
692
+ * "aiEngine": "claude-cli", // 可选,AI 引擎
693
+ * "baseBranch": "develop", // 可选,基准分支,默认自动查找 main/master
694
+ * "createNewBranch": true, // 可选,是否创建新分支,默认 true
695
+ * "username": "zhangsan", // 可选,用户名(用于分支命名),默认 system
696
+ * "mergeToAlpha": false, // 可选,是否合并到 alpha 分支
697
+ * "mergeToBeta": false, // 可选,是否合并到 beta 分支
698
+ * "mergeToMaster": false, // 可选,是否合并到 master 分支
699
+ * "needsApproval": true // 可选,是否需要人工确认后才执行 Git 操作,默认 true
700
+ * }
701
+ *
702
+ * Response:
703
+ * {
704
+ * "success": true,
705
+ * "taskId": "task-1234567890-abc123",
706
+ * "message": "任务已创建,正在后台处理"
707
+ * }
708
+ */
709
+ router.post("/api/fix-bug", strictLimiter, async (ctx) => {
710
+ try {
711
+ const {
712
+ projectPath: rawProjectPath,
713
+ repoUrl: rawRepoUrl,
714
+ requirement: rawRequirement,
715
+ runTests = false,
716
+ autoPush = true,
717
+ aiEngine: rawAiEngine,
718
+ baseBranch: rawBaseBranch = null,
719
+ createNewBranch = true,
720
+ username: rawUsername = "system",
721
+ mergeToAlpha = false,
722
+ mergeToBeta = false,
723
+ mergeToMaster = false,
724
+ needsApproval = true, // 是否需要人工确认,默认 true
725
+ } = ctx.request.body;
726
+
727
+ // 验证和清理输入
728
+ if (!rawRequirement) {
729
+ ctx.status = 400;
730
+ ctx.body = {
731
+ success: false,
732
+ error: "缺少必填参数: requirement",
733
+ };
734
+ return;
735
+ }
736
+
737
+ // 清理需求描述
738
+ const requirement = InputSanitizer.sanitizeRequirement(rawRequirement);
739
+
740
+ // 清理用户名
741
+ const username = InputSanitizer.sanitizeUsername(rawUsername);
742
+
743
+ // 验证 AI 引擎
744
+ const aiEngine = InputSanitizer.validateAIEngine(rawAiEngine);
745
+
746
+ // 验证基准分支
747
+ const baseBranch = InputSanitizer.validateBranchName(rawBaseBranch);
748
+
749
+ // 清理项目路径和仓库 URL
750
+ let projectPath = rawProjectPath;
751
+ let repoUrl = rawRepoUrl;
752
+
753
+ if (projectPath) {
754
+ projectPath = InputSanitizer.sanitizeProjectPath(projectPath);
755
+ }
756
+
757
+ if (repoUrl) {
758
+ repoUrl = InputSanitizer.sanitizeRepoUrl(repoUrl);
759
+ }
760
+
761
+ if (!projectPath && !repoUrl) {
762
+ ctx.status = 400;
763
+ ctx.body = {
764
+ success: false,
765
+ error: "必须提供 projectPath 或 repoUrl 之一",
766
+ };
767
+ return;
768
+ }
769
+
770
+ // 创建异步任务
771
+ const taskId = taskManager.createTask(requirement, projectPath || repoUrl, {
772
+ projectPath,
773
+ repoUrl,
774
+ runTests,
775
+ autoPush,
776
+ aiEngine,
777
+ baseBranch,
778
+ createNewBranch,
779
+ username,
780
+ mergeToAlpha,
781
+ mergeToBeta,
782
+ mergeToMaster,
783
+ needsApproval,
784
+ });
785
+
786
+ const params = {
787
+ projectPath,
788
+ repoUrl,
789
+ requirement,
790
+ runTests,
791
+ autoPush,
792
+ aiEngine,
793
+ baseBranch,
794
+ createNewBranch,
795
+ username,
796
+ mergeToAlpha,
797
+ mergeToBeta,
798
+ mergeToMaster,
799
+ needsApproval,
800
+ };
801
+
802
+ // 🔑 使用任务队列(如果启用)或直接执行
803
+ if (taskQueue) {
804
+ // 添加到任务队列
805
+ try {
806
+ const queueJob = await taskQueue.addTask({
807
+ taskId,
808
+ params,
809
+ priority: 0, // 可以根据需求设置优先级
810
+ });
811
+
812
+ logger.info(`📋 任务已加入队列: ${taskId}`, {
813
+ taskId,
814
+ queueJobId: queueJob.id,
815
+ queueType: queueJob.queueType,
816
+ });
817
+
818
+ ctx.body = {
819
+ success: true,
820
+ id: taskId,
821
+ taskId,
822
+ message: "任务已加入队列,等待处理",
823
+ statusUrl: `/api/tasks/${taskId}`,
824
+ queueInfo: {
825
+ queueJobId: queueJob.id,
826
+ queueType: queueJob.queueType,
827
+ },
828
+ };
829
+ } catch (error) {
830
+ logger.error(`队列添加失败,降级为直接执行: ${error.message}`);
831
+
832
+ // 降级为直接执行
833
+ executeFixTask(taskId, params).catch((err) => {
834
+ logger.error(`任务 ${taskId} 异步执行出错: ${err.message}`, {
835
+ taskId,
836
+ error: err.stack,
837
+ });
838
+ });
839
+
840
+ ctx.body = {
841
+ success: true,
842
+ id: taskId,
843
+ taskId,
844
+ message: "任务已创建,正在后台处理(直接执行模式)",
845
+ statusUrl: `/api/tasks/${taskId}`,
846
+ };
847
+ }
848
+ } else {
849
+ // 直接在后台异步执行任务
850
+ executeFixTask(taskId, params).catch((err) => {
851
+ logger.error(`任务 ${taskId} 异步执行出错: ${err.message}`, {
852
+ taskId,
853
+ error: err.stack,
854
+ });
855
+ });
856
+
857
+ ctx.body = {
858
+ success: true,
859
+ id: taskId,
860
+ taskId,
861
+ message: "任务已创建,正在后台处理",
862
+ statusUrl: `/api/tasks/${taskId}`,
863
+ };
864
+ }
865
+ } catch (error) {
866
+ ctx.status = 400;
867
+ ctx.body = {
868
+ success: false,
869
+ error: error.message,
870
+ };
871
+ }
872
+ });
873
+
874
+ /**
875
+ * 查询任务状态
876
+ * GET /api/tasks/:taskId
877
+ */
878
+ router.get("/api/tasks/:taskId", async (ctx) => {
879
+ const { taskId } = ctx.params;
880
+ const task = taskManager.getTask(taskId);
881
+
882
+ if (!task) {
883
+ ctx.status = 404;
884
+ ctx.body = {
885
+ success: false,
886
+ error: "任务不存在",
887
+ };
888
+ return;
889
+ }
890
+
891
+ ctx.body = {
892
+ success: true,
893
+ task: {
894
+ ...task,
895
+ // 添加 taskId 别名(前端使用 taskId 而不是 id)
896
+ taskId: task.id,
897
+ // 添加便捷字段
898
+ isRunning: task.status === "running",
899
+ isCompleted: task.status === "completed",
900
+ isFailed: task.status === "failed",
901
+ isPending: task.status === "pending",
902
+ isWaitingApproval: task.status === "waiting_approval",
903
+ },
904
+ };
905
+ });
906
+
907
+ /**
908
+ * 取消任务
909
+ * POST /api/tasks/:taskId/cancel
910
+ */
911
+ router.post("/api/tasks/:taskId/cancel", async (ctx) => {
912
+ try {
913
+ const { taskId } = ctx.params;
914
+
915
+ taskManager.cancelTask(taskId);
916
+
917
+ ctx.body = {
918
+ success: true,
919
+ message: "任务已取消",
920
+ taskId,
921
+ };
922
+ } catch (error) {
923
+ ctx.status = 400;
924
+ ctx.body = {
925
+ success: false,
926
+ error: error.message,
927
+ };
928
+ }
929
+ });
930
+
931
+ /**
932
+ * 查看任务修改内容
933
+ * GET /api/tasks/:taskId/changes
934
+ */
935
+ router.get("/api/tasks/:taskId/changes", async (ctx) => {
936
+ try {
937
+ const { taskId } = ctx.params;
938
+ const task = taskManager.getTask(taskId);
939
+
940
+ if (!task) {
941
+ ctx.status = 404;
942
+ ctx.body = {
943
+ success: false,
944
+ error: "任务不存在",
945
+ };
946
+ return;
947
+ }
948
+
949
+ // 只有等待确认状态的任务才能查看修改
950
+ if (task.status !== "waiting_approval") {
951
+ ctx.status = 400;
952
+ ctx.body = {
953
+ success: false,
954
+ error: `任务状态为 ${task.status},无法查看修改内容`,
955
+ hint: "只有等待确认状态的任务才能查看修改",
956
+ };
957
+ return;
958
+ }
959
+
960
+ const { fixResult, workingDir, branchName } = task;
961
+ logger.info(`查看任务 ${taskId} 的修改内容`);
962
+
963
+ // 优先使用已保存的 gitDiff 数据
964
+ if (fixResult?.gitDiff) {
965
+ logger.info("使用已保存的 gitDiff 数据");
966
+
967
+ ctx.body = {
968
+ success: true,
969
+ taskId,
970
+ changes: {
971
+ fullDiff: fixResult.gitDiff.fullDiff,
972
+ actualChangedFiles: fixResult.gitDiff.actualChangedFiles,
973
+ fileChanges: fixResult.gitDiff.fileChanges,
974
+ untrackedFiles: fixResult.gitDiff.untrackedFiles,
975
+ summary: fixResult.summary || "",
976
+ conversationTurns: fixResult.conversationTurns,
977
+ fullOutput: fixResult.fullOutput,
978
+ },
979
+ projectInfo: {
980
+ workingDir,
981
+ branchName,
982
+ },
983
+ actions: {
984
+ approve: `/api/tasks/${taskId}/approve`,
985
+ reject: `/api/tasks/${taskId}/reject`,
986
+ },
987
+ };
988
+ return;
989
+ }
990
+
991
+ // 降级方案: 实时获取 git diff (兼容旧数据或 gitDiff 不存在的情况)
992
+ logger.info("gitDiff 不存在,实时获取 git diff");
993
+
994
+ // 使用 GitService 获取 diff
995
+ const gitDiffResult = GitService.getWorkspaceDiff(workingDir);
996
+
997
+ // 对比 AI 记录的文件和实际变更的文件
998
+ const recordedFiles = fixResult?.changedFiles || [];
999
+ const recordedSet = new Set(recordedFiles);
1000
+ const actualSet = new Set(gitDiffResult.actualChangedFiles);
1001
+
1002
+ // 找出 AI 遗漏的文件(实际变更了但没记录)
1003
+ const missedFiles = gitDiffResult.actualChangedFiles.filter(
1004
+ (f) => !recordedSet.has(f)
1005
+ );
1006
+
1007
+ // 找出 AI 多记录的文件(记录了但实际没变更)
1008
+ const extraFiles = recordedFiles.filter((f) => !actualSet.has(f));
1009
+
1010
+ // 为文件添加 isMissed 标记
1011
+ const fileChanges = gitDiffResult.fileChanges.map((fileChange) => ({
1012
+ ...fileChange,
1013
+ isMissed: missedFiles.includes(fileChange.file),
1014
+ }));
1015
+
1016
+ ctx.body = {
1017
+ success: true,
1018
+ taskId,
1019
+ changes: {
1020
+ fullDiff: gitDiffResult.fullDiff, // 完整的 diff
1021
+ actualChangedFiles: gitDiffResult.actualChangedFiles, // 实际变更的文件列表(包括修改+新增)
1022
+ recordedFiles, // AI 记录的文件列表
1023
+ missedFiles, // AI 遗漏的文件
1024
+ extraFiles, // AI 多记录的文件
1025
+ fileChanges, // 每个文件的详细 diff
1026
+ stagedFiles: gitDiffResult.stagedFiles, // 已暂存的文件
1027
+ unstagedFiles: gitDiffResult.unstagedFiles, // 未暂存的文件
1028
+ untrackedFiles: gitDiffResult.untrackedFiles, // 未跟踪的新文件
1029
+ summary: fixResult?.summary || "无摘要信息",
1030
+ conversationTurns: fixResult?.conversationTurns,
1031
+ fullOutput: fixResult?.fullOutput,
1032
+ },
1033
+ projectInfo: {
1034
+ workingDir,
1035
+ branchName,
1036
+ },
1037
+ actions: {
1038
+ approve: `/api/tasks/${taskId}/approve`,
1039
+ reject: `/api/tasks/${taskId}/reject`,
1040
+ },
1041
+ };
1042
+ } catch (error) {
1043
+ ctx.status = 500;
1044
+ ctx.body = {
1045
+ success: false,
1046
+ error: error.message,
1047
+ };
1048
+ }
1049
+ });
1050
+
1051
+ /**
1052
+ * 确认任务修改并执行 Git 操作
1053
+ * POST /api/tasks/:taskId/approve
1054
+ */
1055
+ router.post("/api/tasks/:taskId/approve", async (ctx) => {
1056
+ try {
1057
+ const { taskId } = ctx.params;
1058
+ const task = taskManager.getTask(taskId);
1059
+
1060
+ if (!task) {
1061
+ ctx.status = 404;
1062
+ ctx.body = {
1063
+ success: false,
1064
+ error: "任务不存在",
1065
+ };
1066
+ return;
1067
+ }
1068
+
1069
+ if (task.status !== "waiting_approval") {
1070
+ ctx.status = 400;
1071
+ ctx.body = {
1072
+ success: false,
1073
+ error: `任务状态为 ${task.status},无法确认`,
1074
+ hint: "只有等待确认状态的任务才能确认",
1075
+ };
1076
+ return;
1077
+ }
1078
+
1079
+ const {
1080
+ fixResult,
1081
+ workingDir,
1082
+ branchName,
1083
+ projectName,
1084
+ aiEngine,
1085
+ pendingGitOperations,
1086
+ } = task;
1087
+
1088
+ if (!fixResult || !workingDir || !pendingGitOperations) {
1089
+ ctx.status = 400;
1090
+ ctx.body = {
1091
+ success: false,
1092
+ error: "任务信息不完整",
1093
+ };
1094
+ return;
1095
+ }
1096
+
1097
+ logger.info(`✅ 开发者确认任务 ${taskId} 的修改,开始执行 Git 操作`, {
1098
+ taskId,
1099
+ });
1100
+
1101
+ // 更新任务状态
1102
+ taskManager.updateProgress(taskId, "running", "开发者已确认,执行 Git 操作");
1103
+
1104
+ if (wsNotifier) {
1105
+ wsNotifier.notifyTaskProgress(
1106
+ taskId,
1107
+ "approved",
1108
+ "开发者已确认修改",
1109
+ 55
1110
+ );
1111
+ }
1112
+
1113
+ // 异步执行 Git 操作
1114
+ performGitOperations(
1115
+ taskId,
1116
+ workingDir,
1117
+ branchName,
1118
+ projectName,
1119
+ aiEngine,
1120
+ fixResult,
1121
+ pendingGitOperations
1122
+ ).catch((err) => {
1123
+ logger.error(`任务 ${taskId} Git 操作失败: ${err.message}`, {
1124
+ taskId,
1125
+ error: err.stack,
1126
+ });
1127
+
1128
+ taskManager.failTask(taskId, err);
1129
+
1130
+ if (wsNotifier) {
1131
+ wsNotifier.notifyTaskFailed(taskId, err);
1132
+ }
1133
+
1134
+ // 🔑 Git 操作失败时释放锁
1135
+ if (workingDir) {
1136
+ projectLock.releaseLock(workingDir, taskId);
1137
+ logger.info(`🔓 Git 操作失败,释放项目锁: ${workingDir}`, { taskId });
1138
+ }
1139
+ });
1140
+
1141
+ ctx.body = {
1142
+ success: true,
1143
+ message: "已确认修改,正在执行 Git 操作",
1144
+ taskId,
1145
+ statusUrl: `/api/tasks/${taskId}`,
1146
+ };
1147
+ } catch (error) {
1148
+ ctx.status = 500;
1149
+ ctx.body = {
1150
+ success: false,
1151
+ error: error.message,
1152
+ };
1153
+ }
1154
+ });
1155
+
1156
+ /**
1157
+ * 拒绝任务修改
1158
+ * POST /api/tasks/:taskId/reject
1159
+ */
1160
+ router.post("/api/tasks/:taskId/reject", async (ctx) => {
1161
+ try {
1162
+ const { taskId } = ctx.params;
1163
+ const { reason } = ctx.request.body;
1164
+
1165
+ const task = taskManager.getTask(taskId);
1166
+
1167
+ if (!task) {
1168
+ ctx.status = 404;
1169
+ ctx.body = {
1170
+ success: false,
1171
+ error: "任务不存在",
1172
+ };
1173
+ return;
1174
+ }
1175
+
1176
+ if (task.status !== "waiting_approval") {
1177
+ ctx.status = 400;
1178
+ ctx.body = {
1179
+ success: false,
1180
+ error: `任务状态为 ${task.status},无法拒绝`,
1181
+ hint: "只有等待确认状态的任务才能拒绝",
1182
+ };
1183
+ return;
1184
+ }
1185
+
1186
+ logger.warn(`❌ 开发者拒绝任务 ${taskId} 的修改`, {
1187
+ taskId,
1188
+ reason: reason || "未提供原因",
1189
+ });
1190
+
1191
+ // 还原代码修改
1192
+ const { workingDir, branchName } = task;
1193
+ let revertResult = { success: false, message: "" };
1194
+
1195
+ if (workingDir) {
1196
+ try {
1197
+ logger.info(`开始还原工作目录 ${workingDir} 的代码修改`);
1198
+
1199
+ // 1. 重置所有已暂存的更改
1200
+ try {
1201
+ ExecUtil.execSync("git reset HEAD .", {
1202
+ cwd: workingDir,
1203
+ });
1204
+ logger.info("已重置暂存区");
1205
+ } catch (err) {
1206
+ logger.warn("重置暂存区失败(可能没有暂存内容):", err.message);
1207
+ }
1208
+
1209
+ // 2. 还原所有已跟踪文件的修改
1210
+ try {
1211
+ ExecUtil.execSync("git checkout .", {
1212
+ cwd: workingDir,
1213
+ });
1214
+ logger.info("已还原所有已跟踪文件的修改");
1215
+ } catch (err) {
1216
+ logger.error("还原文件失败:", err.message);
1217
+ throw err;
1218
+ }
1219
+
1220
+ // 3. 删除所有未跟踪的文件(新增的文件)
1221
+ try {
1222
+ const untrackedFiles = ExecUtil.execSync(
1223
+ "git ls-files --others --exclude-standard",
1224
+ {
1225
+ cwd: workingDir,
1226
+ }
1227
+ )
1228
+ .split("\n")
1229
+ .filter(Boolean);
1230
+
1231
+ if (untrackedFiles.length > 0) {
1232
+ const fs = require("fs");
1233
+ for (const file of untrackedFiles) {
1234
+ const fullPath = path.join(workingDir, file);
1235
+ if (fs.existsSync(fullPath)) {
1236
+ fs.unlinkSync(fullPath);
1237
+ logger.info(`已删除新增文件: ${file}`);
1238
+ }
1239
+ }
1240
+ }
1241
+ } catch (err) {
1242
+ logger.warn("删除未跟踪文件失败:", err.message);
1243
+ }
1244
+
1245
+ // 4. 如果有分支,切换回主分支并删除工作分支
1246
+ if (branchName && branchName !== "main" && branchName !== "master") {
1247
+ try {
1248
+ // 获取默认分支
1249
+ const defaultBranch = ExecUtil.execSync(
1250
+ "git symbolic-ref refs/remotes/origin/HEAD | sed 's@^refs/remotes/origin/@@'",
1251
+ {
1252
+ cwd: workingDir,
1253
+ }
1254
+ ).trim() || "main";
1255
+
1256
+ // 切换到默认分支
1257
+ ExecUtil.execSync(`git checkout ${defaultBranch}`, {
1258
+ cwd: workingDir,
1259
+ });
1260
+
1261
+ // 删除工作分支
1262
+ ExecUtil.execSync(`git branch -D ${branchName}`, {
1263
+ cwd: workingDir,
1264
+ });
1265
+
1266
+ logger.info(`已删除工作分支: ${branchName}`);
1267
+ } catch (err) {
1268
+ logger.warn(`删除分支失败: ${err.message}`);
1269
+ }
1270
+ }
1271
+
1272
+ revertResult = {
1273
+ success: true,
1274
+ message: "代码已成功还原到修改前状态",
1275
+ };
1276
+ logger.info("✅ 代码还原完成");
1277
+ } catch (error) {
1278
+ revertResult = {
1279
+ success: false,
1280
+ message: `代码还原失败: ${error.message}`,
1281
+ };
1282
+ logger.error("❌ 代码还原失败:", error);
1283
+ }
1284
+ }
1285
+
1286
+ // 更新任务状态为失败
1287
+ const error = new Error(
1288
+ `开发者拒绝修改: ${reason || "未提供拒绝原因"}`
1289
+ );
1290
+ taskManager.failTask(taskId, error);
1291
+
1292
+ // WebSocket 通知
1293
+ if (wsNotifier) {
1294
+ wsNotifier.notifyTaskFailed(taskId, error);
1295
+ }
1296
+
1297
+ // 释放项目锁
1298
+ if (workingDir) {
1299
+ projectLock.releaseLock(workingDir, taskId);
1300
+ logger.info(`🔓 拒绝修改,释放项目锁: ${workingDir}`, { taskId });
1301
+ }
1302
+
1303
+ ctx.body = {
1304
+ success: true,
1305
+ message: "已拒绝修改",
1306
+ taskId,
1307
+ reason: reason || "未提供原因",
1308
+ revert: revertResult, // 代码还原结果
1309
+ };
1310
+ } catch (error) {
1311
+ ctx.status = 500;
1312
+ ctx.body = {
1313
+ success: false,
1314
+ error: error.message,
1315
+ };
1316
+ }
1317
+ });
1318
+
1319
+ /**
1320
+ * 获取所有任务列表
1321
+ * GET /api/tasks
1322
+ *
1323
+ * 查询参数:
1324
+ * - page: 页码,从 1 开始,默认 1
1325
+ * - pageSize: 每页数量,默认 20,最大 100
1326
+ * - status: 任务状态过滤 (pending, running, waiting_approval, completed, failed)
1327
+ * - repoUrl: 仓库地址过滤(支持模糊匹配)
1328
+ * - projectPath: 项目路径过滤(支持模糊匹配)
1329
+ */
1330
+ router.get("/api/tasks", async (ctx) => {
1331
+ // 解析分页参数
1332
+ const page = Math.max(1, parseInt(ctx.query.page) || 1);
1333
+ const pageSize = Math.min(100, Math.max(1, parseInt(ctx.query.pageSize) || 20));
1334
+
1335
+ // 解析过滤参数
1336
+ const statusFilter = ctx.query.status; // 可选过滤状态:pending, running, waiting_approval, completed, failed
1337
+ const repoUrlFilter = ctx.query.repoUrl; // 仓库地址过滤
1338
+ const projectPathFilter = ctx.query.projectPath; // 项目路径过滤
1339
+
1340
+ // 获取所有任务
1341
+ let tasks = taskManager.getAllTasks(10000); // 获取足够多的任务用于过滤
1342
+
1343
+ // 应用过滤条件
1344
+ if (statusFilter) {
1345
+ tasks = tasks.filter((task) => task.status === statusFilter);
1346
+ }
1347
+
1348
+ if (repoUrlFilter) {
1349
+ tasks = tasks.filter((task) =>
1350
+ task.repoUrl && task.repoUrl.toLowerCase().includes(repoUrlFilter.toLowerCase())
1351
+ );
1352
+ }
1353
+
1354
+ if (projectPathFilter) {
1355
+ tasks = tasks.filter((task) =>
1356
+ task.projectPath && task.projectPath.toLowerCase().includes(projectPathFilter.toLowerCase())
1357
+ );
1358
+ }
1359
+
1360
+ // 计算分页
1361
+ const total = tasks.length;
1362
+ const totalPages = Math.ceil(total / pageSize);
1363
+ const offset = (page - 1) * pageSize;
1364
+ const paginatedTasks = tasks.slice(offset, offset + pageSize);
1365
+
1366
+ // 统计信息(基于所有任务,不受过滤影响)
1367
+ const allTasks = taskManager.getAllTasks(10000);
1368
+ const stats = {
1369
+ total: allTasks.length,
1370
+ pending: allTasks.filter((t) => t.status === "pending").length,
1371
+ running: allTasks.filter((t) => t.status === "running").length,
1372
+ waiting_approval: allTasks.filter((t) => t.status === "waiting_approval").length,
1373
+ completed: allTasks.filter((t) => t.status === "completed").length,
1374
+ failed: allTasks.filter((t) => t.status === "failed").length,
1375
+ };
1376
+
1377
+ ctx.body = {
1378
+ success: true,
1379
+ // 分页信息
1380
+ pagination: {
1381
+ page,
1382
+ pageSize,
1383
+ total,
1384
+ totalPages,
1385
+ hasNextPage: page < totalPages,
1386
+ hasPrevPage: page > 1,
1387
+ },
1388
+ // 过滤条件
1389
+ filters: {
1390
+ status: statusFilter || null,
1391
+ repoUrl: repoUrlFilter || null,
1392
+ projectPath: projectPathFilter || null,
1393
+ },
1394
+ // 全局统计
1395
+ stats,
1396
+ // 任务列表
1397
+ tasks: paginatedTasks.map((task) => ({
1398
+ ...task,
1399
+ // 添加 taskId 别名(前端使用 taskId 而不是 id)
1400
+ taskId: task.id,
1401
+ // 添加便捷字段
1402
+ isRunning: task.status === "running",
1403
+ isCompleted: task.status === "completed",
1404
+ isFailed: task.status === "failed",
1405
+ isPending: task.status === "pending",
1406
+ isWaitingApproval: task.status === "waiting_approval",
1407
+ })),
1408
+ };
1409
+ });
1410
+
1411
+ /**
1412
+ * WebSocket 统计信息
1413
+ * GET /api/websocket/stats
1414
+ */
1415
+ router.get("/api/websocket/stats", async (ctx) => {
1416
+ if (!wsNotifier) {
1417
+ ctx.body = {
1418
+ success: false,
1419
+ error: "WebSocket 服务未启用",
1420
+ suggestion: "运行 npm run install:ws 安装 WebSocket 支持",
1421
+ };
1422
+ return;
1423
+ }
1424
+
1425
+ ctx.body = {
1426
+ success: true,
1427
+ stats: wsNotifier.getStats(),
1428
+ };
1429
+ });
1430
+
1431
+ /**
1432
+ * 任务队列统计信息
1433
+ * GET /api/queue/stats
1434
+ */
1435
+ router.get("/api/queue/stats", async (ctx) => {
1436
+ if (!taskQueue) {
1437
+ ctx.body = {
1438
+ success: false,
1439
+ error: "任务队列未启用",
1440
+ message: "当前使用直接执行模式",
1441
+ };
1442
+ return;
1443
+ }
1444
+
1445
+ try {
1446
+ const stats = await taskQueue.getStats();
1447
+ ctx.body = {
1448
+ success: true,
1449
+ stats,
1450
+ };
1451
+ } catch (error) {
1452
+ ctx.status = 500;
1453
+ ctx.body = {
1454
+ success: false,
1455
+ error: error.message,
1456
+ };
1457
+ }
1458
+ });
1459
+
1460
+ /**
1461
+ * 项目锁统计信息
1462
+ * GET /api/project-locks/stats
1463
+ */
1464
+ router.get("/api/project-locks/stats", async (ctx) => {
1465
+ try {
1466
+ const stats = projectLock.getStats();
1467
+ ctx.body = {
1468
+ success: true,
1469
+ stats,
1470
+ };
1471
+ } catch (error) {
1472
+ ctx.status = 500;
1473
+ ctx.body = {
1474
+ success: false,
1475
+ error: error.message,
1476
+ };
1477
+ }
1478
+ });
1479
+
1480
+ /**
1481
+ * 测试接口 - 简单的本地文件修改测试
1482
+ * POST /api/test
1483
+ */
1484
+ router.post("/api/test", async (ctx) => {
1485
+ try {
1486
+ const {
1487
+ projectPath: rawProjectPath,
1488
+ requirement: rawRequirement,
1489
+ aiEngine: rawAiEngine,
1490
+ } = ctx.request.body;
1491
+
1492
+ if (!rawProjectPath || !rawRequirement) {
1493
+ ctx.status = 400;
1494
+ ctx.body = {
1495
+ success: false,
1496
+ error: "缺少必填参数: projectPath 和 requirement",
1497
+ };
1498
+ return;
1499
+ }
1500
+
1501
+ // 清理和验证输入
1502
+ const projectPath = InputSanitizer.sanitizeProjectPath(rawProjectPath);
1503
+ const requirement = InputSanitizer.sanitizeRequirement(rawRequirement);
1504
+ const aiEngine = InputSanitizer.validateAIEngine(rawAiEngine);
1505
+
1506
+ // 选择 AI 引擎
1507
+ const { service: aiService, name: aiName } = getAIService(aiEngine);
1508
+
1509
+ logger.info(`🧪 测试模式: 仅执行 ${aiName} 修复,不进行 Git 操作`);
1510
+
1511
+ const result = await aiService.fixCode(projectPath, requirement);
1512
+
1513
+ ctx.body = result;
1514
+ } catch (error) {
1515
+ ctx.status = 400;
1516
+ ctx.body = {
1517
+ success: false,
1518
+ error: error.message,
1519
+ };
1520
+ }
1521
+ });
1522
+
1523
+ // 注册路由
1524
+ app.use(router.routes());
1525
+ app.use(router.allowedMethods());
1526
+
1527
+ // 启动服务
1528
+ const server = app.listen(config.get("server.port"), () => {
1529
+ logger.info("=".repeat(60));
1530
+ logger.info("🚀 AI 自动修复服务已启动 (Koa 框架)");
1531
+ logger.info("=".repeat(60));
1532
+ logger.info(`📍 服务地址: http://localhost:${config.get("server.port")}`);
1533
+ logger.info(`📂 工作目录: ${config.get("server.workDir")}`);
1534
+
1535
+ // 初始化 WebSocket 服务
1536
+ if (wsNotifier) {
1537
+ try {
1538
+ wsNotifier.initialize(server);
1539
+ logger.info(
1540
+ `📡 WebSocket 服务已启动: ws://localhost:${config.get(
1541
+ "server.port"
1542
+ )}/ws`
1543
+ );
1544
+ } catch (error) {
1545
+ logger.error("❌ WebSocket 服务初始化失败:", error);
1546
+ }
1547
+ }
1548
+
1549
+ // 显示任务队列信息
1550
+ if (taskQueue) {
1551
+ taskQueue
1552
+ .getStats()
1553
+ .then((stats) => {
1554
+ logger.info(`📋 任务队列已启用: ${stats.queueType}`);
1555
+ if (stats.queueType === "redis") {
1556
+ logger.info(` - Redis 队列`);
1557
+ } else {
1558
+ logger.info(` - 本地队列(最大并发: ${stats.maxConcurrent})`);
1559
+ }
1560
+ logger.info(` - 等待中: ${stats.waiting}`);
1561
+ logger.info(` - 执行中: ${stats.active}`);
1562
+ })
1563
+ .catch((err) => {
1564
+ logger.warn("获取队列统计失败:", err.message);
1565
+ });
1566
+ } else {
1567
+ logger.info("📋 任务队列未启用(直接执行模式)");
1568
+ }
1569
+
1570
+ // 显示项目锁信息
1571
+ logger.info("🔒 项目锁管理器已启用");
1572
+ logger.info(" - 防止同一项目的并发 Git 冲突");
1573
+ logger.info(" - 不同项目可并行执行");
1574
+
1575
+ logger.info(`🤖 默认 AI 引擎: Claude CLI`);
1576
+
1577
+ logger.info(`支持的引擎:`);
1578
+ logger.info(` - claude-cli: Claude CLI (推荐,使用 claude -p 命令)`);
1579
+ logger.info(
1580
+ `📢 企微通知: ${
1581
+ config.get("notification.wecomWebhook") ? "已配置" : "未配置"
1582
+ }`
1583
+ );
1584
+ logger.info(
1585
+ `🔐 API 认证: ${config.get("security.apiKey") ? "已启用" : "未启用"}`
1586
+ );
1587
+
1588
+ logger.info("=".repeat(60));
1589
+ logger.info("可用接口:");
1590
+ logger.info(" GET /health - 健康检查(含性能指标)");
1591
+ logger.info(" GET /api/metrics - 性能监控指标");
1592
+ logger.info(" GET /api/config - 配置信息(需认证)");
1593
+ logger.info(
1594
+ " POST /api/fix-bug - 异步修复(推荐,立即返回任务ID)"
1595
+ );
1596
+ logger.info(" GET /api/tasks/:taskId - 查询任务状态");
1597
+ logger.info(" GET /api/tasks/:taskId/changes - 查看任务修改内容(需等待确认)");
1598
+ logger.info(" POST /api/tasks/:taskId/approve - 确认任务修改并执行 Git 操作");
1599
+ logger.info(" POST /api/tasks/:taskId/reject - 拒绝任务修改");
1600
+ logger.info(" POST /api/tasks/:taskId/cancel - 取消任务");
1601
+ logger.info(" GET /api/tasks - 获取任务列表");
1602
+ logger.info(" GET /api/queue/stats - 任务队列统计");
1603
+ logger.info(" GET /api/project-locks/stats - 项目锁统计");
1604
+ logger.info(" GET /api/websocket/stats - WebSocket 统计");
1605
+ logger.info(" POST /api/test - 测试修复(仅 AI,同步)");
1606
+
1607
+ logger.info("=".repeat(60));
1608
+ logger.info("💡 新功能提示:");
1609
+ logger.info(" - ✅ 请求追踪:每个请求自动分配唯一 ID");
1610
+ logger.info(" - ✅ 性能监控:实时收集任务执行指标");
1611
+ logger.info(" - ✅ 配置管理:集中化配置验证和访问");
1612
+ logger.info(" - ✅ 分支冲突检测:自动处理分支名称冲突");
1613
+ logger.info(" - ✅ Git 分支验证:确保分支名称符合规范");
1614
+ logger.info(" - ✅ 人工确认机制:AI 修复后需开发者确认才执行 Git 操作");
1615
+
1616
+ logger.info("=".repeat(60));
1617
+ logger.info("🔒 安全功能:");
1618
+ logger.info(" - API Key 认证(通过 X-API-Key 请求头)");
1619
+ logger.info(" - IP 白名单(通过 ALLOWED_IPS 环境变量配置)");
1620
+ logger.info(" - 请求频率限制(10s 30 次,敏感接口 1小时 20 次)");
1621
+ logger.info(" - 输入验证和清理(防止注入攻击)");
1622
+ });
1623
+
1624
+ // 优雅关闭
1625
+ const gracefulShutdown = async (signal) => {
1626
+ logger.info(`👋 收到 ${signal} 信号,正在优雅退出...`);
1627
+
1628
+ // 关闭任务队列
1629
+ if (taskQueue) {
1630
+ try {
1631
+ logger.info("📋 关闭任务队列...");
1632
+ await taskQueue.close();
1633
+ logger.info("✅ 任务队列已关闭");
1634
+ } catch (error) {
1635
+ logger.error("❌ 关闭任务队列失败:", error);
1636
+ }
1637
+ }
1638
+
1639
+ // 关闭 WebSocket 服务
1640
+ if (wsNotifier) {
1641
+ logger.info("📡 关闭 WebSocket 服务...");
1642
+ wsNotifier.close();
1643
+ }
1644
+
1645
+ server.close(() => {
1646
+ logger.info("✅ 服务器已关闭");
1647
+ process.exit(0);
1648
+ });
1649
+
1650
+ // 如果 10 秒后还没关闭,强制退出
1651
+ setTimeout(() => {
1652
+ logger.error("⚠️ 强制退出");
1653
+ process.exit(1);
1654
+ }, 10000);
1655
+ };
1656
+
1657
+ process.on("SIGTERM", () => gracefulShutdown("SIGTERM"));
1658
+ process.on("SIGINT", () => gracefulShutdown("SIGINT"));