momo-ai 1.0.0 → 1.0.1

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 (107) hide show
  1. package/README.md +116 -0
  2. package/package.json +2 -1
  3. package/src/_template/LAIN/.momo/advanced/actor.md +42 -0
  4. package/src/_template/LAIN/.momo/scripts/submodule-clean.sh +56 -0
  5. package/src/_template/LAIN/AGENTS.md +103 -0
  6. package/src/_template/LAIN/changes/proposal.md +39 -0
  7. package/src/_template/LAIN/changes/tasks/task-detail.md +45 -0
  8. package/src/_template/LAIN/changes/tasks.md +49 -0
  9. package/src/_template/LAIN/execute/admin-n-f-dashboard.md +53 -0
  10. package/src/_template/LAIN/execute/admin-n-f-form.md +51 -0
  11. package/src/_template/LAIN/execute/admin-n-f-home.md +49 -0
  12. package/src/_template/LAIN/execute/admin-n-f-list.md +52 -0
  13. package/src/_template/LAIN/execute/admin-n-f-login.md +56 -0
  14. package/src/_template/LAIN/specification/project-model.md +13 -0
  15. package/src/_template/LAIN/specification/project.md +73 -0
  16. package/src/_template/LAIN/specification/requirement.md +25 -0
  17. package/src/_template/PROMPT/add.md.ejs +5 -0
  18. package/src/_template/PROMPT/plan.md.ejs +5 -0
  19. package/src/_template/PROMPT/run.md.ejs +9 -0
  20. package/src/_template/PROMPT/tasks.md.ejs +5 -0
  21. package/src/commander/actor.json +12 -0
  22. package/src/commander/actors.json +6 -0
  23. package/src/commander/add.json +12 -0
  24. package/src/commander/archive.json +12 -0
  25. package/src/commander/env.json +7 -0
  26. package/src/commander/help.json +7 -0
  27. package/src/commander/init.json +13 -0
  28. package/src/commander/list.json +7 -0
  29. package/src/commander/open.json +7 -0
  30. package/src/commander/plan.json +12 -0
  31. package/src/commander/repo.json +18 -0
  32. package/src/commander/run.json +18 -0
  33. package/src/commander/show.json +12 -0
  34. package/src/commander/tasks.json +18 -0
  35. package/src/commander/unlock.json +6 -0
  36. package/src/commander/validate.json +12 -0
  37. package/src/epic/history/ai.economy.impl.fn.excel.js +148 -0
  38. package/src/epic/history/ai.economy.impl.fn.execute.js +117 -0
  39. package/src/epic/history/ai.economy.impl.fn.java.js +140 -0
  40. package/src/epic/history/ai.economy.impl.fn.json.js +23 -0
  41. package/src/epic/history/ai.economy.impl.fn.plugin.js +160 -0
  42. package/src/epic/history/ai.economy.impl.fn.react.js +219 -0
  43. package/src/epic/history/ai.export.impl.fn.parse.js +345 -0
  44. package/src/epic/history/ai.export.impl.fn.seek.js +42 -0
  45. package/src/epic/history/ai.export.interface.fn.string.js +5 -0
  46. package/src/epic/history/ai.export.interface.io.js +69 -0
  47. package/src/epic/history/ai.export.interface.util.js +17 -0
  48. package/src/epic/history/ai.parse.metadata.js +94 -0
  49. package/src/epic/history/ai.path.fn.dir.operation.js +87 -0
  50. package/src/epic/history/ai.path.fn.io.command.js +43 -0
  51. package/src/epic/history/ai.path.fn.io.specification.js +59 -0
  52. package/src/epic/history/ai.path.fn.io.typed.js +63 -0
  53. package/src/epic/history/ai.path.fn.out.content.js +47 -0
  54. package/src/epic/history/ai.string.fn.str.util.js +63 -0
  55. package/src/epic/history/ai.uncork.fn.element.feature.js +52 -0
  56. package/src/epic/history/ai.uncork.fn.it.feature.js +118 -0
  57. package/src/epic/history/ai.uncork.fn.to.typed.js +74 -0
  58. package/src/epic/history/ai.under.fn.cx.evaluate.js +81 -0
  59. package/src/epic/history/ai.under.fn.fx.terminal.js +143 -0
  60. package/src/epic/history/ai.unified.fn.fn.error.code.js +108 -0
  61. package/src/epic/history/ai.unified.fn.is.decision.js +20 -0
  62. package/src/epic/history/ai.unified.fn.sorter.element.js +26 -0
  63. package/src/epic/history/zero.__.fn.find.util.js +37 -0
  64. package/src/epic/history/zero.__.v.constant.js +5 -0
  65. package/src/epic/index.js +50 -0
  66. package/src/epic/lain.fn.execute.js +116 -0
  67. package/src/epic/lain.fn.parse.js +94 -0
  68. package/src/epic/momo.fn.cx.js +81 -0
  69. package/src/epic/momo.fn.dir.js +87 -0
  70. package/src/epic/momo.fn.element.js +52 -0
  71. package/src/epic/momo.fn.find.js +37 -0
  72. package/src/epic/momo.fn.fx.js +143 -0
  73. package/src/epic/momo.fn.io.js +157 -0
  74. package/src/epic/momo.fn.is.js +20 -0
  75. package/src/epic/momo.fn.it.js +118 -0
  76. package/src/epic/momo.fn.log.js +50 -0
  77. package/src/epic/momo.fn.out.js +47 -0
  78. package/src/epic/momo.fn.sorter.js +26 -0
  79. package/src/epic/momo.fn.str.js +63 -0
  80. package/src/epic/momo.fn.to.js +74 -0
  81. package/src/epic/momo.v.constant.js +5 -0
  82. package/src/epic/momo.v.errorcode.js +108 -0
  83. package/src/executor/executeActor.js +113 -0
  84. package/src/executor/executeActors.js +58 -0
  85. package/src/executor/executeAdd.js +248 -0
  86. package/src/executor/executeArchive.js +124 -0
  87. package/src/executor/executeEnv.js +158 -0
  88. package/src/executor/executeHelp.js +23 -0
  89. package/src/executor/executeInit.js +321 -0
  90. package/src/executor/executeList.js +111 -0
  91. package/src/executor/executeOpen.js +130 -0
  92. package/src/executor/executePlan.js +134 -0
  93. package/src/executor/executeRepo.js +234 -0
  94. package/src/executor/executeRun.js +321 -0
  95. package/src/executor/executeShow.js +164 -0
  96. package/src/executor/executeTasks.js +260 -0
  97. package/src/executor/executeUnlock.js +110 -0
  98. package/src/executor/executeValidate.js +210 -0
  99. package/src/executor/index.js +35 -0
  100. package/src/momo.js +39 -1
  101. package/.idea/copilot.data.migration.agent.xml +0 -6
  102. package/.idea/copilot.data.migration.ask.xml +0 -6
  103. package/.idea/copilot.data.migration.ask2agent.xml +0 -6
  104. package/.idea/copilot.data.migration.edit.xml +0 -6
  105. package/.idea/modules.xml +0 -8
  106. package/.idea/r2mo-lain.iml +0 -12
  107. package/.idea/vcs.xml +0 -6
