momo-ai 1.0.15 → 1.0.17

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.17",
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 -->
@@ -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
 
@@ -115,6 +115,7 @@ 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
 
@@ -127,6 +128,19 @@ const _findTaskInstances = (taskName) => {
127
128
  relativePath: path.relative(process.cwd(), taskPath)
128
129
  });
129
130
  }
131
+
132
+ // 如果 .md 文件不存在,检查 .done 文件
133
+ const doneFile = `${taskName}.done`;
134
+ const donePath = path.resolve(tasksDir, doneFile);
135
+
136
+ if (!fs.existsSync(taskPath) && fs.existsSync(donePath)) {
137
+ taskInstances.push({
138
+ name: taskName,
139
+ path: donePath,
140
+ requirement: requirement,
141
+ relativePath: path.relative(process.cwd(), donePath)
142
+ });
143
+ }
130
144
  }
131
145
  });
132
146
 
@@ -316,6 +330,46 @@ const _extractTitleFromMarkdown = (filePath) => {
316
330
  }
317
331
  };
318
332
 
333
+ /**
334
+ * 保存任务分派记录
335
+ * @param {string} taskName 任务名称
336
+ * @param {string} workspaceName 工作空间名称
337
+ * @param {string} actorName Actor名称
338
+ * @param {string} requirementName 需求名称
339
+ */
340
+ const _saveAssignmentRecord = async (taskName, workspaceName, actorName, requirementName) => {
341
+ try {
342
+ // 确保 .activities 目录存在
343
+ const activitiesDir = path.resolve(process.cwd(), '.activities');
344
+ if (!fs.existsSync(activitiesDir)) {
345
+ await fsAsync.mkdir(activitiesDir, { recursive: true });
346
+ }
347
+
348
+ // 创建文件名:{任务编号}-develop-xx-时间戳.txt
349
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-').replace('T', '_').slice(0, -5);
350
+ const fileName = `${taskName}-${workspaceName}-${timestamp}.txt`;
351
+ const filePath = path.join(activitiesDir, fileName);
352
+
353
+ // 写入分派记录
354
+ const recordContent = `Task Assignment Record
355
+ =====================
356
+
357
+ Task Name: ${taskName}
358
+ Workspace: ${workspaceName}
359
+ Actor: ${actorName}
360
+ Requirement: ${requirementName}
361
+ Assigned At: ${new Date().toISOString()}
362
+
363
+ This task has been assigned to workspace ${workspaceName} for actor ${actorName}.
364
+ `;
365
+
366
+ await fsAsync.writeFile(filePath, recordContent);
367
+ Ec.waiting(`[Momo AI] 任务分派记录已保存: ${filePath}`);
368
+ } catch (error) {
369
+ Ec.waiting(`⚠️ 无法保存任务分派记录: ${error.message}`);
370
+ }
371
+ };
372
+
319
373
  module.exports = async (options) => {
320
374
  // 参数提取
321
375
  const parsed = Ec.parseArgument(options);
@@ -386,6 +440,13 @@ module.exports = async (options) => {
386
440
  Ec.error('❌ 不能选择正在进行中的任务,请重新选择');
387
441
  continue;
388
442
  }
443
+
444
+ // 检查选中的任务是否已完成
445
+ if (status === '已完成') {
446
+ Ec.info(`✅ 任务 ${allTasks[selectedIndex].name} 已经完成,不用分派`);
447
+ Ec.askClose();
448
+ process.exit(0);
449
+ }
389
450
 
390
451
  // 直接使用用户选择的任务,跳过重复检查
391
452
  selectedTask = allTasks[selectedIndex];
@@ -451,6 +512,13 @@ module.exports = async (options) => {
451
512
  Ec.error('❌ 不能选择正在进行中的任务,请重新选择');
452
513
  continue;
453
514
  }
515
+
516
+ // 检查选中的任务是否已完成
517
+ if (status === '已完成') {
518
+ Ec.info(`✅ 任务 ${taskInstances[selectedIndex].name} 已经完成,不用分派`);
519
+ Ec.askClose();
520
+ process.exit(0);
521
+ }
454
522
 
455
523
  selectedTask = taskInstances[selectedIndex];
456
524
  break;
@@ -466,6 +534,13 @@ module.exports = async (options) => {
466
534
  process.exit(1);
467
535
  }
468
536
 
537
+ // 检查任务是否已完成
538
+ if (status === '已完成') {
539
+ Ec.info(`✅ 任务 ${taskName} 已经完成,不用分派`);
540
+ Ec.askClose();
541
+ process.exit(0);
542
+ }
543
+
469
544
  selectedTask = taskInstances[0];
470
545
  }
471
546
 
@@ -498,6 +573,11 @@ module.exports = async (options) => {
498
573
  workspaceLockPath = _createWorkspaceLock(selectedWorkspace.name, selectedTask.name, actorName);
499
574
  }
500
575
 
576
+ // 保存任务分派记录
577
+ if (selectedWorkspace) {
578
+ await _saveAssignmentRecord(selectedTask.name, selectedWorkspace.name, actorName, requirementName);
579
+ }
580
+
501
581
  // 读取模板文件并填充参数,然后拷贝到剪贴板
502
582
  const templatePath = path.resolve(__dirname, '../_template/PROMPT/run.md.ejs');
503
583
 
@@ -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
  // 执行剪切板任务(使用第一个任务)