openmatrix 0.2.1 → 0.2.3

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.
@@ -151,26 +151,40 @@ exports.autoCommand = new commander_1.Command('auto')
151
151
  console.log(`\n📋 任务: ${parsedTask.title}`);
152
152
  console.log(` 目标: ${parsedTask.goals.join(', ')}`);
153
153
  }
154
+ // 获取质量配置(在 breakdown 之前,因为需要传入)
155
+ const qualityConfig = index_js_1.QUALITY_PRESETS[qualityLevel];
154
156
  // 拆解任务
155
157
  if (!options.json) {
156
158
  console.log('\n🔧 拆解任务...');
157
159
  }
158
160
  const planner = new task_planner_js_1.TaskPlanner();
159
- const subTasks = planner.breakdown(parsedTask, {});
160
- // 创建任务到状态管理器
161
+ const subTasks = planner.breakdown(parsedTask, {}, qualityConfig);
162
+ // 创建任务到状态管理器,并建立 ID 映射
163
+ // TaskPlanner 生成的 taskId 和 StateManager 创建的 id 不同,
164
+ // 需要映射后才能正确设置 dependencies
165
+ const taskIdMap = new Map();
161
166
  for (const subTask of subTasks) {
162
- await stateManager.createTask({
167
+ const created = await stateManager.createTask({
163
168
  title: subTask.title,
164
169
  description: subTask.description,
165
170
  priority: subTask.priority,
166
171
  timeout: subTask.estimatedComplexity === 'high' ? 300000 :
167
172
  subTask.estimatedComplexity === 'medium' ? 180000 : 120000,
168
- dependencies: subTask.dependencies,
173
+ dependencies: [],
169
174
  assignedAgent: subTask.assignedAgent
170
175
  });
176
+ taskIdMap.set(subTask.taskId, created.id);
177
+ }
178
+ // 映射并更新依赖关系
179
+ for (const subTask of subTasks) {
180
+ const actualId = taskIdMap.get(subTask.taskId);
181
+ const resolvedDeps = subTask.dependencies
182
+ .map(dep => taskIdMap.get(dep))
183
+ .filter((id) => id !== undefined);
184
+ if (resolvedDeps.length > 0) {
185
+ await stateManager.updateTask(actualId, { dependencies: resolvedDeps });
186
+ }
171
187
  }
172
- // 获取质量配置
173
- const qualityConfig = index_js_1.QUALITY_PRESETS[qualityLevel];
174
188
  // auto 模式: 空审批点数组 = bypass permissions
175
189
  const approvalPoints = [];
176
190
  // 更新状态
@@ -58,7 +58,7 @@ exports.completeCommand = new commander_1.Command('complete')
58
58
  const task = await stateManager.getTask(taskId);
59
59
  if (!task) {
60
60
  console.log(JSON.stringify({ error: `任务 ${taskId} 不存在` }));
61
- process.exit(1);
61
+ return;
62
62
  }
63
63
  const isSuccess = !options.failed;
64
64
  const now = new Date().toISOString();
@@ -282,6 +282,5 @@ exports.meetingCommand = new commander_1.Command('meeting')
282
282
  }
283
283
  catch (error) {
284
284
  console.error('❌ 处理失败:', error instanceof Error ? error.message : error);
285
- process.exit(1);
286
285
  }
287
286
  });
@@ -59,6 +59,7 @@ exports.startCommand = new commander_1.Command('start')
59
59
  .option('--docs <level>', '文档级别 (full|basic|minimal|none)')
60
60
  .option('--tasks-json <json>', 'AI 已拆分的任务 JSON (跳过自动解析)')
61
61
  .option('--e2e-tests', '启用 E2E 测试')
62
+ .option('--e2e-type <type>', 'E2E 测试类型 (web|visual)')
62
63
  .option('--research-context <path>', '研究上下文 JSON 路径 (来自 /om:research 的 context.json)')