@@ -0,0 +1,260 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const util = require('util');
4
+ const { spawn } = require('child_process');
5
+ const Ec = require('../epic');
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
+ */
62
+ const _copyToClipboard = async (content) => {
63
+ try {
64
+ Ec.waiting("📋 正在将内容复制到剪切板...");
65
+ const proc = spawn('pbcopy', { stdio: 'pipe' });
66
+ proc.stdin.write(content);
67
+ proc.stdin.end();
68
+
69
+ // 等待复制操作完成
70
+ await new Promise((resolve, reject) => {
71
+ proc.on('close', (code) => {
72
+ if (code === 0) {
73
+ resolve();
74
+ } else {
75
+ reject(new Error(`pbcopy 进程退出码: ${code}`));
76
+ }
77
+ });
78
+ proc.on('error', reject);
79
+ });
80
+
81
+ Ec.waiting('📄 任务检查提示词已拷贝到剪切板');
82
+ } catch (error) {
83
+ Ec.waiting(`⚠️ 无法拷贝到剪切板: ${error.message}`);
84
+ // 备选方案:显示内容
85
+ Ec.waiting('提示词内容:');
86
+ console.log(content);
87
+ }
88
+ };
89
+
90
+ /**
91
+ * 处理剪切板任务
92
+ * @param {string} taskName 任务名称
93
+ * @param {Array} taskInstances 任务实例列表
94
+ */
95
+ const _handleClipboardTask = async (taskName, taskInstances) => {
96
+ // 读取模板文件并填充参数,然后拷贝到剪切板
97
+ const templatePath = path.resolve(__dirname, '../_template/PROMPT/tasks.md.ejs');
98
+ Ec.waiting(`📄 读取模板文件: ${templatePath}`);
99
+
100
+ if (fs.existsSync(templatePath)) {
101
+ try {
102
+ // 读取并处理提示词模板
103
+ const templateContent = await _readTemplate(templatePath);
104
+ Ec.waiting("🔧 渲染模板参数");
105
+
106
+ const renderedContent = _renderTemplate(templateContent, {
107
+ REQ: taskInstances[0].requirement,
108
+ TASK: taskName
109
+ });
110
+
111
+ Ec.waiting("📋 准备复制到剪切板");
112
+ Ec.waiting(`📋 剪切板内容预览: ${renderedContent.substring(0, 50)}${renderedContent.length > 50 ? '...' : ''}`);
113
+
114
+ // 将提示词复制到剪贴板
115
+ await _copyToClipboard(renderedContent);
116
+ } catch (error) {
117
+ Ec.waiting(`⚠️ 处理模板时出错: ${error.message}`);
118
+ }
119
+ } else {
120
+ Ec.waiting(`⚠️ 未找到模板文件: ${templatePath}`);
121
+ }
122
+ };
123
+
124
+ module.exports = async (options) => {
125
+ // 参数提取
126
+ const parsed = Ec.parseArgument(options);
127
+ const taskName = parsed.task;
128
+
129
+ try {
130
+ // 获取 changes 目录路径
131
+ const changesDir = path.resolve(process.cwd(), 'specification', 'changes');
132
+
133
+ // 检查目录是否存在
134
+ if (!fs.existsSync(changesDir)) {
135
+ Ec.error("❌ 未找到 changes 目录,请先初始化项目");
136
+ Ec.askClose();
137
+ process.exit(1);
138
+ }
139
+
140
+ // 获取所有需求目录
141
+ const requirements = fs.readdirSync(changesDir).filter(file =>
142
+ fs.statSync(path.join(changesDir, file)).isDirectory()
143
+ );
144
+
145
+ if (requirements.length === 0) {
146
+ Ec.waiting("🔍 未找到任何需求");
147
+ Ec.askClose();
148
+ process.exit(0);
149
+ }
150
+
151
+ Ec.waiting(`📁 找到 ${requirements.length} 个需求目录`);
152
+
153
+ // 如果指定了任务名称,只检查该任务的重复信息
154
+ if (taskName) {
155
+ Ec.waiting(`🔍 检查任务 ${taskName} 的重复信息...`);
156
+
157
+ const taskInstances = [];
158
+
159
+ // 查找指定任务
160
+ requirements.forEach(requirement => {
161
+ const tasksDir = path.resolve(changesDir, requirement, 'tasks');
162
+
163
+ // 检查 tasks 目录是否存在
164
+ if (fs.existsSync(tasksDir) && fs.statSync(tasksDir).isDirectory()) {
165
+ const taskFile = `${taskName}.md`;
166
+ const taskPath = path.resolve(tasksDir, taskFile);
167
+
168
+ // 检查任务文件是否存在
169
+ if (fs.existsSync(taskPath)) {
170
+ const relativePath = path.relative(process.cwd(), taskPath);
171
+ taskInstances.push({
172
+ name: taskName,
173
+ path: relativePath,
174
+ requirement: requirement
175
+ });
176
+ }
177
+ }
178
+ });
179
+
180
+ if (taskInstances.length === 0) {
181
+ Ec.waiting(`✅ 未找到任务 ${taskName}`);
182
+ // 即使未找到任务也执行剪切板任务
183
+ await _handleClipboardTask(taskName, [{ requirement: 'unknown' }]);
184
+ Ec.info(`✅ 任务 ${taskName} 的检查完成`);
185
+ Ec.askClose();
186
+ process.exit(0);
187
+ } else if (taskInstances.length === 1) {
188
+ Ec.waiting(`✅ 任务 ${taskName} 无重复`);
189
+ Ec.waiting(` - ${taskInstances[0].path}`);
190
+ // 即使无重复也执行剪切板任务
191
+ await _handleClipboardTask(taskName, taskInstances);
192
+ Ec.info(`✅ 任务 ${taskName} 的检查完成`);
193
+ Ec.askClose();
194
+ process.exit(0);
195
+ } else {
196
+ Ec.waiting(`⚠️ 发现 ${taskInstances.length} 个重复任务 ${taskName}:`);
197
+ taskInstances.forEach((task, index) => {
198
+ Ec.waiting(` ${index + 1}. ${task.path}`);
199
+ });
200
+
201
+ // 执行剪切板任务
202
+ await _handleClipboardTask(taskName, taskInstances);
203
+ Ec.info(`✅ 任务 ${taskName} 的重复检查完成`);
204
+ Ec.askClose();
205
+ process.exit(0);
206
+ }
207
+ }
208
+
209
+ // 未指定任务名称,列出所有任务
210
+ const tasks = [];
211
+
212
+ requirements.forEach(requirement => {
213
+ const tasksDir = path.resolve(changesDir, requirement, 'tasks');
214
+
215
+ // 检查 tasks 目录是否存在
216
+ if (fs.existsSync(tasksDir) && fs.statSync(tasksDir).isDirectory()) {
217
+ // 获取所有 .md 文件
218
+ const taskFiles = fs.readdirSync(tasksDir).filter(file =>
219
+ file.endsWith('.md')
220
+ );
221
+
222
+ taskFiles.forEach(taskFile => {
223
+ const name = path.basename(taskFile, '.md');
224
+ const taskPath = path.relative(process.cwd(), path.resolve(tasksDir, taskFile));
225
+
226
+ tasks.push({
227
+ name: name,
228
+ path: taskPath,
229
+ requirement: requirement
230
+ });
231
+ });
232
+ }
233
+ });
234
+
235
+ if (tasks.length === 0) {
236
+ Ec.waiting("🔍 未找到任何任务");
237
+ // 即使未找到任务也执行剪切板任务(使用默认值)
238
+ await _handleClipboardTask('default', [{ requirement: 'unknown' }]);
239
+ Ec.info("✅ 任务列表显示完成");
240
+ Ec.askClose();
241
+ process.exit(0);
242
+ }
243
+
244
+ // 列出所有任务(包含路径)
245
+ Ec.waiting(`📊 共找到 ${tasks.length} 个任务:`);
246
+ tasks.forEach((task, index) => {
247
+ Ec.waiting(`${index + 1}. ${task.name}, ${task.path}`);
248
+ });
249
+
250
+ // 执行剪切板任务(使用第一个任务)
251
+ await _handleClipboardTask(tasks[0].name, [tasks[0]]);
252
+ Ec.info("✅ 任务列表显示完成");
253
+ Ec.askClose();
254
+ process.exit(0);
255
+ } catch (error) {
256
+ Ec.error(`❌ 执行过程中发生错误: ${error.message}`);
257
+ Ec.askClose();
258
+ process.exit(1);
259
+ }
260
+ };
@@ -0,0 +1,110 @@
1
+ const Ec = require('../epic');
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+
5
+ /**
6
+ * 删除 specification 目录下的所有任务锁文件
7
+ * @param {string} dir specification 目录路径
8
+ * @returns {number} 删除的文件数量
9
+ */
10
+ const _unlockSpecification = (dir) => {
11
+ let deletedCount = 0;
12
+
13
+ if (!fs.existsSync(dir)) {
14
+ return deletedCount;
15
+ }
16
+
17
+ // 递归查找 specification/changes/*/tasks/*.lock 文件
18
+ const changesDir = path.join(dir, 'changes');
19
+ if (fs.existsSync(changesDir) && fs.statSync(changesDir).isDirectory()) {
20
+ const requirements = fs.readdirSync(changesDir).filter(file =>
21
+ fs.statSync(path.join(changesDir, file)).isDirectory()
22
+ );
23
+
24
+ requirements.forEach(requirement => {
25
+ const tasksDir = path.join(changesDir, requirement, 'tasks');
26
+ if (fs.existsSync(tasksDir) && fs.statSync(tasksDir).isDirectory()) {
27
+ const taskFiles = fs.readdirSync(tasksDir).filter(file =>
28
+ file.endsWith('.lock')
29
+ );
30
+
31
+ taskFiles.forEach(taskFile => {
32
+ const taskFilePath = path.join(tasksDir, taskFile);
33
+ try {
34
+ fs.unlinkSync(taskFilePath);
35
+ Ec.waiting(`🔓 已解锁任务: ${path.relative(process.cwd(), taskFilePath)}`);
36
+ deletedCount++;
37
+ } catch (error) {
38
+ Ec.waiting(`⚠️ 无法解锁任务: ${path.relative(process.cwd(), taskFilePath)} - ${error.message}`);
39
+ }
40
+ });
41
+ }
42
+ });
43
+ }
44
+
45
+ return deletedCount;
46
+ };
47
+
48
+ /**
49
+ * 删除 source 目录下的工作空间锁文件
50
+ * @param {string} dir source 目录路径
51
+ * @returns {number} 删除的文件数量
52
+ */
53
+ const _unlockSource = (dir) => {
54
+ let deletedCount = 0;
55
+
56
+ if (!fs.existsSync(dir)) {
57
+ return deletedCount;
58
+ }
59
+
60
+ // 只删除 source/*.lock 文件(工作空间锁文件)
61
+ const lockFiles = fs.readdirSync(dir).filter(file =>
62
+ file.endsWith('.lock')
63
+ );
64
+
65
+ lockFiles.forEach(lockFile => {
66
+ const lockFilePath = path.join(dir, lockFile);
67
+ try {
68
+ fs.unlinkSync(lockFilePath);
69
+ Ec.waiting(`🔓 已解锁工作空间: ${path.relative(process.cwd(), lockFilePath)}`);
70
+ deletedCount++;
71
+ } catch (error) {
72
+ Ec.waiting(`⚠️ 无法解锁工作空间: ${path.relative(process.cwd(), lockFilePath)} - ${error.message}`);
73
+ }
74
+ });
75
+
76
+ return deletedCount;
77
+ };
78
+
79
+ module.exports = (options) => {
80
+ // 参数提取
81
+ const parsed = Ec.parseArgument(options);
82
+
83
+ try {
84
+ Ec.waiting("🔍 正在查找并解锁所有.lock文件...");
85
+
86
+ // 解锁specification目录下的任务锁文件
87
+ const specificationDir = path.resolve(process.cwd(), 'specification');
88
+ const specDeletedCount = _unlockSpecification(specificationDir);
89
+
90
+ // 解锁source目录下的工作空间锁文件
91
+ const sourceDir = path.resolve(process.cwd(), 'source');
92
+ const sourceDeletedCount = _unlockSource(sourceDir);
93
+
94
+ const totalDeletedCount = specDeletedCount + sourceDeletedCount;
95
+
96
+ if (totalDeletedCount === 0) {
97
+ Ec.waiting("✅ 未找到任何.lock文件,无需解锁");
98
+ } else {
99
+ Ec.waiting(`✅ 共解锁 ${totalDeletedCount} 个文件`);
100
+ }
101
+
102
+ Ec.info("🔓 解锁操作完成");
103
+ Ec.askClose();
104
+ process.exit(0);
105
+ } catch (error) {
106
+ Ec.error(`❌ 解锁过程中发生错误: ${error.message}`);
107
+ Ec.askClose();
108
+ process.exit(1);
109
+ }
110
+ };
@@ -0,0 +1,210 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const Ec = require('../epic');
4
+
5
+ /**
6
+ * 获取模板中的标题结构
7
+ * @param {string} templatePath 模板文件路径
8
+ * @returns {Array} 标题结构数组
9
+ */
10
+ const _getTemplateStructure = (templatePath) => {
11
+ if (!fs.existsSync(templatePath)) {
12
+ throw new Error(`模板文件不存在: ${templatePath}`);
13
+ }
14
+
15
+ const content = fs.readFileSync(templatePath, 'utf8');
16
+ const lines = content.split('\n');
17
+ const headings = [];
18
+
19
+ lines.forEach(line => {
20
+ // 直接计算 # 符号数量确定层级
21
+ const headingMatch = line.match(/^(#{1,6})\s+(.+)$/);
22
+ if (headingMatch) {
23
+ const level = headingMatch[1].length;
24
+ const title = headingMatch[2].trim();
25
+ // 移除标题中的占位符内容(如 [xxx])
26
+ const cleanTitle = title.replace(/\[.*?\]/g, '').trim();
27
+ if (cleanTitle) {
28
+ headings.push({level, title: cleanTitle});
29
+ }
30
+ }
31
+ });
32
+
33
+ return headings;
34
+ };
35
+
36
+ /**
37
+ * 获取需求文件中的标题结构
38
+ * @param {string} filePath 需求文件路径
39
+ * @returns {Array} 标题结构数组
40
+ */
41
+ const _getFileStructure = (filePath) => {
42
+ if (!fs.existsSync(filePath)) {
43
+ throw new Error(`需求文件不存在: ${filePath}`);
44
+ }
45
+
46
+ const content = fs.readFileSync(filePath, 'utf8');
47
+ const lines = content.split('\n');
48
+ const headings = [];
49
+
50
+ lines.forEach(line => {
51
+ // 直接计算 # 符号数量确定层级
52
+ const headingMatch = line.match(/^(#{1,6})\s+(.+)$/);
53
+ if (headingMatch) {
54
+ const level = headingMatch[1].length;
55
+ const title = headingMatch[2].trim();
56
+ headings.push({level, title});
57
+ }
58
+ });
59
+
60
+ return headings;
61
+ };
62
+
63
+ /**
64
+ * 构建完整的树型结构
65
+ * @param {Array} headings 标题数组
66
+ * @returns {Array} 树型结构
67
+ */
68
+ const _buildTreeStructure = (headings) => {
69
+ const structure = [];
70
+ const stack = [];
71
+
72
+ headings.forEach(heading => {
73
+ const {level, title} = heading;
74
+
75
+ // 调整栈以匹配当前层级
76
+ while (stack.length >= level) {
77
+ stack.pop();
78
+ }
79
+
80
+ const item = {level, title, children: []};
81
+
82
+ if (stack.length > 0) {
83
+ stack[stack.length - 1].children.push(item);
84
+ } else {
85
+ structure.push(item);
86
+ }
87
+
88
+ stack.push(item);
89
+ });
90
+
91
+ return structure;
92
+ };
93
+
94
+ /**
95
+ * 打印树型结构
96
+ * @param {Array} items 结构项数组
97
+ * @param {number} indent 缩进级别
98
+ */
99
+ const _printTree = (items, indent = 0) => {
100
+ items.forEach(item => {
101
+ const prefix = ' '.repeat(indent);
102
+ Ec.waiting(`${prefix}├─ ${item.title}`);
103
+
104
+ if (item.children && item.children.length > 0) {
105
+ _printTree(item.children, indent + 1);
106
+ }
107
+ });
108
+ };
109
+
110
+ /**
111
+ * 打印带高亮的树型结构
112
+ * @param {Array} items 结构项数组
113
+ * @param {Set} missingTitles 缺失的标题集合
114
+ * @param {number} indent 缩进级别
115
+ */
116
+ const _printTreeWithHighlight = (items, missingTitles, indent = 0) => {
117
+ items.forEach(item => {
118
+ const prefix = ' '.repeat(indent);
119
+ if (missingTitles.has(item.title)) {
120
+ // 缺失的章节用红色高亮并添加emoji标识
121
+ Ec.error(`${prefix}├─ ❌ ${item.title}`);
122
+ } else {
123
+ // 正常章节用白色显示
124
+ Ec.waiting(`${prefix}├─ ${item.title}`);
125
+ }
126
+
127
+ if (item.children && item.children.length > 0) {
128
+ _printTreeWithHighlight(item.children, missingTitles, indent + 1);
129
+ }
130
+ });
131
+ };
132
+
133
+ /**
134
+ * 查找缺失的章节
135
+ * @param {Array} templateStructure 模板结构
136
+ * @param {Array} fileStructure 文件结构
137
+ * @returns {Array} 缺失的章节列表
138
+ */
139
+ const _findMissingSections = (templateStructure, fileStructure) => {
140
+ const templateTitles = templateStructure.map(item => item.title);
141
+ const fileTitles = fileStructure.map(item => item.title);
142
+
143
+ // 找出在模板中存在但在文件中缺失的章节
144
+ return templateStructure.filter(item =>
145
+ !fileTitles.includes(item.title)
146
+ );
147
+ };
148
+
149
+ module.exports = (options) => {
150
+ // 参数提取
151
+ const parsed = Ec.parseArgument(options);
152
+ const requirementName = parsed.name || parsed.n;
153
+
154
+ // 验证参数
155
+ if (!requirementName) {
156
+ Ec.error("❌ 请提供需求名称 (-n, --name)");
157
+ process.exit(1);
158
+ }
159
+
160
+ try {
161
+ // 构建路径
162
+ const projectDir = process.cwd();
163
+ const changesDir = path.join(projectDir, 'specification', 'changes');
164
+ const requirementDir = path.join(changesDir, requirementName);
165
+ const proposalFile = path.join(requirementDir, 'proposal.md');
166
+ const templateFile = path.join(__dirname, '../_template/LAIN/changes/proposal.md');
167
+
168
+ // 检查需求目录是否存在
169
+ if (!fs.existsSync(requirementDir)) {
170
+ Ec.error(`❌ 需求 "${requirementName}" 不存在`);
171
+ process.exit(1);
172
+ }
173
+
174
+ // 检查 proposal.md 文件是否存在
175
+ if (!fs.existsSync(proposalFile)) {
176
+ Ec.error(`❌ 需求 "${requirementName}" 中未找到 proposal.md 文件`);
177
+ process.exit(1);
178
+ }
179
+
180
+ // 获取模板和文件的结构
181
+ const templateStructure = _getTemplateStructure(templateFile);
182
+ const fileStructure = _getFileStructure(proposalFile);
183
+
184
+ // 查找缺失的章节
185
+ const missingSections = _findMissingSections(templateStructure, fileStructure);
186
+
187
+ // 构建模板的树型结构
188
+ const templateTree = _buildTreeStructure(templateStructure);
189
+
190
+ if (missingSections.length === 0) {
191
+ Ec.info(`✅ 需求 "${requirementName}" 格式验证通过`);
192
+ process.exit(0);
193
+ } else {
194
+ Ec.waiting(`需求名称: ${requirementName}`);
195
+ Ec.waiting('文档结构验证结果:');
196
+
197
+ // 创建缺失标题的集合
198
+ const missingTitles = new Set(missingSections.map(section => section.title));
199
+
200
+ // 以模板结构为基准打印树型结构,并高亮缺失的章节
201
+ _printTreeWithHighlight(templateTree, missingTitles);
202
+
203
+ Ec.error(`\n❌ 需求 "${requirementName}" 格式验证失败,缺失 ${missingSections.length} 个章节`);
204
+ process.exit(1);
205
+ }
206
+ } catch (error) {
207
+ Ec.error(`❌ 执行过程中发生错误: ${error.message}`);
208
+ process.exit(1);
209
+ }
210
+ };
@@ -0,0 +1,35 @@
1
+ const executeHelp = require('./executeHelp');
2
+ const executeInit = require('./executeInit');
3
+ const executeRepo = require('./executeRepo');
4
+ const executeAdd = require('./executeAdd');
5
+ const executePlan = require('./executePlan');
6
+ const executeArchive = require('./executeArchive');
7
+ const executeEnv = require('./executeEnv');
8
+ const executeOpen = require('./executeOpen');
9
+ const executeShow = require('./executeShow');
10
+ const executeList = require('./executeList');
11
+ const executeValidate = require('./executeValidate');
12
+ const executeActor = require('./executeActor');
13
+ const executeActors = require('./executeActors');
14
+ const executeRun = require('./executeRun');
15
+ const executeTasks = require('./executeTasks');
16
+ const executeUnlock = require('./executeUnlock');
17
+ const exported = {
18
+ executeHelp,
19
+ executeInit,
20
+ executeRepo,
21
+ executeAdd,
22
+ executePlan,
23
+ executeArchive,
24
+ executeEnv,
25
+ executeOpen,
26
+ executeShow,
27
+ executeList,
28
+ executeValidate,
29
+ executeActor,
30
+ executeActors,
31
+ executeRun,
32
+ executeTasks,
33
+ executeUnlock
34
+ };
35
+ module.exports = exported;
package/src/momo.js CHANGED
@@ -1 +1,39 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
+ const Ec = require('./epic');
3
+ const Executor = require('./executor');
4
+ const path = require('path');
5
+ const fs = require('fs');
6
+
7
+ // 检查是否是 version 命令模式
8
+ const _isVersionMode = () => {
9
+ const args = process.argv.slice(2);
10
+ return args.includes('version') || args.includes('-v') || args.includes('--version');
11
+ };
12
+
13
+ // 获取项目版本号
14
+ const _getVersion = () => {
15
+ try {
16
+ const packagePath = path.resolve(__dirname, '../package.json');
17
+ const packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
18
+ return packageJson.version || '未知版本';
19
+ } catch (error) {
20
+ return '未知版本';
21
+ }
22
+ };
23
+
24
+ // 如果是 version 命令,则只输出版本号并退出
25
+ if (_isVersionMode()) {
26
+ console.log(_getVersion());
27
+ process.exit(0);
28
+ }
29
+
30
+ // 输出头部
31
+ Ec.executeHeader("Rachel Momo / SDD");
32
+
33
+ // 读取配置文件
34
+ const configArr = Ec.parseMetadata();
35
+ Ec.executeBody(configArr, Executor);
36
+
37
+
38
+ // 输出尾部
39
+ Ec.executeEnd();
@@ -1,6 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <project version="4">
3
- <component name="AgentMigrationStateService">
4
- <option name="migrationStatus" value="COMPLETED" />
5
- </component>
6
- </project>
@@ -1,6 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <project version="4">
3
- <component name="AskMigrationStateService">
4
- <option name="migrationStatus" value="COMPLETED" />
5
- </component>
6
- </project>
@@ -1,6 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <project version="4">
3
- <component name="Ask2AgentMigrationStateService">
4
- <option name="migrationStatus" value="COMPLETED" />
5
- </component>
6
- </project>
@@ -1,6 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <project version="4">
3
- <component name="EditMigrationStateService">
4
- <option name="migrationStatus" value="COMPLETED" />
5
- </component>
6
- </project>
package/.idea/modules.xml DELETED
@@ -1,8 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <project version="4">
3
- <component name="ProjectModuleManager">
4
- <modules>
5
- <module fileurl="file://$PROJECT_DIR$/.idea/r2mo-lain.iml" filepath="$PROJECT_DIR$/.idea/r2mo-lain.iml" />
6
- </modules>
7
- </component>
8
- </project>