momo-ai 1.0.15 → 1.0.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "momo-ai",
3
- "version": "1.0.15",
3
+ "version": "1.0.18",
4
4
  "description": "Rachel Momo ( OpenSpec )",
5
5
  "main": "src/momo.js",
6
6
  "bin": {
@@ -6,4 +6,5 @@
6
6
  任务完成后,请记得删除以下两个锁文件:
7
7
  1. 任务锁文件: specification/changes/<%= REQ %>/tasks/<%= TASK %>.lock
8
8
  2. 任务空间锁文件: source/<%= ROOT %>.lock
9
+ 3. 任务完成之后你需要十分智能地检查 git 仓库状态并且提交一份代码到 git 远程仓库,注意提交之前保证自己手中代码是最新的,若有冲突要解决冲突后合并提交。
9
10
  <!-- END -->
@@ -9,8 +9,8 @@
9
9
  "description": "当前项目的远程 Git 仓库地址!"
10
10
  },
11
11
  {
12
- "name": "i",
13
- "alias": "instance",
12
+ "name": "instance",
13
+ "alias": "i",
14
14
  "description": "需要 Clone 的远程仓库的实例副本数量!",
15
15
  "default": 10
16
16
  }
@@ -4,65 +4,92 @@
4
4
 
5
5
  const readline = require('readline');
6
6
  const colors = require('colors');
7
- const Ec = require('../epic');
7
+ const path = require('path');
8
+ const fs = require('fs');
9
+ const terminalCommands = require('../terminal');
10
+
11
+ // 设置颜色主题
12
+ colors.setTheme({
13
+ silly: 'rainbow',
14
+ input: 'grey',
15
+ verbose: 'cyan',
16
+ prompt: 'red',
17
+ info: 'green',
18
+ data: 'blue',
19
+ help: 'cyan',
20
+ warn: 'yellow',
21
+ debug: 'magenta',
22
+ error: 'red'
23
+ });
8
24
 
9
25
  // 显示欢迎界面和菜单
10
26
  const showMenu = () => {
11
27
  // 清屏
12
28
  process.stdout.write('\x1Bc');
13
-
29
+
14
30
  // 显示标准头部信息
15
- Ec.executeHeader("Rachel Momo / SDD");
16
-
31
+ showHeader();
32
+
17
33
  // 使用96个字符宽度
18
34
  const width = 96;
19
35
  const headerBorder = '='.repeat(width).blue;
20
36
  const footerBorder = '-'.repeat(width).blue;
21
-
37
+
22
38
  console.log('');
23
39
  console.log(headerBorder);
24
40
  const title = 'Momo AI / Lain Console';
25
41
  const padding = ' '.repeat(Math.floor((width - title.length) / 2) - 1);
26
42
  console.log(`${padding}${title}`.bold.brightCyan);
27
43
  console.log(headerBorder);
28
-
44
+
29
45
  console.log('');
30
46
  console.log('欢迎使用 Momo AI / Lain 控制台!'.green);
31
47
  console.log('这是一个交互式命令行界面。'.yellow);
32
48
  console.log('');
33
-
49
+
34
50
  console.log('可用命令:'.bold);
35
51
  console.log(' help - 显示帮助信息'.white);
36
- console.log(' exit - 退出控制台'.white);
52
+ console.log(' llm - 查看大模型配置信息'.white);
37
53
  console.log(' quit - 退出控制台'.white);
38
54
  console.log('');
39
-
55
+
40
56
  console.log('请在提示符后输入命令。'.gray);
41
57
  console.log(footerBorder);
42
58
  };
43
59
 
