momo-ai 1.0.20 → 1.0.22
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/.claude/skills/algorithmic-art/LICENSE.txt +202 -0
- package/.claude/skills/algorithmic-art/SKILL.md +405 -0
- package/.claude/skills/algorithmic-art/templates/generator_template.js +223 -0
- package/.claude/skills/algorithmic-art/templates/viewer.html +599 -0
- package/.claude/skills/r2mo-rad-lain/SKILL.md +101 -0
- package/.cursor/mcp.json +17 -0
- package/.obsidian/app.json +1 -0
- package/.obsidian/appearance.json +4 -0
- package/.obsidian/community-plugins.json +4 -0
- package/.obsidian/core-plugins.json +33 -0
- package/.obsidian/plugins/ai-agent/main.js +98495 -0
- package/.obsidian/plugins/ai-agent/manifest.json +11 -0
- package/.obsidian/plugins/ai-agent/styles.css +806 -0
- package/.obsidian/plugins/dataview/main.js +20876 -0
- package/.obsidian/plugins/dataview/manifest.json +11 -0
- package/.obsidian/plugins/dataview/styles.css +141 -0
- package/.obsidian/plugins/obsidian-excalidraw-plugin/main.js +10 -0
- package/.obsidian/plugins/obsidian-excalidraw-plugin/manifest.json +12 -0
- package/.obsidian/plugins/obsidian-excalidraw-plugin/styles.css +1 -0
- package/.obsidian/plugins/templater-obsidian/main.js +45 -0
- package/.obsidian/plugins/templater-obsidian/manifest.json +11 -0
- package/.obsidian/plugins/templater-obsidian/styles.css +226 -0
- package/.obsidian/plugins/terminal/main.js +200 -0
- package/.obsidian/plugins/terminal/manifest.json +14 -0
- package/.obsidian/plugins/terminal/styles.css +32 -0
- package/.obsidian/themes/AnuPpuccin/manifest.json +7 -0
- package/.obsidian/themes/AnuPpuccin/theme.css +9080 -0
- package/.obsidian/themes/Things/manifest.json +7 -0
- package/.obsidian/themes/Things/theme.css +1628 -0
- package/.obsidian/workspace.json +196 -0
- package/.trae/skills/algorithmic-art/LICENSE.txt +202 -0
- package/.trae/skills/algorithmic-art/SKILL.md +405 -0
- package/.trae/skills/algorithmic-art/templates/generator_template.js +223 -0
- package/.trae/skills/algorithmic-art/templates/viewer.html +599 -0
- package/.trae/skills/doc-coauthoring/SKILL.md +375 -0
- package/.trae/skills/frontend-design/LICENSE.txt +177 -0
- package/.trae/skills/frontend-design/SKILL.md +42 -0
- package/.trae/skills/r2mo-rad-lain/SKILL.md +101 -0
- package/README.md +12 -148
- package/docs/images/logo.jpeg +0 -0
- package/docs/images/r2mo-lain.png +0 -0
- package/install.sh +1 -0
- package/package.json +15 -11
- package/skills/r2mo-rad-domain/SKILL.md +70 -0
- package/skills/r2mo-rad-lain/SKILL.md +101 -0
- package/src/_mcp/skills-server.mjs +70 -0
- package/src/_skill/repositories.json +22 -0
- package/src/_template/LAIN/.obsidian/app.json +1 -0
- package/src/_template/LAIN/.obsidian/appearance.json +10 -0
- package/src/_template/LAIN/.obsidian/community-plugins.json +7 -0
- package/src/_template/LAIN/.obsidian/core-plugins.json +33 -0
- package/src/_template/LAIN/.obsidian/plugins/dataview/main.js +20876 -0
- package/src/_template/LAIN/.obsidian/plugins/dataview/manifest.json +11 -0
- package/src/_template/LAIN/.obsidian/plugins/dataview/styles.css +141 -0
- package/src/_template/LAIN/.obsidian/plugins/obsidian-excalidraw-plugin/data.json +815 -0
- package/src/_template/LAIN/.obsidian/plugins/obsidian-excalidraw-plugin/main.js +10 -0
- package/src/_template/LAIN/.obsidian/plugins/obsidian-excalidraw-plugin/manifest.json +12 -0
- package/src/_template/LAIN/.obsidian/plugins/obsidian-excalidraw-plugin/styles.css +1 -0
- package/src/_template/LAIN/.obsidian/plugins/obsidian-kanban/main.js +153 -0
- package/src/_template/LAIN/.obsidian/plugins/obsidian-kanban/manifest.json +11 -0
- package/src/_template/LAIN/.obsidian/plugins/obsidian-kanban/styles.css +1 -0
- package/src/_template/LAIN/.obsidian/plugins/obsidian-plantuml/main.js +7732 -0
- package/src/_template/LAIN/.obsidian/plugins/obsidian-plantuml/manifest.json +10 -0
- package/src/_template/LAIN/.obsidian/plugins/obsidian-plantuml/styles.css +38 -0
- package/src/_template/LAIN/.obsidian/plugins/obsidian-tasks-plugin/main.js +504 -0
- package/src/_template/LAIN/.obsidian/plugins/obsidian-tasks-plugin/manifest.json +12 -0
- package/src/_template/LAIN/.obsidian/plugins/obsidian-tasks-plugin/styles.css +1 -0
- package/src/_template/LAIN/.obsidian/snippets/body-font.css +27 -0
- package/src/_template/LAIN/.obsidian/themes/Primary/manifest.json +9 -0
- package/src/_template/LAIN/.obsidian/themes/Primary/theme.css +3878 -0
- package/src/_template/LAIN/.obsidian/themes/Retro Windows/manifest.json +7 -0
- package/src/_template/LAIN/.obsidian/themes/Retro Windows/theme.css +582 -0
- package/src/_template/LAIN/.obsidian/themes/RetroOS 98/manifest.json +9 -0
- package/src/_template/LAIN/.obsidian/themes/RetroOS 98/theme.css +2566 -0
- package/src/_template/LAIN/.obsidian/types.json +28 -0
- package/src/_template/LAIN/.obsidian/workspace.json +184 -0
- package/src/_template/LAIN/AGENTS.md +170 -16
- package/src/_template/R2MO/domain-enhance.md +10 -0
- package/src/commander/app.json +13 -0
- package/src/commander/apply.json +13 -0
- package/src/commander/ask.json +6 -0
- package/src/commander/docs.json +13 -0
- package/src/commander/domain.json +19 -0
- package/src/commander/help.json +5 -0
- package/src/commander/init.json +1 -1
- package/src/commander/mcp.json +13 -0
- package/src/commander/mmr0.json +6 -0
- package/src/commander/mmr2.json +6 -0
- package/src/commander/open.json +8 -2
- package/src/executor/executeApp.js +133 -0
- package/src/executor/executeApply.js +611 -0
- package/src/executor/executeAsk.js +274 -0
- package/src/executor/executeDocs.js +498 -0
- package/src/executor/executeDomain.js +293 -0
- package/src/executor/executeEnv.js +48 -38
- package/src/executor/executeHelp.js +77 -16
- package/src/executor/executeInit.js +176 -346
- package/src/executor/executeMcp.js +363 -0
- package/src/executor/executeMmr0.js +488 -0
- package/src/executor/executeMmr2.js +880 -0
- package/src/executor/executeOpen.js +144 -125
- package/src/executor/index.js +17 -39
- package/src/momo.js +2 -1
- package/src/python/r2mo_proto.py +418 -0
- package/src/python/r2mo_proto_database.py +369 -0
- package/src/python/r2mo_proto_domain.py +458 -0
- package/src/utils/momo-args.js +39 -0
- package/src/utils/momo-file-utils.js +75 -0
- package/src/utils/momo-menu.js +84 -0
- package/src/_template/LAIN/.momo/advanced/actor.md +0 -42
- package/src/_template/LAIN/.momo/advanced/refer.json +0 -46
- package/src/_template/LAIN/.momo/scripts/submodule-clean.sh +0 -56
- package/src/_template/LAIN/changes/proposal.md +0 -39
- package/src/_template/LAIN/changes/tasks/task-detail.md +0 -45
- package/src/_template/LAIN/changes/tasks.md +0 -49
- package/src/_template/LAIN/execute/admin-n-f-dashboard.md +0 -53
- package/src/_template/LAIN/execute/admin-n-f-form.md +0 -51
- package/src/_template/LAIN/execute/admin-n-f-home.md +0 -49
- package/src/_template/LAIN/execute/admin-n-f-list.md +0 -52
- package/src/_template/LAIN/execute/admin-n-f-login.md +0 -56
- package/src/_template/LAIN/specification/project-model.md +0 -13
- package/src/_template/LAIN/specification/project.md +0 -73
- package/src/_template/LAIN/specification/requirement.md +0 -25
- package/src/commander/actor.json +0 -12
- package/src/commander/actors.json +0 -6
- package/src/commander/add.json +0 -12
- package/src/commander/agent.json +0 -12
- package/src/commander/agentcfg.json +0 -5
- package/src/commander/archive.json +0 -12
- package/src/commander/commit.json +0 -12
- package/src/commander/console.json +0 -7
- package/src/commander/lain.json +0 -7
- package/src/commander/list.json +0 -7
- package/src/commander/plan.json +0 -12
- package/src/commander/project.json +0 -12
- package/src/commander/pull.json +0 -6
- package/src/commander/push.json +0 -6
- package/src/commander/repo.json +0 -18
- package/src/commander/run.json +0 -18
- package/src/commander/show.json +0 -12
- package/src/commander/tasks.json +0 -18
- package/src/commander/unlock.json +0 -6
- package/src/commander/validate.json +0 -12
- package/src/executor/executeActor.js +0 -133
- package/src/executor/executeActors.js +0 -58
- package/src/executor/executeAdd.js +0 -307
- package/src/executor/executeAgent.js +0 -299
- package/src/executor/executeAgentCfg.js +0 -210
- package/src/executor/executeArchive.js +0 -124
- package/src/executor/executeCommit.js +0 -202
- package/src/executor/executeConsole.js +0 -142
- package/src/executor/executeList.js +0 -133
- package/src/executor/executePlan.js +0 -164
- package/src/executor/executeProject.js +0 -313
- package/src/executor/executePull.js +0 -127
- package/src/executor/executePush.js +0 -243
- package/src/executor/executeRepo.js +0 -238
- package/src/executor/executeRun.js +0 -644
- package/src/executor/executeShow.js +0 -164
- package/src/executor/executeTasks.js +0 -384
- package/src/executor/executeUnlock.js +0 -110
- package/src/executor/executeValidate.js +0 -210
|
@@ -1,644 +0,0 @@
|
|
|
1
|
-
const Ec = require('../epic');
|
|
2
|
-
const fs = require('fs');
|
|
3
|
-
const path = require('path');
|
|
4
|
-
const util = require('util');
|
|
5
|
-
const { outCopy } = require('../epic/momo.fn.out');
|
|
6
|
-
const fsAsync = require('fs').promises;
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* 读取模板文件并提取 <!-- BEGIN --> 到 <!-- END --> 之间的内容
|
|
10
|
-
* @param {string} templatePath 模板文件路径
|
|
11
|
-
* @returns {Promise<string>} 提取的模板内容
|
|
12
|
-
*/
|
|
13
|
-
const _readTemplate = async (templatePath) => {
|
|
14
|
-
try {
|
|
15
|
-
const content = await fsAsync.readFile(templatePath, 'utf8');
|
|
16
|
-
const lines = content.split('\n');
|
|
17
|
-
let beginIndex = -1;
|
|
18
|
-
let endIndex = -1;
|
|
19
|
-
|
|
20
|
-
for (let i = 0; i < lines.length; i++) {
|
|
21
|
-
if (lines[i].includes('<!-- BEGIN -->')) {
|
|
22
|
-
beginIndex = i;
|
|
23
|
-
} else if (lines[i].includes('<!-- END -->')) {
|
|
24
|
-
endIndex = i;
|
|
25
|
-
break;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
if (beginIndex !== -1 && endIndex !== -1) {
|
|
30
|
-
// 提取 BEGIN 和 END 之间的内容(不包括 BEGIN 和 END 行)
|
|
31
|
-
return lines.slice(beginIndex + 1, endIndex).join('\n');
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
return content;
|
|
35
|
-
} catch (error) {
|
|
36
|
-
Ec.waiting(`读取模板文件失败: ${error.message}`);
|
|
37
|
-
throw error;
|
|
38
|
-
}
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* 渲染 EJS 模板
|
|
43
|
-
* @param {string} template 模板内容
|
|
44
|
-
* @param {Object} data 数据对象
|
|
45
|
-
* @returns {string} 渲染后的内容
|
|
46
|
-
*/
|
|
47
|
-
const _renderTemplate = (template, data) => {
|
|
48
|
-
let result = template;
|
|
49
|
-
for (const key in data) {
|
|
50
|
-
if (data.hasOwnProperty(key)) {
|
|
51
|
-
const regex = new RegExp(`<%=\\s*${key}\\s*%>`, 'g');
|
|
52
|
-
result = result.replace(regex, data[key]);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
return result;
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* 将内容复制到剪贴板
|
|
60
|
-
* @param {string} content 要复制的内容
|
|
61
|
-
* @param {string} requirementName 需求名称
|
|
62
|
-
* @param {string} taskName 任务名称
|
|
63
|
-
*/
|
|
64
|
-
const _copyToClipboard = async (content, requirementName, taskName) => {
|
|
65
|
-
try {
|
|
66
|
-
// 使用统一的剪贴板函数
|
|
67
|
-
await outCopy(content);
|
|
68
|
-
Ec.waiting('📄 任务执行提示词已拷贝到剪切板');
|
|
69
|
-
|
|
70
|
-
// 保存内容到 .working 目录
|
|
71
|
-
const workingDir = path.resolve(process.cwd(), '.working');
|
|
72
|
-
const fileName = `REQ-${requirementName}-${taskName}.txt`;
|
|
73
|
-
const filePath = path.join(workingDir, fileName);
|
|
74
|
-
|
|
75
|
-
// 确保 .working 目录存在
|
|
76
|
-
if (!fs.existsSync(workingDir)) {
|
|
77
|
-
await fsAsync.mkdir(workingDir, { recursive: true });
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// 写入文件
|
|
81
|
-
await fsAsync.writeFile(filePath, content);
|
|
82
|
-
Ec.waiting(`📄 任务执行提示词已保存到文件: ${filePath}`);
|
|
83
|
-
} catch (error) {
|
|
84
|
-
Ec.waiting(`⚠️ 无法拷贝到剪切板: ${error.message}`);
|
|
85
|
-
// 备选方案:显示内容
|
|
86
|
-
Ec.waiting('提示词内容:');
|
|
87
|
-
console.log(content);
|
|
88
|
-
if (process.platform === 'win32') {
|
|
89
|
-
Ec.waiting('💡 Windows 用户提示: 请确保您在具有足够权限的命令行中运行此命令');
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* 查找所有匹配的任务实例
|
|
96
|
-
* @param {string} taskName 任务名称
|
|
97
|
-
* @returns {Array} 任务实例列表
|
|
98
|
-
*/
|
|
99
|
-
const _findTaskInstances = (taskName) => {
|
|
100
|
-
const changesDir = path.resolve(process.cwd(), 'specification', 'changes');
|
|
101
|
-
const taskInstances = [];
|
|
102
|
-
|
|
103
|
-
if (!fs.existsSync(changesDir)) {
|
|
104
|
-
return taskInstances;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// 获取所有需求目录
|
|
108
|
-
const requirements = fs.readdirSync(changesDir).filter(file =>
|
|
109
|
-
fs.statSync(path.join(changesDir, file)).isDirectory()
|
|
110
|
-
);
|
|
111
|
-
|
|
112
|
-
// 查找所有匹配的任务文件
|
|
113
|
-
requirements.forEach(requirement => {
|
|
114
|
-
const tasksDir = path.resolve(changesDir, requirement, 'tasks');
|
|
115
|
-
|
|
116
|
-
// 检查 tasks 目录是否存在
|
|
117
|
-
if (fs.existsSync(tasksDir) && fs.statSync(tasksDir).isDirectory()) {
|
|
118
|
-
// 检查 .md 文件
|
|
119
|
-
const taskFile = `${taskName}.md`;
|
|
120
|
-
const taskPath = path.resolve(tasksDir, taskFile);
|
|
121
|
-
|
|
122
|
-
// 检查任务文件是否存在
|
|
123
|
-
if (fs.existsSync(taskPath)) {
|
|
124
|
-
// 提取任务标题
|
|
125
|
-
const title = _extractTitleFromMarkdown(taskPath);
|
|
126
|
-
taskInstances.push({
|
|
127
|
-
name: taskName,
|
|
128
|
-
path: taskPath,
|
|
129
|
-
requirement: requirement,
|
|
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
|
|
148
|
-
});
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
return taskInstances;
|
|
154
|
-
};
|
|
155
|
-
|
|
156
|
-
/**
|
|
157
|
-
* 枚举所有可用的任务
|
|
158
|
-
* @returns {Array} 任务列表
|
|
159
|
-
*/
|
|
160
|
-
const _enumerateAllTasks = () => {
|
|
161
|
-
const changesDir = path.resolve(process.cwd(), 'specification', 'changes');
|
|
162
|
-
const tasks = [];
|
|
163
|
-
|
|
164
|
-
if (!fs.existsSync(changesDir)) {
|
|
165
|
-
return tasks;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// 获取所有需求目录
|
|
169
|
-
const requirements = fs.readdirSync(changesDir).filter(file =>
|
|
170
|
-
fs.statSync(path.join(changesDir, file)).isDirectory()
|
|
171
|
-
);
|
|
172
|
-
|
|
173
|
-
// 查找所有任务文件
|
|
174
|
-
requirements.forEach(requirement => {
|
|
175
|
-
const tasksDir = path.resolve(changesDir, requirement, 'tasks');
|
|
176
|
-
|
|
177
|
-
// 检查 tasks 目录是否存在
|
|
178
|
-
if (fs.existsSync(tasksDir) && fs.statSync(tasksDir).isDirectory()) {
|
|
179
|
-
const taskFiles = fs.readdirSync(tasksDir).filter(file =>
|
|
180
|
-
file.endsWith('.md')
|
|
181
|
-
);
|
|
182
|
-
|
|
183
|
-
taskFiles.forEach(taskFile => {
|
|
184
|
-
const taskName = path.basename(taskFile, '.md');
|
|
185
|
-
const taskPath = path.resolve(tasksDir, taskFile);
|
|
186
|
-
const title = _extractTitleFromMarkdown(taskPath);
|
|
187
|
-
|
|
188
|
-
tasks.push({
|
|
189
|
-
name: taskName,
|
|
190
|
-
path: taskPath,
|
|
191
|
-
requirement: requirement,
|
|
192
|
-
relativePath: path.relative(process.cwd(), taskPath),
|
|
193
|
-
display: `${requirement}/${taskName}`,
|
|
194
|
-
title: title
|
|
195
|
-
});
|
|
196
|
-
});
|
|
197
|
-
}
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
return tasks;
|
|
201
|
-
};
|
|
202
|
-
|
|
203
|
-
/**
|
|
204
|
-
* 获取所有可用的工作空间(排除已被锁定的)
|
|
205
|
-
* @returns {Array} 可用工作空间列表
|
|
206
|
-
*/
|
|
207
|
-
const _getAvailableWorkspaces = () => {
|
|
208
|
-
const sourceDir = path.resolve(process.cwd(), 'source');
|
|
209
|
-
const workspaces = [];
|
|
210
|
-
|
|
211
|
-
if (!fs.existsSync(sourceDir)) {
|
|
212
|
-
return workspaces;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
// 获取所有目录
|
|
216
|
-
const dirs = fs.readdirSync(sourceDir).filter(file =>
|
|
217
|
-
fs.statSync(path.join(sourceDir, file)).isDirectory()
|
|
218
|
-
);
|
|
219
|
-
|
|
220
|
-
// 检查哪些工作空间可用(没有对应的.lock文件)
|
|
221
|
-
dirs.forEach(dir => {
|
|
222
|
-
const lockFilePath = path.resolve(sourceDir, `${dir}.lock`);
|
|
223
|
-
if (!fs.existsSync(lockFilePath)) {
|
|
224
|
-
workspaces.push({
|
|
225
|
-
name: dir,
|
|
226
|
-
path: path.join('source', dir)
|
|
227
|
-
});
|
|
228
|
-
}
|
|
229
|
-
});
|
|
230
|
-
|
|
231
|
-
return workspaces;
|
|
232
|
-
};
|
|
233
|
-
|
|
234
|
-
/**
|
|
235
|
-
* 自动选择一个可用的工作空间
|
|
236
|
-
* @returns {Object|null} 选中的工作空间对象或null
|
|
237
|
-
*/
|
|
238
|
-
const _selectWorkspace = () => {
|
|
239
|
-
const workspaces = _getAvailableWorkspaces();
|
|
240
|
-
|
|
241
|
-
if (workspaces.length === 0) {
|
|
242
|
-
return null;
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
// 随机选择一个工作空间
|
|
246
|
-
const randomIndex = Math.floor(Math.random() * workspaces.length);
|
|
247
|
-
return workspaces[randomIndex];
|
|
248
|
-
};
|
|
249
|
-
|
|
250
|
-
/**
|
|
251
|
-
* 创建任务锁文件
|
|
252
|
-
* @param {string} taskName 任务名称
|
|
253
|
-
* @param {string} requirementName 需求名称
|
|
254
|
-
* @param {string} actorName Actor名称
|
|
255
|
-
* @returns {string} 锁文件路径
|
|
256
|
-
*/
|
|
257
|
-
const _createTaskLock = (taskName, requirementName, actorName) => {
|
|
258
|
-
const taskLockDir = path.resolve(process.cwd(), 'specification', 'changes', requirementName, 'tasks');
|
|
259
|
-
const taskLockPath = path.resolve(taskLockDir, `${taskName}.lock`);
|
|
260
|
-
|
|
261
|
-
// 确保目录存在
|
|
262
|
-
if (!fs.existsSync(taskLockDir)) {
|
|
263
|
-
fs.mkdirSync(taskLockDir, {recursive: true});
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
// 创建锁文件
|
|
267
|
-
fs.writeFileSync(taskLockPath, `Locked by: ${actorName}\nLocked at: ${new Date().toISOString()}\n`);
|
|
268
|
-
return taskLockPath;
|
|
269
|
-
};
|
|
270
|
-
|
|
271
|
-
/**
|
|
272
|
-
* 创建工作空间锁文件
|
|
273
|
-
* @param {string} workspaceName 工作空间名称
|
|
274
|
-
* @param {string} taskName 任务名称
|
|
275
|
-
* @param {string} actorName Actor名称
|
|
276
|
-
* @returns {string} 锁文件路径
|
|
277
|
-
*/
|
|
278
|
-
const _createWorkspaceLock = (workspaceName, taskName, actorName) => {
|
|
279
|
-
const sourceDir = path.resolve(process.cwd(), 'source');
|
|
280
|
-
const workspaceLockPath = path.resolve(sourceDir, `${workspaceName}.lock`);
|
|
281
|
-
|
|
282
|
-
// 创建锁文件
|
|
283
|
-
fs.writeFileSync(workspaceLockPath, `Task: ${taskName}\nLocked by: ${actorName}\nLocked at: ${new Date().toISOString()}\n`);
|
|
284
|
-
return workspaceLockPath;
|
|
285
|
-
};
|
|
286
|
-
|
|
287
|
-
/**
|
|
288
|
-
* 检查任务状态
|
|
289
|
-
* @param {string} taskName 任务名称
|
|
290
|
-
* @param {string} tasksDir 任务目录
|
|
291
|
-
* @returns {string} 任务状态
|
|
292
|
-
*/
|
|
293
|
-
const _checkTaskStatus = (taskName, tasksDir) => {
|
|
294
|
-
// 检查.lock文件(进行中)
|
|
295
|
-
const lockFile = path.join(tasksDir, `${taskName}.lock`);
|
|
296
|
-
if (fs.existsSync(lockFile)) {
|
|
297
|
-
return '进行中';
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
// 检查.done文件(已完成)
|
|
301
|
-
const doneFile = path.join(tasksDir, `${taskName}.done`);
|
|
302
|
-
if (fs.existsSync(doneFile)) {
|
|
303
|
-
return '已完成';
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
// 默认状态(未开始)
|
|
307
|
-
return '未开始';
|
|
308
|
-
};
|
|
309
|
-
|
|
310
|
-
/**
|
|
311
|
-
* 从 Markdown 文件中提取第一个标题
|
|
312
|
-
* @param {string} filePath 文件路径
|
|
313
|
-
* @returns {string} 第一个标题内容
|
|
314
|
-
*/
|
|
315
|
-
const _extractTitleFromMarkdown = (filePath) => {
|
|
316
|
-
try {
|
|
317
|
-
if (!fs.existsSync(filePath)) {
|
|
318
|
-
return path.basename(filePath, '.md');
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
const content = fs.readFileSync(filePath, 'utf8');
|
|
322
|
-
const lines = content.split('\n');
|
|
323
|
-
|
|
324
|
-
for (const line of lines) {
|
|
325
|
-
// 匹配 # 开头的标题行
|
|
326
|
-
const titleMatch = line.match(/^#\s+(.+)$/);
|
|
327
|
-
if (titleMatch) {
|
|
328
|
-
return titleMatch[1].trim();
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
// 如果没有找到标题,使用文件名(不含扩展名)作为任务名称
|
|
333
|
-
return path.basename(filePath, '.md');
|
|
334
|
-
} catch (error) {
|
|
335
|
-
return path.basename(filePath, '.md');
|
|
336
|
-
}
|
|
337
|
-
};
|
|
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
|
-
|
|
381
|
-
module.exports = async (options) => {
|
|
382
|
-
// 参数提取
|
|
383
|
-
const parsed = Ec.parseArgument(options);
|
|
384
|
-
|
|
385
|
-
const actorName = parsed.actor;
|
|
386
|
-
const taskName = parsed.task;
|
|
387
|
-
|
|
388
|
-
try {
|
|
389
|
-
let selectedTask;
|
|
390
|
-
|
|
391
|
-
// 如果没有提供任务名称,则让用户选择任务
|
|
392
|
-
if (!taskName || taskName === 'unset') {
|
|
393
|
-
Ec.waiting("🔍 未指定任务,正在枚举所有可用任务...");
|
|
394
|
-
|
|
395
|
-
// 枚举所有任务
|
|
396
|
-
const allTasks = _enumerateAllTasks();
|
|
397
|
-
|
|
398
|
-
if (allTasks.length === 0) {
|
|
399
|
-
Ec.error("❌ 未找到任何任务");
|
|
400
|
-
Ec.askClose();
|
|
401
|
-
process.exit(1);
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
// 循环直到用户选择一个非进行中的任务
|
|
405
|
-
while (true) {
|
|
406
|
-
Ec.waiting(`🔍 找到 ${allTasks.length} 个任务,请选择要执行的任务:`);
|
|
407
|
-
|
|
408
|
-
allTasks.forEach((task, index) => {
|
|
409
|
-
// 获取任务状态
|
|
410
|
-
const tasksDir = path.resolve(process.cwd(), 'specification', 'changes', task.requirement, 'tasks');
|
|
411
|
-
const status = _checkTaskStatus(task.name, tasksDir);
|
|
412
|
-
|
|
413
|
-
// 为状态添加颜色代码
|
|
414
|
-
let coloredStatus;
|
|
415
|
-
switch (status) {
|
|
416
|
-
case '进行中':
|
|
417
|
-
coloredStatus = status.blue;
|
|
418
|
-
break;
|
|
419
|
-
case '已完成':
|
|
420
|
-
coloredStatus = status.green;
|
|
421
|
-
break;
|
|
422
|
-
case '未开始':
|
|
423
|
-
coloredStatus = status.red;
|
|
424
|
-
break;
|
|
425
|
-
default:
|
|
426
|
-
coloredStatus = status;
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
const coloredName = task.name.cyan;
|
|
430
|
-
Ec.waiting(`${index + 1}. [${coloredStatus}] ${task.display} (${coloredName})`);
|
|
431
|
-
});
|
|
432
|
-
|
|
433
|
-
// 添加退出选项
|
|
434
|
-
Ec.waiting(`${allTasks.length + 1}. 退出`);
|
|
435
|
-
|
|
436
|
-
// 获取用户选择
|
|
437
|
-
const answer = await Ec.ask('请输入选项编号: ');
|
|
438
|
-
const selectedIndex = parseInt(answer) - 1;
|
|
439
|
-
|
|
440
|
-
// 验证选择
|
|
441
|
-
if (isNaN(selectedIndex) || selectedIndex < 0 || selectedIndex > allTasks.length) {
|
|
442
|
-
Ec.error('❌ 无效的选择');
|
|
443
|
-
continue;
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
// 检查是否选择退出
|
|
447
|
-
if (selectedIndex === allTasks.length) {
|
|
448
|
-
Ec.info("已退出任务选择");
|
|
449
|
-
Ec.askClose();
|
|
450
|
-
process.exit(0);
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
// 检查选中的任务是否正在进行中
|
|
454
|
-
const tasksDir = path.resolve(process.cwd(), 'specification', 'changes', allTasks[selectedIndex].requirement, 'tasks');
|
|
455
|
-
const status = _checkTaskStatus(allTasks[selectedIndex].name, tasksDir);
|
|
456
|
-
|
|
457
|
-
if (status === '进行中') {
|
|
458
|
-
Ec.error('❌ 不能选择正在进行中的任务,请重新选择');
|
|
459
|
-
continue;
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
// 检查选中的任务是否已完成
|
|
463
|
-
if (status === '已完成') {
|
|
464
|
-
Ec.info(`✅ 任务 ${allTasks[selectedIndex].name} 已经完成,不用分派`);
|
|
465
|
-
Ec.askClose();
|
|
466
|
-
process.exit(0);
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
// 直接使用用户选择的任务,跳过重复检查
|
|
470
|
-
selectedTask = allTasks[selectedIndex];
|
|
471
|
-
Ec.waiting(`✅ 选择任务: ${selectedTask.display}`);
|
|
472
|
-
break;
|
|
473
|
-
}
|
|
474
|
-
} else {
|
|
475
|
-
// 查找所有匹配的任务实例
|
|
476
|
-
const taskInstances = _findTaskInstances(taskName);
|
|
477
|
-
|
|
478
|
-
if (taskInstances.length === 0) {
|
|
479
|
-
Ec.error(`❌ 未找到任务 ${taskName}`);
|
|
480
|
-
Ec.askClose();
|
|
481
|
-
process.exit(1);
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
// 如果有多个任务实例,让用户选择
|
|
485
|
-
if (taskInstances.length > 1) {
|
|
486
|
-
// 循环直到用户选择一个非进行中的任务
|
|
487
|
-
while (true) {
|
|
488
|
-
Ec.waiting(`🔍 发现 ${taskInstances.length} 个重复任务,请选择要执行的任务:`);
|
|
489
|
-
|
|
490
|
-
taskInstances.forEach((task, index) => {
|
|
491
|
-
// 获取任务状态
|
|
492
|
-
const tasksDir = path.resolve(process.cwd(), 'specification', 'changes', task.requirement, 'tasks');
|
|
493
|
-
const status = _checkTaskStatus(task.name, tasksDir);
|
|
494
|
-
|
|
495
|
-
// 为状态添加颜色代码
|
|
496
|
-
let coloredStatus;
|
|
497
|
-
switch (status) {
|
|
498
|
-
case '进行中':
|
|
499
|
-
coloredStatus = status.blue;
|
|
500
|
-
break;
|
|
501
|
-
case '已完成':
|
|
502
|
-
coloredStatus = status.green;
|
|
503
|
-
break;
|
|
504
|
-
case '未开始':
|
|
505
|
-
coloredStatus = status.red;
|
|
506
|
-
break;
|
|
507
|
-
default:
|
|
508
|
-
coloredStatus = status;
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
const coloredName = task.name.cyan;
|
|
512
|
-
Ec.waiting(`${index + 1}. [${coloredStatus}] ${task.relativePath} (${coloredName})`);
|
|
513
|
-
});
|
|
514
|
-
|
|
515
|
-
// 获取用户选择
|
|
516
|
-
const answer = await Ec.ask('请输入选项编号: ');
|
|
517
|
-
const selectedIndex = parseInt(answer) - 1;
|
|
518
|
-
|
|
519
|
-
// 验证选择
|
|
520
|
-
if (isNaN(selectedIndex) || selectedIndex < 0 || selectedIndex >= taskInstances.length) {
|
|
521
|
-
Ec.error('❌ 无效的选择');
|
|
522
|
-
continue;
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
// 检查选中的任务是否正在进行中
|
|
526
|
-
const tasksDir = path.resolve(process.cwd(), 'specification', 'changes', taskInstances[selectedIndex].requirement, 'tasks');
|
|
527
|
-
const status = _checkTaskStatus(taskInstances[selectedIndex].name, tasksDir);
|
|
528
|
-
|
|
529
|
-
if (status === '进行中') {
|
|
530
|
-
Ec.error('❌ 不能选择正在进行中的任务,请重新选择');
|
|
531
|
-
continue;
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
// 检查选中的任务是否已完成
|
|
535
|
-
if (status === '已完成') {
|
|
536
|
-
Ec.info(`✅ 任务 ${taskInstances[selectedIndex].name} 已经完成,不用分派`);
|
|
537
|
-
Ec.askClose();
|
|
538
|
-
process.exit(0);
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
selectedTask = taskInstances[selectedIndex];
|
|
542
|
-
break;
|
|
543
|
-
}
|
|
544
|
-
} else {
|
|
545
|
-
// 检查唯一任务是否正在进行中
|
|
546
|
-
const tasksDir = path.resolve(process.cwd(), 'specification', 'changes', taskInstances[0].requirement, 'tasks');
|
|
547
|
-
const status = _checkTaskStatus(taskInstances[0].name, tasksDir);
|
|
548
|
-
|
|
549
|
-
if (status === '进行中') {
|
|
550
|
-
Ec.error(`❌ 任务 ${taskName} 正在进行中,无法重复执行`);
|
|
551
|
-
Ec.askClose();
|
|
552
|
-
process.exit(1);
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
// 检查任务是否已完成
|
|
556
|
-
if (status === '已完成') {
|
|
557
|
-
Ec.info(`✅ 任务 ${taskName} 已经完成,不用分派`);
|
|
558
|
-
Ec.askClose();
|
|
559
|
-
process.exit(0);
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
selectedTask = taskInstances[0];
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
Ec.waiting(`✅ 找到任务: ${selectedTask.relativePath}`);
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
// 记录需求名称
|
|
569
|
-
const requirementName = selectedTask.requirement;
|
|
570
|
-
|
|
571
|
-
// 自动选择工作空间
|
|
572
|
-
const selectedWorkspace = _selectWorkspace();
|
|
573
|
-
|
|
574
|
-
if (!selectedWorkspace) {
|
|
575
|
-
Ec.error("❌ 所有工作空间都已被占用,请稍后再试");
|
|
576
|
-
Ec.askClose();
|
|
577
|
-
process.exit(1);
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
// 创建锁文件
|
|
581
|
-
let taskLockPath = null;
|
|
582
|
-
let workspaceLockPath = null;
|
|
583
|
-
|
|
584
|
-
// 创建任务锁文件
|
|
585
|
-
if (requirementName) {
|
|
586
|
-
taskLockPath = _createTaskLock(selectedTask.name, requirementName, actorName);
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
// 创建工作空间锁文件
|
|
590
|
-
if (selectedWorkspace) {
|
|
591
|
-
workspaceLockPath = _createWorkspaceLock(selectedWorkspace.name, selectedTask.name, actorName);
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
// 保存任务分派记录
|
|
595
|
-
if (selectedWorkspace) {
|
|
596
|
-
await _saveAssignmentRecord(selectedTask.name, selectedWorkspace.name, actorName, requirementName);
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
// 读取模板文件并填充参数,然后拷贝到剪贴板
|
|
600
|
-
const templatePath = path.resolve(__dirname, '../_template/PROMPT/run.md.ejs');
|
|
601
|
-
|
|
602
|
-
if (fs.existsSync(templatePath)) {
|
|
603
|
-
try {
|
|
604
|
-
// 读取并处理提示词模板
|
|
605
|
-
const templateContent = await _readTemplate(templatePath);
|
|
606
|
-
const renderedContent = _renderTemplate(templateContent, {
|
|
607
|
-
TASK: selectedTask.name,
|
|
608
|
-
REQ: requirementName,
|
|
609
|
-
ROOT: selectedWorkspace ? selectedWorkspace.path : 'source/<WORKSPACE_PATH>'
|
|
610
|
-
});
|
|
611
|
-
|
|
612
|
-
// 打印额外信息
|
|
613
|
-
Ec.waiting(`执行任务: ${selectedTask.name} - ${selectedTask.title || '未知任务'}`);
|
|
614
|
-
Ec.waiting(`使用工作空间: ${selectedWorkspace.path}`);
|
|
615
|
-
Ec.waiting('提示词内容:');
|
|
616
|
-
Ec.waiting('--------------------------------------------------');
|
|
617
|
-
|
|
618
|
-
// 按行打印提示词内容,保持原有格式
|
|
619
|
-
const lines = renderedContent.split('\n');
|
|
620
|
-
lines.forEach(line => {
|
|
621
|
-
Ec.waiting(line);
|
|
622
|
-
});
|
|
623
|
-
|
|
624
|
-
Ec.waiting('--------------------------------------------------');
|
|
625
|
-
|
|
626
|
-
// 将提示词复制到剪贴板
|
|
627
|
-
await _copyToClipboard(renderedContent, requirementName, selectedTask.name);
|
|
628
|
-
Ec.info(`✅ 任务 ${selectedTask.name} 准备完成,请查看剪切板中的提示词`);
|
|
629
|
-
} catch (error) {
|
|
630
|
-
Ec.info(`✅ 任务 ${selectedTask.name} 查找完成`);
|
|
631
|
-
}
|
|
632
|
-
} else {
|
|
633
|
-
Ec.info(`✅ 任务 ${selectedTask.name} 查找完成`);
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
Ec.askClose();
|
|
637
|
-
process.exit(0);
|
|
638
|
-
|
|
639
|
-
} catch (error) {
|
|
640
|
-
Ec.error(`❌ 执行过程中发生错误: ${error.message}`);
|
|
641
|
-
Ec.askClose();
|
|
642
|
-
process.exit(1);
|
|
643
|
-
}
|
|
644
|
-
};
|