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
@@ -0,0 +1,391 @@
1
+ const sqlite3 = require('sqlite3').verbose();
2
+ const { open } = require('sqlite');
3
+ const path = require('path');
4
+ const { logger } = require('../logger');
5
+
6
+ /**
7
+ * 数据库管理器
8
+ * 支持 SQLite 数据库操作
9
+ */
10
+ class DatabaseManager {
11
+ constructor() {
12
+ this.db = null;
13
+ this.dbPath = path.join(__dirname, '../../data/tasks.db');
14
+ }
15
+
16
+ /**
17
+ * 初始化数据库
18
+ */
19
+ async initialize() {
20
+ try {
21
+ // 打开数据库连接
22
+ this.db = await open({
23
+ filename: this.dbPath,
24
+ driver: sqlite3.Database
25
+ });
26
+
27
+ logger.info(`✅ 数据库已连接: ${this.dbPath}`);
28
+
29
+ // 创建表结构
30
+ await this.createTables();
31
+
32
+ // 创建索引
33
+ await this.createIndexes();
34
+
35
+ logger.info('✅ 数据库初始化完成');
36
+
37
+ } catch (error) {
38
+ logger.error('数据库初始化失败:', error);
39
+ throw error;
40
+ }
41
+ }
42
+
43
+ /**
44
+ * 创建表结构
45
+ */
46
+ async createTables() {
47
+ // 任务表
48
+ await this.db.exec(`
49
+ CREATE TABLE IF NOT EXISTS tasks (
50
+ id TEXT PRIMARY KEY,
51
+ status TEXT NOT NULL DEFAULT 'pending',
52
+ requirement TEXT NOT NULL,
53
+ project_path TEXT,
54
+ repo_url TEXT,
55
+ run_tests BOOLEAN DEFAULT 0,
56
+ auto_push BOOLEAN DEFAULT 1,
57
+ ai_engine TEXT,
58
+ base_branch TEXT,
59
+ create_new_branch BOOLEAN DEFAULT 1,
60
+ username TEXT DEFAULT 'system',
61
+ merge_to_alpha BOOLEAN DEFAULT 0,
62
+ merge_to_beta BOOLEAN DEFAULT 0,
63
+ merge_to_master BOOLEAN DEFAULT 0,
64
+ created_at TEXT NOT NULL,
65
+ started_at TEXT,
66
+ completed_at TEXT,
67
+ duration TEXT,
68
+ error TEXT,
69
+ UNIQUE(id)
70
+ );
71
+ `);
72
+
73
+ // 任务结果表
74
+ await this.db.exec(`
75
+ CREATE TABLE IF NOT EXISTS task_results (
76
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
77
+ task_id TEXT NOT NULL,
78
+ success BOOLEAN NOT NULL,
79
+ project_name TEXT,
80
+ branch TEXT,
81
+ base_branch TEXT,
82
+ changed_files TEXT,
83
+ summary TEXT,
84
+ conversation_turns INTEGER,
85
+ tests_passed BOOLEAN,
86
+ pushed BOOLEAN,
87
+ merge_results TEXT,
88
+ created_at TEXT NOT NULL,
89
+ FOREIGN KEY (task_id) REFERENCES tasks(id) ON DELETE CASCADE
90
+ );
91
+ `);
92
+
93
+ // 任务进度表
94
+ await this.db.exec(`
95
+ CREATE TABLE IF NOT EXISTS task_progress (
96
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
97
+ task_id TEXT NOT NULL,
98
+ step TEXT NOT NULL,
99
+ message TEXT NOT NULL,
100
+ created_at TEXT NOT NULL,
101
+ FOREIGN KEY (task_id) REFERENCES tasks(id) ON DELETE CASCADE
102
+ );
103
+ `);
104
+
105
+ // 性能指标表
106
+ await this.db.exec(`
107
+ CREATE TABLE IF NOT EXISTS metrics (
108
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
109
+ metric_type TEXT NOT NULL,
110
+ metric_key TEXT NOT NULL,
111
+ metric_value REAL NOT NULL,
112
+ metadata TEXT,
113
+ created_at TEXT NOT NULL
114
+ );
115
+ `);
116
+
117
+ logger.info('✅ 数据表创建完成');
118
+ }
119
+
120
+ /**
121
+ * 创建索引
122
+ */
123
+ async createIndexes() {
124
+ await this.db.exec(`
125
+ CREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(status);
126
+ CREATE INDEX IF NOT EXISTS idx_tasks_created_at ON tasks(created_at DESC);
127
+ CREATE INDEX IF NOT EXISTS idx_task_progress_task_id ON task_progress(task_id);
128
+ CREATE INDEX IF NOT EXISTS idx_metrics_type ON metrics(metric_type, created_at DESC);
129
+ `);
130
+
131
+ logger.info('✅ 数据库索引创建完成');
132
+ }
133
+
134
+ /**
135
+ * 创建任务
136
+ */
137
+ async createTask(taskData) {
138
+ const {
139
+ id,
140
+ requirement,
141
+ projectPath,
142
+ repoUrl,
143
+ runTests,
144
+ autoPush,
145
+ aiEngine,
146
+ baseBranch,
147
+ createNewBranch,
148
+ username,
149
+ mergeToAlpha,
150
+ mergeToBeta,
151
+ mergeToMaster,
152
+ } = taskData;
153
+
154
+ await this.db.run(`
155
+ INSERT INTO tasks (
156
+ id, status, requirement, project_path, repo_url,
157
+ run_tests, auto_push, ai_engine, base_branch,
158
+ create_new_branch, username,
159
+ merge_to_alpha, merge_to_beta, merge_to_master,
160
+ created_at
161
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
162
+ `, [
163
+ id, 'pending', requirement, projectPath, repoUrl,
164
+ runTests ? 1 : 0, autoPush ? 1 : 0, aiEngine, baseBranch,
165
+ createNewBranch ? 1 : 0, username,
166
+ mergeToAlpha ? 1 : 0, mergeToBeta ? 1 : 0, mergeToMaster ? 1 : 0,
167
+ new Date().toISOString()
168
+ ]);
169
+
170
+ logger.debug(`任务已保存到数据库: ${id}`);
171
+ }
172
+
173
+ /**
174
+ * 更新任务状态
175
+ */
176
+ async updateTaskStatus(taskId, status, data = {}) {
177
+ const updates = {
178
+ status,
179
+ ...data
180
+ };
181
+
182
+ const fields = Object.keys(updates)
183
+ .map(key => `${this.camelToSnake(key)} = ?`)
184
+ .join(', ');
185
+
186
+ const values = Object.values(updates);
187
+ values.push(taskId);
188
+
189
+ await this.db.run(
190
+ `UPDATE tasks SET ${fields} WHERE id = ?`,
191
+ values
192
+ );
193
+
194
+ logger.debug(`任务状态已更新: ${taskId} -> ${status}`);
195
+ }
196
+
197
+ /**
198
+ * 添加任务进度
199
+ */
200
+ async addTaskProgress(taskId, step, message) {
201
+ await this.db.run(`
202
+ INSERT INTO task_progress (task_id, step, message, created_at)
203
+ VALUES (?, ?, ?, ?)
204
+ `, [taskId, step, message, new Date().toISOString()]);
205
+ }
206
+
207
+ /**
208
+ * 保存任务结果
209
+ */
210
+ async saveTaskResult(taskId, result) {
211
+ await this.db.run(`
212
+ INSERT INTO task_results (
213
+ task_id, success, project_name, branch, base_branch,
214
+ changed_files, summary, conversation_turns,
215
+ tests_passed, pushed, merge_results, created_at
216
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
217
+ `, [
218
+ taskId,
219
+ result.success ? 1 : 0,
220
+ result.projectName,
221
+ result.branch,
222
+ result.baseBranch,
223
+ JSON.stringify(result.changedFiles || []),
224
+ result.summary,
225
+ result.conversationTurns,
226
+ result.testsPassed ? 1 : 0,
227
+ result.pushed ? 1 : 0,
228
+ JSON.stringify(result.mergeResults || {}),
229
+ new Date().toISOString()
230
+ ]);
231
+ }
232
+
233
+ /**
234
+ * 获取任务
235
+ */
236
+ async getTask(taskId) {
237
+ const task = await this.db.get(
238
+ 'SELECT * FROM tasks WHERE id = ?',
239
+ [taskId]
240
+ );
241
+
242
+ if (!task) return null;
243
+
244
+ return this.snakeToCamelObject(task);
245
+ }
246
+
247
+ /**
248
+ * 获取任务列表
249
+ */
250
+ async getTasks(options = {}) {
251
+ const {
252
+ status,
253
+ limit = 20,
254
+ offset = 0,
255
+ orderBy = 'created_at DESC'
256
+ } = options;
257
+
258
+ let query = 'SELECT * FROM tasks';
259
+ const params = [];
260
+
261
+ if (status) {
262
+ query += ' WHERE status = ?';
263
+ params.push(status);
264
+ }
265
+
266
+ query += ` ORDER BY ${orderBy} LIMIT ? OFFSET ?`;
267
+ params.push(limit, offset);
268
+
269
+ const tasks = await this.db.all(query, params);
270
+
271
+ return tasks.map(task => this.snakeToCamelObject(task));
272
+ }
273
+
274
+ /**
275
+ * 获取任务统计
276
+ */
277
+ async getTaskStats() {
278
+ const stats = await this.db.get(`
279
+ SELECT
280
+ COUNT(*) as total,
281
+ SUM(CASE WHEN status = 'pending' THEN 1 ELSE 0 END) as pending,
282
+ SUM(CASE WHEN status = 'running' THEN 1 ELSE 0 END) as running,
283
+ SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) as completed,
284
+ SUM(CASE WHEN status = 'failed' THEN 1 ELSE 0 END) as failed,
285
+ SUM(CASE WHEN status = 'cancelled' THEN 1 ELSE 0 END) as cancelled
286
+ FROM tasks
287
+ `);
288
+
289
+ return stats;
290
+ }
291
+
292
+ /**
293
+ * 获取任务进度历史
294
+ */
295
+ async getTaskProgress(taskId) {
296
+ const progress = await this.db.all(
297
+ 'SELECT * FROM task_progress WHERE task_id = ? ORDER BY created_at ASC',
298
+ [taskId]
299
+ );
300
+
301
+ return progress.map(p => this.snakeToCamelObject(p));
302
+ }
303
+
304
+ /**
305
+ * 保存性能指标
306
+ */
307
+ async saveMetric(metricType, metricKey, metricValue, metadata = null) {
308
+ await this.db.run(`
309
+ INSERT INTO metrics (metric_type, metric_key, metric_value, metadata, created_at)
310
+ VALUES (?, ?, ?, ?, ?)
311
+ `, [
312
+ metricType,
313
+ metricKey,
314
+ metricValue,
315
+ metadata ? JSON.stringify(metadata) : null,
316
+ new Date().toISOString()
317
+ ]);
318
+ }
319
+
320
+ /**
321
+ * 获取性能指标
322
+ */
323
+ async getMetrics(metricType, limit = 100) {
324
+ const metrics = await this.db.all(`
325
+ SELECT * FROM metrics
326
+ WHERE metric_type = ?
327
+ ORDER BY created_at DESC
328
+ LIMIT ?
329
+ `, [metricType, limit]);
330
+
331
+ return metrics.map(m => ({
332
+ ...m,
333
+ metadata: m.metadata ? JSON.parse(m.metadata) : null
334
+ }));
335
+ }
336
+
337
+ /**
338
+ * 清理旧数据
339
+ */
340
+ async cleanup(daysToKeep = 30) {
341
+ const cutoffDate = new Date();
342
+ cutoffDate.setDate(cutoffDate.getDate() - daysToKeep);
343
+
344
+ const result = await this.db.run(`
345
+ DELETE FROM tasks
346
+ WHERE created_at < ?
347
+ AND status IN ('completed', 'failed', 'cancelled')
348
+ `, [cutoffDate.toISOString()]);
349
+
350
+ logger.info(`✅ 清理了 ${result.changes} 条旧任务记录`);
351
+
352
+ return result.changes;
353
+ }
354
+
355
+ /**
356
+ * 驼峰转下划线
357
+ */
358
+ camelToSnake(str) {
359
+ return str.replace(/[A-Z]/g, letter => `_${letter.toLowerCase()}`);
360
+ }
361
+
362
+ /**
363
+ * 下划线转驼峰
364
+ */
365
+ snakeToCamel(str) {
366
+ return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
367
+ }
368
+
369
+ /**
370
+ * 对象字段转驼峰
371
+ */
372
+ snakeToCamelObject(obj) {
373
+ const result = {};
374
+ for (const key in obj) {
375
+ result[this.snakeToCamel(key)] = obj[key];
376
+ }
377
+ return result;
378
+ }
379
+
380
+ /**
381
+ * 关闭数据库连接
382
+ */
383
+ async close() {
384
+ if (this.db) {
385
+ await this.db.close();
386
+ logger.info('数据库连接已关闭');
387
+ }
388
+ }
389
+ }
390
+
391
+ module.exports = new DatabaseManager();
@@ -0,0 +1,252 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const { logger } = require('../logger');
4
+ const databaseManager = require('./manager');
5
+
6
+ /**
7
+ * 数据迁移工具
8
+ * 将 JSON 文件数据迁移到 SQLite 数据库
9
+ */
10
+ class MigrationTool {
11
+ constructor() {
12
+ this.jsonFilePath = path.join(__dirname, '../../data/tasks.json');
13
+ this.backupPath = path.join(__dirname, '../../data/tasks.json.backup');
14
+ }
15
+
16
+ /**
17
+ * 执行迁移
18
+ */
19
+ async migrate() {
20
+ try {
21
+ logger.info('开始数据迁移...');
22
+
23
+ // 1. 检查 JSON 文件是否存在
24
+ if (!fs.existsSync(this.jsonFilePath)) {
25
+ logger.info('未找到 JSON 文件,跳过迁移');
26
+ return {
27
+ success: true,
28
+ migrated: 0,
29
+ message: '未找到数据文件'
30
+ };
31
+ }
32
+
33
+ // 2. 读取 JSON 数据
34
+ const jsonData = this.readJsonFile();
35
+
36
+ if (!jsonData || jsonData.length === 0) {
37
+ logger.info('JSON 文件为空,跳过迁移');
38
+ return {
39
+ success: true,
40
+ migrated: 0,
41
+ message: '数据文件为空'
42
+ };
43
+ }
44
+
45
+ logger.info(`找到 ${jsonData.length} 条任务记录`);
46
+
47
+ // 3. 初始化数据库
48
+ await databaseManager.initialize();
49
+
50
+ // 4. 备份 JSON 文件
51
+ this.backupJsonFile();
52
+
53
+ // 5. 迁移数据
54
+ let migrated = 0;
55
+ let failed = 0;
56
+
57
+ for (const task of jsonData) {
58
+ try {
59
+ await this.migrateTask(task);
60
+ migrated++;
61
+ } catch (error) {
62
+ logger.error(`迁移任务失败: ${task.id}`, error);
63
+ failed++;
64
+ }
65
+ }
66
+
67
+ logger.info(`✅ 数据迁移完成: 成功 ${migrated} 条, 失败 ${failed} 条`);
68
+
69
+ return {
70
+ success: true,
71
+ migrated,
72
+ failed,
73
+ total: jsonData.length
74
+ };
75
+
76
+ } catch (error) {
77
+ logger.error('数据迁移失败:', error);
78
+ throw error;
79
+ }
80
+ }
81
+
82
+ /**
83
+ * 迁移单个任务
84
+ */
85
+ async migrateTask(task) {
86
+ // 创建任务记录
87
+ await databaseManager.createTask({
88
+ id: task.id,
89
+ requirement: task.requirement,
90
+ projectPath: task.projectPath,
91
+ repoUrl: task.repoUrl,
92
+ runTests: task.runTests,
93
+ autoPush: task.autoPush,
94
+ aiEngine: task.aiEngine,
95
+ baseBranch: task.baseBranch,
96
+ createNewBranch: task.createNewBranch,
97
+ username: task.username,
98
+ mergeToAlpha: task.mergeToAlpha,
99
+ mergeToBeta: task.mergeToBeta,
100
+ mergeToMaster: task.mergeToMaster,
101
+ });
102
+
103
+ // 更新任务状态
104
+ await databaseManager.updateTaskStatus(task.id, task.status, {
105
+ startedAt: task.startedAt,
106
+ completedAt: task.completedAt,
107
+ duration: task.duration,
108
+ error: task.error,
109
+ });
110
+
111
+ // 保存任务结果
112
+ if (task.result) {
113
+ await databaseManager.saveTaskResult(task.id, task.result);
114
+ }
115
+
116
+ // 保存任务进度
117
+ if (task.progress) {
118
+ await databaseManager.addTaskProgress(
119
+ task.id,
120
+ task.progress.step || 'unknown',
121
+ task.progress.message || ''
122
+ );
123
+ }
124
+ }
125
+
126
+ /**
127
+ * 读取 JSON 文件
128
+ */
129
+ readJsonFile() {
130
+ try {
131
+ const data = fs.readFileSync(this.jsonFilePath, 'utf-8');
132
+ return JSON.parse(data);
133
+ } catch (error) {
134
+ logger.error('读取 JSON 文件失败:', error);
135
+ return null;
136
+ }
137
+ }
138
+
139
+ /**
140
+ * 备份 JSON 文件
141
+ */
142
+ backupJsonFile() {
143
+ try {
144
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
145
+ const backupFile = `${this.backupPath}.${timestamp}`;
146
+
147
+ fs.copyFileSync(this.jsonFilePath, backupFile);
148
+ logger.info(`✅ JSON 文件已备份: ${backupFile}`);
149
+ } catch (error) {
150
+ logger.error('备份 JSON 文件失败:', error);
151
+ }
152
+ }
153
+
154
+ /**
155
+ * 验证迁移结果
156
+ */
157
+ async verify() {
158
+ try {
159
+ logger.info('验证迁移结果...');
160
+
161
+ const jsonData = this.readJsonFile();
162
+ if (!jsonData) {
163
+ logger.warn('无法读取 JSON 文件进行验证');
164
+ return false;
165
+ }
166
+
167
+ const dbStats = await databaseManager.getTaskStats();
168
+
169
+ logger.info('迁移验证:');
170
+ logger.info(` JSON 文件任务数: ${jsonData.length}`);
171
+ logger.info(` 数据库任务总数: ${dbStats.total}`);
172
+
173
+ if (jsonData.length === dbStats.total) {
174
+ logger.info('✅ 验证通过:任务数量一致');
175
+ return true;
176
+ } else {
177
+ logger.warn('⚠️ 验证失败:任务数量不一致');
178
+ return false;
179
+ }
180
+
181
+ } catch (error) {
182
+ logger.error('验证迁移结果失败:', error);
183
+ return false;
184
+ }
185
+ }
186
+
187
+ /**
188
+ * 回滚迁移(恢复 JSON 文件)
189
+ */
190
+ async rollback() {
191
+ try {
192
+ logger.info('开始回滚迁移...');
193
+
194
+ // 查找最新的备份文件
195
+ const dataDir = path.dirname(this.jsonFilePath);
196
+ const files = fs.readdirSync(dataDir);
197
+ const backupFiles = files
198
+ .filter(f => f.startsWith('tasks.json.backup'))
199
+ .sort()
200
+ .reverse();
201
+
202
+ if (backupFiles.length === 0) {
203
+ logger.error('未找到备份文件');
204
+ return false;
205
+ }
206
+
207
+ const latestBackup = path.join(dataDir, backupFiles[0]);
208
+ fs.copyFileSync(latestBackup, this.jsonFilePath);
209
+
210
+ logger.info(`✅ 已从备份恢复: ${latestBackup}`);
211
+ return true;
212
+
213
+ } catch (error) {
214
+ logger.error('回滚迁移失败:', error);
215
+ return false;
216
+ }
217
+ }
218
+ }
219
+
220
+ // 如果直接运行此文件,执行迁移
221
+ if (require.main === module) {
222
+ const migration = new MigrationTool();
223
+
224
+ (async () => {
225
+ try {
226
+ // 执行迁移
227
+ const result = await migration.migrate();
228
+
229
+ console.log('\n迁移结果:');
230
+ console.log(` 成功: ${result.migrated || 0} 条`);
231
+ console.log(` 失败: ${result.failed || 0} 条`);
232
+ console.log(` 总计: ${result.total || 0} 条`);
233
+
234
+ // 验证迁移
235
+ const verified = await migration.verify();
236
+
237
+ if (verified) {
238
+ console.log('\n✅ 迁移成功并已验证');
239
+ process.exit(0);
240
+ } else {
241
+ console.log('\n⚠️ 迁移完成但验证失败,建议检查数据');
242
+ process.exit(1);
243
+ }
244
+
245
+ } catch (error) {
246
+ console.error('\n❌ 迁移失败:', error.message);
247
+ process.exit(1);
248
+ }
249
+ })();
250
+ }
251
+
252
+ module.exports = MigrationTool;