63
64
  .action(async (input, options) => {
64
65
  const basePath = process.cwd();
@@ -266,15 +267,21 @@ async function handleTasksJson(options, stateManager, state, omPath, basePath) {
266
267
  if (tasksInput.e2eTests || options.e2eTests) {
267
268
  qualityConfig.e2eTests = true;
268
269
  }
270
+ // E2E 类型(通过 answers 传递给 TaskPlanner)
271
+ const extraAnswers = { ...(resolvedInput.answers || {}) };
272
+ // tasks-input.json 中的 e2eType 或 CLI 参数
273
+ const e2eTypeValue = tasksInput.e2eType || options.e2eType;
274
+ if (e2eTypeValue) {
275
+ extraAnswers.e2eType = e2eTypeValue;
276
+ }
269
277
  if (!options.json) {
270
278
  console.log(`\n📋 任务: ${parsedTask.title}`);
271
279
  console.log(` 目标: ${parsedTask.goals.join(', ')}`);
272
280
  console.log('\n🔧 拆解任务...');
273
281
  }
274
282
  // 使用 TaskPlanner 拆分(保持原有拆分逻辑)
275
- const answers = resolvedInput.answers || {};
276
283
  const planner = new task_planner_js_1.TaskPlanner();
277
- const subTasks = planner.breakdown(parsedTask, answers, qualityConfig, resolvedInput.plan);
284
+ const subTasks = planner.breakdown(parsedTask, extraAnswers, qualityConfig, resolvedInput.plan);
278
285
  // 创建任务到状态管理器,并建立 ID 映射
279
286
  // TaskPlanner 生成的 taskId 和 StateManager 创建的 id 不同,
280
287
  // 需要映射后才能正确设置 dependencies
@@ -123,7 +123,6 @@ async function showStatus(manager, reporter, options) {
123
123
  }
124
124
  catch (error) {
125
125
  console.error(chalk_1.default.red('Error:'), error);
126
- process.exit(1);
127
126
  }
128
127
  }
129
128
  /**
@@ -54,7 +54,7 @@ export declare class AIReviewer {
54
54
  /**
55
55
  * 解析 AI Review 结果
56
56
  */
57
- parseReviewResult(output: string): ReviewReport;
57
+ parseReviewResult(taskId: string, output: string): ReviewReport;
58
58
  /**
59
59
  * 生成验收阶段提示词 (包含 AI Review)
60
60
  */
@@ -171,7 +171,7 @@ AI_REVIEW_REJECTED
171
171
  /**
172
172
  * 解析 AI Review 结果
173
173
  */
174
- parseReviewResult(output) {
174
+ parseReviewResult(taskId, output) {
175
175
  const lines = output.split('\n');
176
176
  const issues = [];
177
177
  const categories = [];
@@ -249,7 +249,7 @@ AI_REVIEW_REJECTED
249
249
  const summaryMatch = output.match(/## 总结\s*([\s\S]*?)(?=```|$)/);
250
250
  const summary = summaryMatch ? summaryMatch[1].trim() : '';
251
251
  return {
252
- taskId: '',
252
+ taskId,
253
253
  overallStatus,
254
254
  categories,
255
255
  issues,
@@ -21,4 +21,5 @@ export declare function extractTasksInputFields(answers: Record<string, string |
21
21
  quality?: string;
22
22
  mode?: string;
23
23
  e2eTests?: boolean;
24
+ e2eType?: string;
24
25
  };
@@ -98,11 +98,14 @@ function extractTasksInputFields(answers) {
98
98
  if (typeof mode === 'string') {
99
99
  result.mode = mode;
100
100
  }
101
- // e2e_tests → e2eTests 布尔字段
101
+ // e2e_tests → e2eTests 布尔字段 + e2eType
102
102
  const e2e = answers['e2e_tests'] || answers['e2eTests'] || answers['E2E测试'];
103
103
  if (e2e !== undefined) {
104
104
  const e2eStr = Array.isArray(e2e) ? e2e[0] : e2e;
105
- result.e2eTests = e2eStr === 'true' || e2eStr === '启用 E2E 测试' || e2eStr === '是';
105
+ result.e2eTests = e2eStr === 'functional' || e2eStr === 'visual' || e2eStr === 'true' || e2eStr === '启用 E2E 测试' || e2eStr === '是';
106
+ if (result.e2eTests && e2eStr === 'visual') {
107
+ result.e2eType = 'visual';
108
+ }
106
109
  }
107
110
  return result;
108
111
  }
@@ -318,7 +318,7 @@ class OrchestratorExecutor {
318
318
  * 处理 Review 结果:解析报告,如有 critical/major 问题则自动创建修复任务
319
319
  */
320
320
  async processReviewResult(taskId, output) {
321
- const report = this.aiReviewer.parseReviewResult(output);
321
+ const report = this.aiReviewer.parseReviewResult(taskId, output);
322
322
  const task = await this.stateManager.getTask(taskId);
323
323
  if (!task)
324
324
  return {};
@@ -416,10 +416,16 @@ class OrchestratorExecutor {
416
416
  */
417
417
  async processRetries(failedTasks) {
418
418
  let retried = 0;
419
- const maxRetries = this.stateManager.getState().then(s => s.config.maxRetries).catch(() => 3);
419
+ let limit;
420
+ try {
421
+ const state = await this.stateManager.getState();
422
+ limit = state.config.maxRetries;
423
+ }
424
+ catch {
425
+ limit = 3;
426
+ }
420
427
  for (const task of failedTasks) {
421
428
  const currentRetryCount = task.retryCount || 0;
422
- const limit = await maxRetries;
423
429
  // 先检查是否还有重试次数,再决定是否加入队列
424
430
  if (currentRetryCount >= limit) {
425
431
  console.log(`⚠️ 任务 ${task.id} 已达到最大重试次数 (${limit}),跳过重试`);
@@ -313,14 +313,15 @@ class InteractiveQuestionGenerator {
313
313
  // 7. E2E 测试
314
314
  questions.push({
315
315
  id: 'e2e_tests',
316
- question: '是否启用端到端 (E2E) 测试?(适用于 Web/Mobile/GUI 项目,耗时较长)',
316
+ question: '是否需要端到端 (E2E) 测试?(适用于 Web/Mobile/GUI 项目,耗时较长)',
317
317
  type: 'single',
318
318
  required: false,
319
319
  category: 'quality',
320
320
  priority: 7,
321
321
  options: [
322
- { key: 'true', label: '启用 E2E 测试', description: '使用 Playwright/Cypress 等框架进行端到端测试' },
323
- { key: 'false', label: '不启用 (推荐)', description: '仅进行单元测试和集成测试,节省时间' }
322
+ { key: 'functional', label: '功能测试 (推荐)', description: '验证业务流程正确性,无需浏览器可视化,速度快' },
323
+ { key: 'visual', label: '视觉验证', description: '需要浏览器可视化验证,可检查页面样式和布局' },
324
+ { key: 'false', label: '不需要', description: '仅进行单元测试和集成测试,节省时间' }
324
325
  ]
325
326
  });
326
327
  // 8. 风险评估
@@ -1097,7 +1097,8 @@ ACCEPT_FAILED
1097
1097
  // 判断是否通过
1098
1098
  const qc = this.qualityConfig;
1099
1099
  const testsPassed = result.tests.failed === 0;
1100
- const coverageOk = result.tests.coverage >= qc.minCoverage;
1100
+ // 覆盖率 -1 表示未测量(regex 回退路径下未匹配到覆盖率时保持 -1)
1101
+ const coverageOk = result.tests.coverage < 0 || result.tests.coverage >= qc.minCoverage;
1101
1102
  const lintOk = qc.strictLint ? result.lint.errors === 0 : true;
1102
1103
  const buildOk = result.build.success;
1103
1104
  const e2eOk = qc.e2eTests ? result.e2e.failed === 0 : true;
@@ -27,8 +27,8 @@ const TRANSITIONS = [
27
27
  { from: 'accept', to: 'failed', event: 'fail' },
28
28
  // failed → retry_queue
29
29
  { from: 'failed', to: 'retry_queue', event: 'retry' },
30
- // retry_queue → pending (需重试次数未超限)
31
- { from: 'retry_queue', to: 'pending', event: 'retry', condition: (task) => (task.retryCount || 0) < 10 },
30
+ // retry_queue → pending (安全上限 100,由 executor 层控制具体 maxRetries)
31
+ { from: 'retry_queue', to: 'pending', event: 'retry', condition: (task) => (task.retryCount || 0) < 100 },
32
32
  // any → pending (cancel and restart)
33
33
  { from: 'scheduled', to: 'pending', event: 'cancel' },
34
34
  { from: 'blocked', to: 'pending', event: 'cancel' },
@@ -47,8 +47,8 @@ export interface UserAnswers {
47
47
  additionalContext?: Record<string, string | string[]>;
48
48
  /** 是否启用 E2E 测试 */
49
49
  e2eTests?: boolean;
50
- /** E2E 测试类型 (web/mobile/gui) */
51
- e2eType?: 'web' | 'mobile' | 'gui';
50
+ /** E2E 测试类型 (web/mobile/gui/visual) */
51
+ e2eType?: 'web' | 'mobile' | 'gui' | 'visual';
52
52
  /** 质量级别 */
53
53
  qualityLevel?: 'fast' | 'balanced' | 'strict';
54
54
  }
@@ -858,13 +858,15 @@ ${userContext.documentationLevel}
858
858
  const str = (v) => Array.isArray(v) ? v.join(', ') : v;
859
859
  const e2eAnswer = str(merged['E2E测试'] || merged['e2eTests'] || merged['e2e']);
860
860
  const e2eTypeAnswer = str(merged['E2E类型'] || merged['e2eType']);
861
+ const isE2EEnabled = e2eAnswer === 'true' || e2eAnswer === 'functional' || e2eAnswer === 'visual' || e2eAnswer === '✅ 启用 E2E 测试' || e2eAnswer === '是';
862
+ let e2eTypeValue = e2eAnswer === 'visual' ? 'visual' : e2eTypeAnswer === 'mobile' ? 'mobile' : e2eTypeAnswer === 'gui' ? 'gui' : 'web';
861
863
  return {
862
864
  objective: str(merged['目标'] || merged['objective']),
863
865
  techStack: this.parseArrayAnswer(str(merged['技术栈'] || merged['techStack']) || ''),
864
866
  testCoverage: str(merged['测试'] || merged['testCoverage']),
865
867
  documentationLevel: str(merged['文档'] || merged['documentationLevel']),
866
- e2eTests: e2eAnswer === 'true' || e2eAnswer === '✅ 启用 E2E 测试' || e2eAnswer === '是',
867
- e2eType: e2eTypeAnswer || 'web',
868
+ e2eTests: isE2EEnabled,
869
+ e2eType: e2eTypeValue,
868
870
  additionalContext: merged
869
871
  };
870
872
  }
@@ -940,8 +942,17 @@ ${devTaskId}
940
942
  }
941
943
  buildE2ETestDescription(e2eType, parsedTask, userContext) {
942
944
  const typeConfig = this.getE2ETypeConfig(e2eType);
945
+ const visualSection = e2eType === 'visual' ? `
946
+ ## 视觉验证要求
947
+ 1. **可视化运行**: 使用有头模式 (headed mode) 运行浏览器,可查看页面渲染
948
+ 2. **样式检查**: 验证页面布局、颜色、字体、间距等视觉元素
949
+ 3. **响应式测试**: 在不同视口下验证页面布局适配
950
+ 4. **交互验证**: 确保动画、过渡效果、hover 状态等交互正常
951
+ 5. **截图对比**: 关键页面应生成截图,便于人工审核
952
+ ` : '';
943
953
  return `## E2E 测试目标
944
954
  执行完整的端到端测试,验证关键用户流程
955
+ ${e2eType === 'visual' ? '(需可视化验证,检查页面样式和布局)' : ''}
945
956
 
946
957
  ## 应用类型
947
958
  ${typeConfig.description}
@@ -965,12 +976,12 @@ ${typeConfig.environments.map(e => `- ${e}`).join('\n')}
965
976
  3. **等待策略**: 使用合理的等待机制,避免硬编码延迟
966
977
  4. **数据隔离**: 测试数据独立,不影响其他测试
967
978
  5. **清理机制**: 测试后清理创建的数据
968
-
979
+ ${visualSection}
969
980
  ## 输出要求
970
981
  - E2E 测试文件 (tests/e2e/*.spec.ts)
971
982
  - 测试执行报告
972
983
  - 截图/录像 (失败时)
973
- - 测试覆盖率报告 (如有)
984
+ ${e2eType === 'visual' ? '- 关键页面截图 (用于视觉审核)\n' : ''}- 测试覆盖率报告 (如有)
974
985
 
975
986
  ## 运行命令
976
987
  \`\`\`bash
@@ -1003,6 +1014,12 @@ ${typeConfig.runCommand}
1003
1014
  frameworks: ['Playwright for Electron', 'Spectron (Electron)', 'Robot Framework', 'PyAutoGUI'],
1004
1015
  environments: ['Windows', 'macOS', 'Linux'],
1005
1016
  runCommand: 'npm run test:e2e'
1017
+ },
1018
+ visual: {
1019
+ description: 'Web 应用 (需可视化验证,检查页面样式和布局)',
1020
+ frameworks: ['Playwright (headed mode,推荐)', 'Cypress (headed)', 'Playwright + Percy (视觉回归)', 'Playwright + Chromatic'],
1021
+ environments: ['Chrome (headed)', '不同视口 (desktop/tablet/mobile)', '不同分辨率设备'],
1022
+ runCommand: 'npx playwright test --headed --reporter=html,list'
1006
1023
  }
1007
1024
  };
1008
1025
  return configs[type];
@@ -110,19 +110,19 @@ class StateManager {
110
110
  this.stateCache = initialState;
111
111
  }
112
112
  else {
113
- // 合并旧状态的统计字段(兼容旧版本)
113
+ // 合并旧状态的统计字段(兼容旧版本,statistics 可能不存在)
114
114
  existing.statistics = {
115
- totalTasks: existing.statistics.totalTasks ?? 0,
116
- completed: existing.statistics.completed ?? 0,
117
- inProgress: existing.statistics.inProgress ?? 0,
118
- failed: existing.statistics.failed ?? 0,
119
- pending: existing.statistics.pending ?? 0,
120
- scheduled: existing.statistics.scheduled ?? 0,
121
- blocked: existing.statistics.blocked ?? 0,
122
- waiting: existing.statistics.waiting ?? 0,
123
- verify: existing.statistics.verify ?? 0,
124
- accept: existing.statistics.accept ?? 0,
125
- retry_queue: existing.statistics.retry_queue ?? 0
115
+ totalTasks: existing.statistics?.totalTasks ?? 0,
116
+ completed: existing.statistics?.completed ?? 0,
117
+ inProgress: existing.statistics?.inProgress ?? 0,
118
+ failed: existing.statistics?.failed ?? 0,
119
+ pending: existing.statistics?.pending ?? 0,
120
+ scheduled: existing.statistics?.scheduled ?? 0,
121
+ blocked: existing.statistics?.blocked ?? 0,
122
+ waiting: existing.statistics?.waiting ?? 0,
123
+ verify: existing.statistics?.verify ?? 0,
124
+ accept: existing.statistics?.accept ?? 0,
125
+ retry_queue: existing.statistics?.retry_queue ?? 0
126
126
  };
127
127
  this.stateCache = existing;
128
128
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openmatrix",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "description": "AI Agent task orchestration system with Claude Code Skills integration",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
package/skills/start.md CHANGED
@@ -165,12 +165,16 @@ AskUserQuestion: `header: "质量等级"`, `multiSelect: false`
165
165
 
166
166
  AskUserQuestion: `header: "E2E 测试"`, `multiSelect: false`
167
167
 
168
- **question:** 是否启用端到端 (E2E) 测试?(适用于 Web/Mobile/GUI 项目,耗时较长)
168
+ **question:** 是否需要端到端 (E2E) 测试?(适用于 Web/Mobile/GUI 项目,耗时较长)
169
169
 
170
170
  | label | description |
171
171
  |-------|-------------|
172
- | `不启用 (推荐)` | 仅进行单元测试和集成测试,节省时间 |
173
- | `启用 E2E 测试` | 使用 Playwright/Cypress 等框架进行端到端测试 |
172
+ | `功能测试 (推荐)` | 验证业务流程正确性,无需浏览器可视化,速度快 |
173
+ | `视觉验证` | 需要浏览器可视化验证,可检查页面样式和布局 |
174
+ | `不需要` | 仅进行单元测试和集成测试,节省时间 |
175
+
176
+ > 功能测试是默认推荐,适用于大多数场景(API/逻辑/数据处理)。
177
+ > 视觉验证适用于前端/移动端项目,需要检查 UI 样式、布局、交互效果。
174
178
 
175
179
  #### 4.3 执行模式(所有任务必选)
176
180
 
@@ -202,7 +206,7 @@ AskUserQuestion: `header: "执行模式"`, `multiSelect: false`
202
206
  📊 统计
203
207
  Goals: N 个(将生成 N个开发 + N个测试 + 审查)
204
208
  质量级别: xxx
205
- E2E 测试: 启用/不启用
209
+ E2E 测试: 功能测试 / 视觉验证 / 不启用
206
210
  ```
207
211
 
208
212
  ### Step 6: AI 提取 goals + 生成 plan
@@ -242,11 +246,13 @@ AskUserQuestion: `header: "执行模式"`, `multiSelect: false`
242
246
  "goalTypes": ["development", "testing", "documentation"],
243
247
  "constraints": ["约束1"],
244
248
  "deliverables": ["src/xxx.ts"],
245
- "plan": "## 技术方案\n1. ...\n2. ..."
249
+ "plan": "## 技术方案\n1. ...\n2. ...",
250
+ "e2eTests": true,
251
+ "e2eType": "visual"
246
252
  }
247
253
  ```
248
254
 
249
- > **注意**: `quality`、`mode`、`e2eTests` 不写入文件,由 Step 8 CLI 参数传递。
255
+ > **注意**: `quality`、`mode` 通过 CLI 参数传递。`e2eTests` `e2eType` 可写入 tasks-input.json 或通过 CLI 参数传递。
250
256
  > **goalTypes** 必须与 goals 数组长度一致,一一对应。
251
257
  > **研究上下文集成**: 如果检测到 `.openmatrix/research/context.json`,将研究的 goals/constraints/deliverables 作为基础,与 AI 提取的内容合并(去重后)。
252
258
 
@@ -264,11 +270,16 @@ openmatrix start --tasks-json @.openmatrix/tasks-input.json --quality <质量等
264
270
  openmatrix start --tasks-json @.openmatrix/tasks-input.json --research-context @.openmatrix/research/context.json --quality <质量等级> --mode <执行模式> --json
265
271
  ```
266
272
 
267
- 如果启用了 E2E 测试,加上 `--e2e-tests`:
273
+ 如果启用了 E2E 测试(功能测试),加上 `--e2e-tests`:
268
274
  ```bash
269
275
  openmatrix start --tasks-json @.openmatrix/tasks-input.json --quality balanced --mode auto --e2e-tests --json
270
276
  ```
271
277
 
278
+ 如果选择了视觉验证,加上 `--e2e-tests --e2e-type visual`:
279
+ ```bash
280
+ openmatrix start --tasks-json @.openmatrix/tasks-input.json --quality balanced --mode auto --e2e-tests --e2e-type visual --json
281
+ ```
282
+
272
283
  **非开发任务**(无质量等级):
273
284
  ```bash
274
285
  openmatrix start --tasks-json @.openmatrix/tasks-input.json --mode <执行模式> --json