60
+ // 显示标准头部信息
61
+ const showHeader = () => {
62
+ const appInfo = JSON.parse(fs.readFileSync(path.resolve(__dirname, '../../package.json'), 'utf8'));
63
+
64
+ console.log(`[Momo AI]`.green.bold + ` ----------------- Rachel Momo / AI工具项 ------------------`.rainbow);
65
+ console.log(`[Momo AI]`.green.bold + ' 应用名称: '.bold + 'Rachel Momo / SDD');
66
+ console.log(`[Momo AI]`.green.bold + ' 工具主页: '.bold + appInfo.homepage.blue);
67
+ console.log(`[Momo AI]`.green.bold + ` 工具版本: ` + `${appInfo.version}`.red + ' ' + `( Node >= 22.x )`.yellow);
68
+ console.log(`[Momo AI]`.green.bold);
69
+ console.log(`[Momo AI]`.green.bold + ` ----------------- AI 系统启动…… ----------------------------`.rainbow);
70
+ };
71
+
44
72
  // 处理用户输入
45
- const handleInput = (input) => {
73
+ const handleInput = (input, commands) => {
46
74
  const command = input.trim().toLowerCase();
47
-
75
+
76
+ // 创建命令上下文
77
+ const context = {
78
+ commands: commands
79
+ };
80
+
48
81
  switch (command) {
49
82
  case '':
50
83
  // 空命令,不处理
51
84
  break;
52
85
  case 'help':
53
- console.log('');
54
- console.log('帮助信息:'.bold.brightYellow);
55
- console.log(' help - 显示此帮助信息'.white);
56
- console.log(' exit - 退出控制台'.white);
57
- console.log(' quit - 退出控制台'.white);
58
- console.log('');
86
+ commands.help(context);
87
+ break;
88
+ case 'llm':
89
+ commands.llm(context);
59
90
  break;
60
- case 'exit':
61
91
  case 'quit':
62
- console.log('');
63
- console.log('感谢使用 Momo AI / Lain 控制台,再见!'.brightGreen);
64
- console.log('');
65
- process.exit(0);
92
+ commands.quit(context);
66
93
  break;
67
94
  default:
68
95
  console.log('');
@@ -97,7 +124,7 @@ const executeConsole = async () => {
97
124
 
98
125
  rl.on('line', (line) => {
99
126
  const input = line.trim();
100
- handleInput(input);
127
+ handleInput(input, terminalCommands);
101
128
  rl.prompt();
102
129
  }).on('close', () => {
103
130
  console.log('\n' + '感谢使用 Momo AI / Lain 控制台,再见!'.brightGreen + '\n');
@@ -21,6 +21,44 @@ const _isGitRepo = async (dirPath) => {
21
21
  }
22
22
  };
23
23
 
24
+ /**
25
+ * 检查是否有未合并的文件
26
+ * @param {string} dirPath 目录路径
27
+ * @returns {Promise<boolean>} 是否有未合并的文件
28
+ */
29
+ const _hasUnmergedFiles = async (dirPath) => {
30
+ try {
31
+ const { stdout } = await execAsync('git diff --name-only --diff-filter=U', { cwd: dirPath });
32
+ return stdout.trim() !== '';
33
+ } catch (error) {
34
+ return false;
35
+ }
36
+ };
37
+
38
+ /**
39
+ * 执行 git merge 命令合并更改
40
+ * @param {string} dirPath 目录路径
41
+ */
42
+ const _mergeChanges = async (dirPath) => {
43
+ try {
44
+ Ec.waiting(`正在合并更改: ${dirPath}`);
45
+ const { stdout, stderr } = await execAsync('git merge --no-edit', { cwd: dirPath });
46
+
47
+ if (stdout) {
48
+ Ec.waiting(`输出: ${stdout.trim()}`);
49
+ }
50
+ if (stderr) {
51
+ Ec.waiting(`进度: ${stderr.trim()}`);
52
+ }
53
+
54
+ Ec.waiting(`✅ 成功合并更改: ${dirPath}`);
55
+ return true;
56
+ } catch (error) {
57
+ Ec.error(`❌ 合并更改失败 ${dirPath}: ${error.message}`);
58
+ return false;
59
+ }
60
+ };
61
+
24
62
  /**
25
63
  * 从远程拉取最新代码
26
64
  * @param {string} dirPath 目录路径
@@ -40,8 +78,33 @@ const _pullRepo = async (dirPath) => {
40
78
  Ec.waiting(`✅ 成功拉取代码: ${dirPath}`);
41
79
  return true;
42
80
  } catch (error) {
43
- Ec.error(`❌ 拉取代码失败 ${dirPath}: ${error.message}`);
44
- return false;
81
+ // 检查是否是分支分歧错误
82
+ if (error.message.includes('Need to specify how to reconcile divergent branches')) {
83
+ try {
84
+ Ec.waiting(`检测到分支分歧错误,正在配置 pull.rebase=false: ${dirPath}`);
85
+ await execAsync('git config pull.rebase false', { cwd: dirPath });
86
+ Ec.waiting(`已设置 pull.rebase=false,重新尝试拉取代码: ${dirPath}`);
87
+
88
+ // 重新执行 pull 操作
89
+ const { stdout, stderr } = await execAsync('git pull', { cwd: dirPath });
90
+
91
+ if (stdout) {
92
+ Ec.waiting(`输出: ${stdout.trim()}`);
93
+ }
94
+ if (stderr) {
95
+ Ec.waiting(`进度: ${stderr.trim()}`);
96
+ }
97
+
98
+ Ec.waiting(`✅ 成功拉取代码: ${dirPath}`);
99
+ return true;
100
+ } catch (retryError) {
101
+ Ec.error(`❌ 重新拉取代码失败 ${dirPath}: ${retryError.message}`);
102
+ return false;
103
+ }
104
+ } else {
105
+ Ec.error(`❌ 拉取代码失败 ${dirPath}: ${error.message}`);
106
+ return false;
107
+ }
45
108
  }
46
109
  };
47
110
 
@@ -176,8 +176,8 @@ module.exports = async (options) => {
176
176
  const parsed = Ec.parseArgument(options);
177
177
 
178
178
  // 获取参数
179
- const repoUrl = parsed.address || parsed.a;
180
- const instanceCount = parsed.i || parsed.instance || 10; // 默认值为10
179
+ const repoUrl = parsed.address;
180
+ const instanceCount = parsed.instance;
181
181
 
182
182
  // 验证参数
183
183
  if (!repoUrl) {
@@ -115,16 +115,36 @@ const _findTaskInstances = (taskName) => {
115
115
 
116
116
  // 检查 tasks 目录是否存在
117
117
  if (fs.existsSync(tasksDir) && fs.statSync(tasksDir).isDirectory()) {
118
+ // 检查 .md 文件
118
119
  const taskFile = `${taskName}.md`;
119
120
  const taskPath = path.resolve(tasksDir, taskFile);
120
121
 
121
122
  // 检查任务文件是否存在
122
123
  if (fs.existsSync(taskPath)) {
124
+ // 提取任务标题
125
+ const title = _extractTitleFromMarkdown(taskPath);
123
126
  taskInstances.push({
124
127
  name: taskName,
125
128
  path: taskPath,
126
129
  requirement: requirement,
127
- relativePath: path.relative(process.cwd(), taskPath)
130
+ relativePath: path.relative(process.cwd(), taskPath),
131
+ title: title
132
+ });
133
+ }
134
+
135
+ // 如果 .md 文件不存在,检查 .done 文件
136
+ const doneFile = `${taskName}.done`;
137
+ const donePath = path.resolve(tasksDir, doneFile);
138
+
139
+ if (!fs.existsSync(taskPath) && fs.existsSync(donePath)) {
140
+ // 提取任务标题(从 .done 文件中)
141
+ const title = _extractTitleFromMarkdown(donePath);
142
+ taskInstances.push({
143
+ name: taskName,
144
+ path: donePath,
145
+ requirement: requirement,
146
+ relativePath: path.relative(process.cwd(), donePath),
147
+ title: title
128
148
  });
129
149
  }
130
150
  }
@@ -316,6 +336,48 @@ const _extractTitleFromMarkdown = (filePath) => {
316
336
  }
317
337
  };
318
338
 
339
+ /**
340
+ * 保存任务分派记录
341
+ * @param {string} taskName 任务名称
342
+ * @param {string} workspaceName 工作空间名称
343
+ * @param {string} actorName Actor名称
344
+ * @param {string} requirementName 需求名称
345
+ */
346
+ const _saveAssignmentRecord = async (taskName, workspaceName, actorName, requirementName) => {
347
+ try {
348
+ // 确保 .activities 目录存在
349
+ const activitiesDir = path.resolve(process.cwd(), '.activities');
350
+ if (!fs.existsSync(activitiesDir)) {
351
+ await fsAsync.mkdir(activitiesDir, { recursive: true });
352
+ }
353
+
354
+ // 创建文件名:{任务编号}-develop-xx-时间戳.txt
355
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-').replace('T', '_').slice(0, -5);
356
+ // 只使用工作空间名称,而不是完整路径
357
+ const workspaceDirName = path.basename(workspaceName);
358
+ const fileName = `${taskName}-${workspaceDirName}-${timestamp}.txt`;
359
+ const filePath = path.join(activitiesDir, fileName);
360
+
361
+ // 写入分派记录
362
+ const recordContent = `Task Assignment Record
363
+ =====================
364
+
365
+ Task Name: ${taskName}
366
+ Workspace: ${workspaceDirName}
367
+ Actor: ${actorName}
368
+ Requirement: ${requirementName}
369
+ Assigned At: ${new Date().toISOString()}
370
+
371
+ This task has been assigned to workspace ${workspaceDirName} for actor ${actorName}.
372
+ `;
373
+
374
+ await fsAsync.writeFile(filePath, recordContent);
375
+ Ec.waiting(`[Momo AI] 任务分派记录已保存: ${filePath}`);
376
+ } catch (error) {
377
+ Ec.waiting(`⚠️ 无法保存任务分派记录: ${error.message}`);
378
+ }
379
+ };
380
+
319
381
  module.exports = async (options) => {
320
382
  // 参数提取
321
383
  const parsed = Ec.parseArgument(options);
@@ -367,16 +429,26 @@ module.exports = async (options) => {
367
429
  const coloredName = task.name.cyan;
368
430
  Ec.waiting(`${index + 1}. [${coloredStatus}] ${task.display} (${coloredName})`);
369
431
  });
432
+
433
+ // 添加退出选项
434
+ Ec.waiting(`${allTasks.length + 1}. 退出`);
370
435
 
371
436
  // 获取用户选择
372
437
  const answer = await Ec.ask('请输入选项编号: ');
373
438
  const selectedIndex = parseInt(answer) - 1;
374
439
 
375
440
  // 验证选择
376
- if (isNaN(selectedIndex) || selectedIndex < 0 || selectedIndex >= allTasks.length) {
441
+ if (isNaN(selectedIndex) || selectedIndex < 0 || selectedIndex > allTasks.length) {
377
442
  Ec.error('❌ 无效的选择');
378
443
  continue;
379
444
  }
445
+
446
+ // 检查是否选择退出
447
+ if (selectedIndex === allTasks.length) {
448
+ Ec.info("已退出任务选择");
449
+ Ec.askClose();
450
+ process.exit(0);
451
+ }
380
452
 
381
453
  // 检查选中的任务是否正在进行中
382
454
  const tasksDir = path.resolve(process.cwd(), 'specification', 'changes', allTasks[selectedIndex].requirement, 'tasks');
@@ -386,6 +458,13 @@ module.exports = async (options) => {
386
458
  Ec.error('❌ 不能选择正在进行中的任务,请重新选择');
387
459
  continue;
388
460
  }
461
+
462
+ // 检查选中的任务是否已完成
463
+ if (status === '已完成') {
464
+ Ec.info(`✅ 任务 ${allTasks[selectedIndex].name} 已经完成,不用分派`);
465
+ Ec.askClose();
466
+ process.exit(0);
467
+ }
389
468
 
390
469
  // 直接使用用户选择的任务,跳过重复检查
391
470
  selectedTask = allTasks[selectedIndex];
@@ -451,6 +530,13 @@ module.exports = async (options) => {
451
530
  Ec.error('❌ 不能选择正在进行中的任务,请重新选择');
452
531
  continue;
453
532
  }
533
+
534
+ // 检查选中的任务是否已完成
535
+ if (status === '已完成') {
536
+ Ec.info(`✅ 任务 ${taskInstances[selectedIndex].name} 已经完成,不用分派`);
537
+ Ec.askClose();
538
+ process.exit(0);
539
+ }
454
540
 
455
541
  selectedTask = taskInstances[selectedIndex];
456
542
  break;
@@ -466,6 +552,13 @@ module.exports = async (options) => {
466
552
  process.exit(1);
467
553
  }
468
554
 
555
+ // 检查任务是否已完成
556
+ if (status === '已完成') {
557
+ Ec.info(`✅ 任务 ${taskName} 已经完成,不用分派`);
558
+ Ec.askClose();
559
+ process.exit(0);
560
+ }
561
+
469
562
  selectedTask = taskInstances[0];
470
563
  }
471
564
 
@@ -498,6 +591,11 @@ module.exports = async (options) => {
498
591
  workspaceLockPath = _createWorkspaceLock(selectedWorkspace.name, selectedTask.name, actorName);
499
592
  }
500
593
 
594
+ // 保存任务分派记录
595
+ if (selectedWorkspace) {
596
+ await _saveAssignmentRecord(selectedTask.name, selectedWorkspace.name, actorName, requirementName);
597
+ }
598
+
501
599
  // 读取模板文件并填充参数,然后拷贝到剪贴板
502
600
  const templatePath = path.resolve(__dirname, '../_template/PROMPT/run.md.ejs');
503
601
 
@@ -512,15 +610,15 @@ module.exports = async (options) => {
512
610
  });
513
611
 
514
612
  // 打印额外信息
515
- Ec.waiting(`[Momo AI] 执行任务: ${selectedTask.name} - ${selectedTask.title}`);
516
- Ec.waiting(`[Momo AI] 使用工作空间: ${selectedWorkspace.path}`);
517
- Ec.waiting('[Momo AI] 提示词内容:');
613
+ Ec.waiting(`执行任务: ${selectedTask.name} - ${selectedTask.title || '未知任务'}`);
614
+ Ec.waiting(`使用工作空间: ${selectedWorkspace.path}`);
615
+ Ec.waiting('提示词内容:');
518
616
  Ec.waiting('--------------------------------------------------');
519
617
 
520
618
  // 按行打印提示词内容,保持原有格式
521
619
  const lines = renderedContent.split('\n');
522
620
  lines.forEach(line => {
523
- Ec.waiting(`[Momo AI] ${line}`);
621
+ Ec.waiting(line);
524
622
  });
525
623
 
526
624
  Ec.waiting('--------------------------------------------------');
@@ -164,6 +164,7 @@ module.exports = async (options) => {
164
164
 
165
165
  // 检查 tasks 目录是否存在
166
166
  if (fs.existsSync(tasksDir) && fs.statSync(tasksDir).isDirectory()) {
167
+ // 检查 .md 文件
167
168
  const taskFile = `${taskName}.md`;
168
169
  const taskPath = path.resolve(tasksDir, taskFile);
169
170
 
@@ -176,6 +177,19 @@ module.exports = async (options) => {
176
177
  requirement: requirement
177
178
  });
178
179
  }
180
+
181
+ // 检查 .done 文件(如果 .md 文件不存在)
182
+ const doneFile = `${taskName}.done`;
183
+ const donePath = path.resolve(tasksDir, doneFile);
184
+
185
+ if (!fs.existsSync(taskPath) && fs.existsSync(donePath)) {
186
+ const relativePath = path.relative(process.cwd(), donePath);
187
+ taskInstances.push({
188
+ name: taskName,
189
+ path: relativePath,
190
+ requirement: requirement
191
+ });
192
+ }
179
193
  }
180
194
  });
181
195
 
@@ -217,11 +231,17 @@ module.exports = async (options) => {
217
231
  // 检查 tasks 目录是否存在
218
232
  if (fs.existsSync(tasksDir) && fs.statSync(tasksDir).isDirectory()) {
219
233
  // 获取所有 .md 文件
220
- const taskFiles = fs.readdirSync(tasksDir).filter(file =>
234
+ const mdFiles = fs.readdirSync(tasksDir).filter(file =>
221
235
  file.endsWith('.md')
222
236
  );
223
237
 
224
- taskFiles.forEach(taskFile => {
238
+ // 获取所有 .done 文件
239
+ const doneFiles = fs.readdirSync(tasksDir).filter(file =>
240
+ file.endsWith('.done')
241
+ );
242
+
243
+ // 处理 .md 文件
244
+ mdFiles.forEach(taskFile => {
225
245
  const name = path.basename(taskFile, '.md');
226
246
  const taskPath = path.relative(process.cwd(), path.resolve(tasksDir, taskFile));
227
247
  const title = _extractTitleFromMarkdown(path.resolve(tasksDir, taskFile));
@@ -251,9 +271,58 @@ module.exports = async (options) => {
251
271
  status: coloredStatus
252
272
  });
253
273
  });
274
+
275
+ // 处理 .done 文件(但没有对应的 .md 文件)
276
+ doneFiles.forEach(doneFile => {
277
+ const name = path.basename(doneFile, '.done');
278
+ // 检查是否已经有对应的 .md 文件在任务列表中
279
+ const hasMdFile = mdFiles.some(mdFile =>
280
+ path.basename(mdFile, '.md') === name
281
+ );
282
+
283
+ // 如果没有对应的 .md 文件,则添加到任务列表
284
+ if (!hasMdFile) {
285
+ const donePath = path.relative(process.cwd(), path.resolve(tasksDir, doneFile));
286
+ // 尝试从 .done 文件中提取标题,如果失败则使用文件名
287
+ let title = _extractTitleFromMarkdown(path.resolve(tasksDir, `${name}.md`));
288
+ // 如果从 .md 文件提取失败,则尝试从 .done 文件提取
289
+ if (title === name) {
290
+ title = _extractTitleFromMarkdown(path.resolve(tasksDir, doneFile));
291
+ }
292
+ // 使用标准的状态检查函数
293
+ const status = _checkTaskStatus(name, tasksDir);
294
+ // 为状态添加颜色代码
295
+ let coloredStatus;
296
+ switch (status) {
297
+ case '进行中':
298
+ coloredStatus = status.blue;
299
+ break;
300
+ case '已完成':
301
+ coloredStatus = status.green;
302
+ break;
303
+ case '未开始':
304
+ coloredStatus = status.red;
305
+ break;
306
+ default:
307
+ coloredStatus = status;
308
+ }
309
+
310
+ tasks.push({
311
+ name: name,
312
+ title: title,
313
+ path: donePath,
314
+ requirement: requirement,
315
+ status: coloredStatus
316
+ });
317
+ }
318
+ });
319
+
254
320
  }
255
321
  });
256
322
 
323
+ // 按任务名称排序
324
+ tasks.sort((a, b) => a.name.localeCompare(b.name));
325
+
257
326
  if (tasks.length === 0) {
258
327
  Ec.waiting("🔍 未找到任何任务");
259
328
  // 即使未找到任务也执行剪切板任务(使用默认值)
@@ -269,7 +338,8 @@ module.exports = async (options) => {
269
338
  const paddedIndex = String(index + 1).padStart(3, '0');
270
339
  const coloredName = task.name.cyan;
271
340
  const coloredPath = task.path.yellow; // 使用黄色高亮路径
272
- Ec.waiting(`${paddedIndex}. ${coloredName} | ${task.title} / ${coloredPath}`);
341
+
342
+ Ec.waiting(`${paddedIndex}. ${coloredName} [${task.status}] | ${task.title} / ${coloredPath}`);
273
343
  });
274
344
 
275
345
  // 执行剪切板任务(使用第一个任务)