openmatrix 0.1.99 → 0.2.2
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/dist/agents/impl/coder-agent.js +42 -42
- package/dist/agents/impl/executor-agent.js +75 -75
- package/dist/agents/impl/planner-agent.js +63 -63
- package/dist/agents/impl/reviewer-agent.js +66 -66
- package/dist/agents/impl/tester-agent.js +56 -56
- package/dist/cli/commands/auto.js +20 -6
- package/dist/cli/commands/complete.js +1 -1
- package/dist/cli/commands/meeting.js +0 -1
- package/dist/cli/commands/report.js +45 -45
- package/dist/cli/commands/start.js +168 -34
- package/dist/cli/commands/status.js +0 -1
- package/dist/cli/commands/step.js +62 -35
- package/dist/orchestrator/ai-reviewer.d.ts +30 -2
- package/dist/orchestrator/ai-reviewer.js +314 -209
- package/dist/orchestrator/answer-mapper.d.ts +1 -0
- package/dist/orchestrator/answer-mapper.js +5 -2
- package/dist/orchestrator/approval-manager.js +14 -13
- package/dist/orchestrator/executor.d.ts +10 -1
- package/dist/orchestrator/executor.js +62 -2
- package/dist/orchestrator/interactive-question-generator.js +4 -3
- package/dist/orchestrator/meeting-manager.js +32 -31
- package/dist/orchestrator/phase-executor.js +9 -6
- package/dist/orchestrator/scheduler.d.ts +8 -6
- package/dist/orchestrator/scheduler.js +53 -22
- package/dist/orchestrator/state-machine.js +2 -2
- package/dist/orchestrator/task-planner.d.ts +83 -4
- package/dist/orchestrator/task-planner.js +702 -124
- package/dist/storage/state-manager.d.ts +6 -0
- package/dist/storage/state-manager.js +28 -0
- package/package.json +55 -55
- package/scripts/build-check.js +19 -19
- package/scripts/install-skills.js +57 -57
- package/skills/approve.md +250 -250
- package/skills/auto.md +298 -298
- package/skills/meeting.md +324 -324
- package/skills/om.md +112 -112
- package/skills/openmatrix.md +112 -112
- package/skills/start.md +38 -6
- package/dist/cli/commands/upgrade.d.ts +0 -2
- package/dist/cli/commands/upgrade.js +0 -329
- package/dist/orchestrator/task-planner.old.d.ts +0 -87
- package/dist/orchestrator/task-planner.old.js +0 -444
|
@@ -7,6 +7,7 @@ const agent_runner_js_1 = require("../agents/agent-runner.js");
|
|
|
7
7
|
const state_machine_js_1 = require("./state-machine.js");
|
|
8
8
|
const phase_executor_js_1 = require("./phase-executor.js");
|
|
9
9
|
const retry_manager_js_1 = require("./retry-manager.js");
|
|
10
|
+
const ai_reviewer_js_1 = require("./ai-reviewer.js");
|
|
10
11
|
/**
|
|
11
12
|
* OrchestratorExecutor - 执行循环核心
|
|
12
13
|
*
|
|
@@ -20,6 +21,7 @@ class OrchestratorExecutor {
|
|
|
20
21
|
stateMachine;
|
|
21
22
|
phaseExecutor;
|
|
22
23
|
retryManager;
|
|
24
|
+
aiReviewer;
|
|
23
25
|
config;
|
|
24
26
|
taskTimers = new Map();
|
|
25
27
|
constructor(stateManager, approvalManager, config) {
|
|
@@ -41,6 +43,7 @@ class OrchestratorExecutor {
|
|
|
41
43
|
this.stateMachine = new state_machine_js_1.StateMachine();
|
|
42
44
|
this.phaseExecutor = new phase_executor_js_1.PhaseExecutor(stateManager, approvalManager);
|
|
43
45
|
this.retryManager = new retry_manager_js_1.RetryManager();
|
|
46
|
+
this.aiReviewer = new ai_reviewer_js_1.AIReviewer();
|
|
44
47
|
}
|
|
45
48
|
/**
|
|
46
49
|
* 获取 PhaseExecutor 实例
|
|
@@ -101,7 +104,7 @@ class OrchestratorExecutor {
|
|
|
101
104
|
}
|
|
102
105
|
// 10. 准备 Subagent 任务
|
|
103
106
|
const subagentTasks = await this.agentRunner.prepareSubagentTasks(executableTasks);
|
|
104
|
-
//
|
|
107
|
+
// 11. 更新任务状态为 in_progress 并设置超时
|
|
105
108
|
for (const task of executableTasks) {
|
|
106
109
|
await this.scheduler.markTaskStarted(task.id);
|
|
107
110
|
this.setupTaskTimeout(task.id);
|
|
@@ -251,6 +254,8 @@ class OrchestratorExecutor {
|
|
|
251
254
|
}
|
|
252
255
|
/**
|
|
253
256
|
* 标记任务完成
|
|
257
|
+
*
|
|
258
|
+
* 增强:对于 reviewer 任务,自动解析 Review 报告并生成修复任务
|
|
254
259
|
*/
|
|
255
260
|
async completeTask(taskId, result) {
|
|
256
261
|
const task = await this.stateManager.getTask(taskId);
|
|
@@ -297,12 +302,53 @@ class OrchestratorExecutor {
|
|
|
297
302
|
}
|
|
298
303
|
else if (currentPhase === 'accept') {
|
|
299
304
|
await this.scheduler.markTaskCompleted(taskId);
|
|
305
|
+
// Review 任务完成:自动解析报告并生成修复任务
|
|
306
|
+
if (task.assignedAgent === 'reviewer' && result.output) {
|
|
307
|
+
return await this.processReviewResult(taskId, result.output);
|
|
308
|
+
}
|
|
300
309
|
}
|
|
301
310
|
}
|
|
302
311
|
else {
|
|
303
312
|
this.clearTaskTimeout(taskId);
|
|
304
313
|
await this.scheduler.markTaskFailed(taskId, result.error || 'Unknown error');
|
|
305
314
|
}
|
|
315
|
+
return {};
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* 处理 Review 结果:解析报告,如有 critical/major 问题则自动创建修复任务
|
|
319
|
+
*/
|
|
320
|
+
async processReviewResult(taskId, output) {
|
|
321
|
+
const report = this.aiReviewer.parseReviewResult(taskId, output);
|
|
322
|
+
const task = await this.stateManager.getTask(taskId);
|
|
323
|
+
if (!task)
|
|
324
|
+
return {};
|
|
325
|
+
// 保存 Review 报告到 artifacts
|
|
326
|
+
await this.stateManager.savePhaseResult(taskId, 'review', {
|
|
327
|
+
report,
|
|
328
|
+
taskId,
|
|
329
|
+
reviewedAt: report.reviewedAt
|
|
330
|
+
});
|
|
331
|
+
if (!this.aiReviewer.needsAutoFix(report)) {
|
|
332
|
+
console.log(`✅ Review 通过: ${taskId} — 无 critical/major 问题`);
|
|
333
|
+
return {};
|
|
334
|
+
}
|
|
335
|
+
// 生成修复任务
|
|
336
|
+
const fixTasks = this.aiReviewer.generateFixTasks(task, report, taskId);
|
|
337
|
+
const createdIds = [];
|
|
338
|
+
for (const fixTask of fixTasks) {
|
|
339
|
+
// 将生成的 taskId 转换为 StateManager 的 ID 格式
|
|
340
|
+
const created = await this.stateManager.createTask({
|
|
341
|
+
title: fixTask.title,
|
|
342
|
+
description: fixTask.description,
|
|
343
|
+
priority: fixTask.priority,
|
|
344
|
+
timeout: fixTask.timeout,
|
|
345
|
+
dependencies: fixTask.dependencies,
|
|
346
|
+
assignedAgent: fixTask.assignedAgent
|
|
347
|
+
});
|
|
348
|
+
createdIds.push(created.id);
|
|
349
|
+
}
|
|
350
|
+
console.log(`🔧 Review 发现 ${report.issues.length} 个问题,已创建 ${createdIds.length} 个修复任务`);
|
|
351
|
+
return { createdFixTasks: createdIds };
|
|
306
352
|
}
|
|
307
353
|
/**
|
|
308
354
|
* 获取当前阶段
|
|
@@ -370,13 +416,27 @@ class OrchestratorExecutor {
|
|
|
370
416
|
*/
|
|
371
417
|
async processRetries(failedTasks) {
|
|
372
418
|
let retried = 0;
|
|
419
|
+
let limit;
|
|
420
|
+
try {
|
|
421
|
+
const state = await this.stateManager.getState();
|
|
422
|
+
limit = state.config.maxRetries;
|
|
423
|
+
}
|
|
424
|
+
catch {
|
|
425
|
+
limit = 3;
|
|
426
|
+
}
|
|
373
427
|
for (const task of failedTasks) {
|
|
428
|
+
const currentRetryCount = task.retryCount || 0;
|
|
429
|
+
// 先检查是否还有重试次数,再决定是否加入队列
|
|
430
|
+
if (currentRetryCount >= limit) {
|
|
431
|
+
console.log(`⚠️ 任务 ${task.id} 已达到最大重试次数 (${limit}),跳过重试`);
|
|
432
|
+
continue;
|
|
433
|
+
}
|
|
374
434
|
this.retryManager.addToQueue(task.id, task.error || 'Unknown error');
|
|
375
435
|
if (this.retryManager.shouldRetry(task.id)) {
|
|
376
436
|
// 将任务从 failed 转为 retry_queue 再转为 pending
|
|
377
437
|
await this.stateManager.updateTask(task.id, {
|
|
378
438
|
status: 'retry_queue',
|
|
379
|
-
retryCount:
|
|
439
|
+
retryCount: currentRetryCount + 1
|
|
380
440
|
});
|
|
381
441
|
await this.stateManager.updateTask(task.id, {
|
|
382
442
|
status: 'pending'
|
|
@@ -313,14 +313,15 @@ class InteractiveQuestionGenerator {
|
|
|
313
313
|
// 7. E2E 测试
|
|
314
314
|
questions.push({
|
|
315
315
|
id: 'e2e_tests',
|
|
316
|
-
question: '
|
|
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: '
|
|
323
|
-
{ key: '
|
|
322
|
+
{ key: 'functional', label: '功能测试 (推荐)', description: '验证业务流程正确性,无需浏览器可视化,速度快' },
|
|
323
|
+
{ key: 'visual', label: '视觉验证', description: '需要浏览器可视化验证,可检查页面样式和布局' },
|
|
324
|
+
{ key: 'false', label: '不需要', description: '仅进行单元测试和集成测试,节省时间' }
|
|
324
325
|
]
|
|
325
326
|
});
|
|
326
327
|
// 8. 风险评估
|
|
@@ -42,24 +42,24 @@ class MeetingManager {
|
|
|
42
42
|
type: 'meeting',
|
|
43
43
|
taskId,
|
|
44
44
|
title: meeting.title,
|
|
45
|
-
description: `
|
|
46
|
-
## 阻塞问题描述
|
|
47
|
-
|
|
48
|
-
**任务**: ${taskId}
|
|
49
|
-
**原因**: ${blockingReason}
|
|
50
|
-
|
|
51
|
-
## 影响范围
|
|
52
|
-
|
|
53
|
-
${impactScope.map(item => `- ${item}`).join('\n')}
|
|
54
|
-
|
|
55
|
-
## 需要的行动
|
|
56
|
-
|
|
57
|
-
请选择以下操作之一:
|
|
58
|
-
|
|
59
|
-
1. **提供信息** - 提供解决阻塞所需的信息
|
|
60
|
-
2. **跳过任务** - 标记任务为可选,跳过继续执行
|
|
61
|
-
3. **修改方案** - 调整任务方案或参数
|
|
62
|
-
4. **取消执行** - 停止整个执行流程
|
|
45
|
+
description: `
|
|
46
|
+
## 阻塞问题描述
|
|
47
|
+
|
|
48
|
+
**任务**: ${taskId}
|
|
49
|
+
**原因**: ${blockingReason}
|
|
50
|
+
|
|
51
|
+
## 影响范围
|
|
52
|
+
|
|
53
|
+
${impactScope.map(item => `- ${item}`).join('\n')}
|
|
54
|
+
|
|
55
|
+
## 需要的行动
|
|
56
|
+
|
|
57
|
+
请选择以下操作之一:
|
|
58
|
+
|
|
59
|
+
1. **提供信息** - 提供解决阻塞所需的信息
|
|
60
|
+
2. **跳过任务** - 标记任务为可选,跳过继续执行
|
|
61
|
+
3. **修改方案** - 调整任务方案或参数
|
|
62
|
+
4. **取消执行** - 停止整个执行流程
|
|
63
63
|
`,
|
|
64
64
|
content: JSON.stringify({ meetingId, blockingReason, impactScope })
|
|
65
65
|
});
|
|
@@ -87,17 +87,17 @@ ${impactScope.map(item => `- ${item}`).join('\n')}
|
|
|
87
87
|
type: 'meeting',
|
|
88
88
|
taskId,
|
|
89
89
|
title: meeting.title,
|
|
90
|
-
description: `
|
|
91
|
-
## 决策点
|
|
92
|
-
|
|
93
|
-
**任务**: ${taskId}
|
|
94
|
-
**问题**: ${decision}
|
|
95
|
-
|
|
96
|
-
## 可选方案
|
|
97
|
-
|
|
98
|
-
${options.map((opt, i) => `${i + 1}. ${opt}`).join('\n')}
|
|
99
|
-
|
|
100
|
-
请在审批时提供您的选择。
|
|
90
|
+
description: `
|
|
91
|
+
## 决策点
|
|
92
|
+
|
|
93
|
+
**任务**: ${taskId}
|
|
94
|
+
**问题**: ${decision}
|
|
95
|
+
|
|
96
|
+
## 可选方案
|
|
97
|
+
|
|
98
|
+
${options.map((opt, i) => `${i + 1}. ${opt}`).join('\n')}
|
|
99
|
+
|
|
100
|
+
请在审批时提供您的选择。
|
|
101
101
|
`,
|
|
102
102
|
content: JSON.stringify({ meetingId, decision, options })
|
|
103
103
|
});
|
|
@@ -136,9 +136,10 @@ ${options.map((opt, i) => `${i + 1}. ${opt}`).join('\n')}
|
|
|
136
136
|
await this.stateManager.saveMeeting(updatedMeeting);
|
|
137
137
|
// 更新关联任务状态
|
|
138
138
|
if (meeting.type === 'blocking') {
|
|
139
|
-
//
|
|
139
|
+
// 将阻塞任务恢复到 pending,让调度器重新调度
|
|
140
|
+
// 阻塞任务可能是 waiting 或 blocked 状态
|
|
140
141
|
const task = await this.stateManager.getTask(meeting.taskId);
|
|
141
|
-
if (task && task.status === 'waiting') {
|
|
142
|
+
if (task && (task.status === 'waiting' || task.status === 'blocked')) {
|
|
142
143
|
await this.stateManager.updateTask(meeting.taskId, {
|
|
143
144
|
status: 'pending',
|
|
144
145
|
error: undefined
|
|
@@ -535,7 +535,7 @@ try {
|
|
|
535
535
|
}
|
|
536
536
|
} catch(e) {
|
|
537
537
|
console.error('❌ Import failed:', e.message);
|
|
538
|
-
process.
|
|
538
|
+
process.exitCode = 1;
|
|
539
539
|
}
|
|
540
540
|
"
|
|
541
541
|
\`\`\`
|
|
@@ -740,7 +740,7 @@ for (const mod of modules) {
|
|
|
740
740
|
}
|
|
741
741
|
if (failed.length > 0) {
|
|
742
742
|
console.error('Failed imports:', failed.join(', '));
|
|
743
|
-
process.
|
|
743
|
+
process.exitCode = 1;
|
|
744
744
|
}
|
|
745
745
|
console.log('All module imports passed');
|
|
746
746
|
"
|
|
@@ -955,7 +955,7 @@ ACCEPT_FAILED
|
|
|
955
955
|
parseQualityReport(output) {
|
|
956
956
|
const result = {
|
|
957
957
|
passed: false,
|
|
958
|
-
tests: { passed: 0, failed: 0, coverage:
|
|
958
|
+
tests: { passed: 0, failed: 0, coverage: -1 }, // -1 = coverage not measured
|
|
959
959
|
build: { success: false, errors: [] },
|
|
960
960
|
lint: { errors: 0, warnings: 0 },
|
|
961
961
|
security: { vulnerabilities: 0 },
|
|
@@ -972,7 +972,8 @@ ACCEPT_FAILED
|
|
|
972
972
|
if (jsonReport.tests) {
|
|
973
973
|
result.tests.passed = jsonReport.tests.passed ?? 0;
|
|
974
974
|
result.tests.failed = jsonReport.tests.failed ?? 0;
|
|
975
|
-
|
|
975
|
+
// -1 表示未测量覆盖率
|
|
976
|
+
result.tests.coverage = jsonReport.tests.coverage ?? -1;
|
|
976
977
|
}
|
|
977
978
|
// 解析 build
|
|
978
979
|
if (jsonReport.build) {
|
|
@@ -1003,7 +1004,8 @@ ACCEPT_FAILED
|
|
|
1003
1004
|
// 判断是否通过
|
|
1004
1005
|
const qc = this.qualityConfig;
|
|
1005
1006
|
const testsPassed = result.tests.failed === 0;
|
|
1006
|
-
|
|
1007
|
+
// 覆盖率 -1 表示未测量(无覆盖率输出),跳过检查
|
|
1008
|
+
const coverageOk = result.tests.coverage < 0 || result.tests.coverage >= qc.minCoverage;
|
|
1007
1009
|
const lintOk = qc.strictLint ? result.lint.errors === 0 : true;
|
|
1008
1010
|
const buildOk = result.build.success;
|
|
1009
1011
|
const e2eOk = qc.e2eTests ? result.e2e.failed === 0 : true;
|
|
@@ -1095,7 +1097,8 @@ ACCEPT_FAILED
|
|
|
1095
1097
|
// 判断是否通过
|
|
1096
1098
|
const qc = this.qualityConfig;
|
|
1097
1099
|
const testsPassed = result.tests.failed === 0;
|
|
1098
|
-
|
|
1100
|
+
// 覆盖率 -1 表示未测量(regex 回退路径下未匹配到覆盖率时保持 -1)
|
|
1101
|
+
const coverageOk = result.tests.coverage < 0 || result.tests.coverage >= qc.minCoverage;
|
|
1099
1102
|
const lintOk = qc.strictLint ? result.lint.errors === 0 : true;
|
|
1100
1103
|
const buildOk = result.build.success;
|
|
1101
1104
|
const e2eOk = qc.e2eTests ? result.e2e.failed === 0 : true;
|
|
@@ -14,8 +14,11 @@ export declare class Scheduler {
|
|
|
14
14
|
private stateManager;
|
|
15
15
|
private stateMachine;
|
|
16
16
|
private config;
|
|
17
|
-
private runningTasks;
|
|
18
17
|
constructor(stateManager: StateManager, config?: Partial<SchedulerConfig>);
|
|
18
|
+
/**
|
|
19
|
+
* 获取当前正在执行的任务数(从持久化状态读取,非内存缓存)
|
|
20
|
+
*/
|
|
21
|
+
private getRunningCount;
|
|
19
22
|
/**
|
|
20
23
|
* 获取下一个可执行的任务
|
|
21
24
|
*/
|
|
@@ -33,7 +36,7 @@ export declare class Scheduler {
|
|
|
33
36
|
*/
|
|
34
37
|
private transitionTask;
|
|
35
38
|
/**
|
|
36
|
-
*
|
|
39
|
+
* 标记任务开始执行(阶段感知,不重置已完成的 phase)
|
|
37
40
|
*/
|
|
38
41
|
markTaskStarted(taskId: string): Promise<void>;
|
|
39
42
|
/**
|
|
@@ -66,11 +69,10 @@ export declare class Scheduler {
|
|
|
66
69
|
*/
|
|
67
70
|
private handleCircularDependencies;
|
|
68
71
|
/**
|
|
69
|
-
*
|
|
72
|
+
* 获取调度状态(从持久化状态读取)
|
|
70
73
|
*/
|
|
71
|
-
getStatus(): {
|
|
74
|
+
getStatus(): Promise<{
|
|
72
75
|
running: number;
|
|
73
76
|
maxConcurrent: number;
|
|
74
|
-
|
|
75
|
-
};
|
|
77
|
+
}>;
|
|
76
78
|
}
|
|
@@ -6,7 +6,6 @@ class Scheduler {
|
|
|
6
6
|
stateManager;
|
|
7
7
|
stateMachine;
|
|
8
8
|
config;
|
|
9
|
-
runningTasks = new Set();
|
|
10
9
|
constructor(stateManager, config) {
|
|
11
10
|
this.stateManager = stateManager;
|
|
12
11
|
this.stateMachine = new state_machine_js_1.StateMachine();
|
|
@@ -16,6 +15,13 @@ class Scheduler {
|
|
|
16
15
|
...config
|
|
17
16
|
};
|
|
18
17
|
}
|
|
18
|
+
/**
|
|
19
|
+
* 获取当前正在执行的任务数(从持久化状态读取,非内存缓存)
|
|
20
|
+
*/
|
|
21
|
+
async getRunningCount() {
|
|
22
|
+
const tasks = await this.stateManager.listTasks();
|
|
23
|
+
return tasks.filter(t => t.status === 'in_progress' || t.status === 'scheduled').length;
|
|
24
|
+
}
|
|
19
25
|
/**
|
|
20
26
|
* 获取下一个可执行的任务
|
|
21
27
|
*/
|
|
@@ -24,7 +30,12 @@ class Scheduler {
|
|
|
24
30
|
// 检测并处理循环依赖
|
|
25
31
|
await this.handleCircularDependencies(tasks);
|
|
26
32
|
// 筛选可执行任务
|
|
27
|
-
const executable =
|
|
33
|
+
const executable = [];
|
|
34
|
+
for (const task of tasks) {
|
|
35
|
+
if (await this.canExecute(task, tasks)) {
|
|
36
|
+
executable.push(task);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
28
39
|
if (executable.length === 0) {
|
|
29
40
|
return null;
|
|
30
41
|
}
|
|
@@ -35,13 +46,14 @@ class Scheduler {
|
|
|
35
46
|
/**
|
|
36
47
|
* 检查任务是否可执行
|
|
37
48
|
*/
|
|
38
|
-
canExecute(task, allTasks) {
|
|
49
|
+
async canExecute(task, allTasks) {
|
|
39
50
|
// 1. 状态必须是 pending 或 retry_queue
|
|
40
51
|
if (task.status !== 'pending' && task.status !== 'retry_queue') {
|
|
41
52
|
return false;
|
|
42
53
|
}
|
|
43
|
-
// 2.
|
|
44
|
-
|
|
54
|
+
// 2. 检查并发限制(从持久化状态读取)
|
|
55
|
+
const runningCount = await this.getRunningCount();
|
|
56
|
+
if (runningCount >= this.config.maxConcurrentTasks) {
|
|
45
57
|
return false;
|
|
46
58
|
}
|
|
47
59
|
// 3. 检查依赖是否完成
|
|
@@ -84,23 +96,39 @@ class Scheduler {
|
|
|
84
96
|
});
|
|
85
97
|
}
|
|
86
98
|
/**
|
|
87
|
-
*
|
|
99
|
+
* 标记任务开始执行(阶段感知,不重置已完成的 phase)
|
|
88
100
|
*/
|
|
89
101
|
async markTaskStarted(taskId) {
|
|
90
|
-
this.
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
102
|
+
const task = await this.stateManager.getTask(taskId);
|
|
103
|
+
if (!task)
|
|
104
|
+
throw new Error(`Task ${taskId} not found`);
|
|
105
|
+
const now = new Date().toISOString();
|
|
106
|
+
// 根据任务当前状态决定设置哪个 phase 为 in_progress
|
|
107
|
+
let phases = { ...task.phases };
|
|
108
|
+
if (task.status === 'pending' || task.status === 'retry_queue') {
|
|
109
|
+
// 首次开始:设置 develop 为 in_progress
|
|
110
|
+
phases.develop = { status: 'in_progress', duration: null, startedAt: now };
|
|
111
|
+
if (phases.verify.status === 'pending') {
|
|
112
|
+
phases.verify = { status: 'pending', duration: null };
|
|
96
113
|
}
|
|
97
|
-
|
|
114
|
+
if (phases.accept.status === 'pending') {
|
|
115
|
+
phases.accept = { status: 'pending', duration: null };
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
else if (task.status === 'verify') {
|
|
119
|
+
// develop 已完成,开始 verify 阶段
|
|
120
|
+
phases.verify = { status: 'in_progress', duration: null, startedAt: now };
|
|
121
|
+
}
|
|
122
|
+
else if (task.status === 'accept') {
|
|
123
|
+
// develop + verify 已完成,开始 accept 阶段
|
|
124
|
+
phases.accept = { status: 'in_progress', duration: null, startedAt: now };
|
|
125
|
+
}
|
|
126
|
+
await this.transitionTask(taskId, 'start', { phases });
|
|
98
127
|
}
|
|
99
128
|
/**
|
|
100
129
|
* 标记任务完成
|
|
101
130
|
*/
|
|
102
131
|
async markTaskCompleted(taskId) {
|
|
103
|
-
this.runningTasks.delete(taskId);
|
|
104
132
|
const task = await this.stateManager.getTask(taskId);
|
|
105
133
|
if (task) {
|
|
106
134
|
await this.transitionTask(taskId, 'accept_done', {
|
|
@@ -115,7 +143,6 @@ class Scheduler {
|
|
|
115
143
|
* 标记任务失败
|
|
116
144
|
*/
|
|
117
145
|
async markTaskFailed(taskId, error) {
|
|
118
|
-
this.runningTasks.delete(taskId);
|
|
119
146
|
const task = await this.stateManager.getTask(taskId);
|
|
120
147
|
const retryCount = task?.retryCount || 0;
|
|
121
148
|
await this.transitionTask(taskId, 'fail', {
|
|
@@ -133,7 +160,6 @@ class Scheduler {
|
|
|
133
160
|
* 标记任务阻塞(需要 Meeting)
|
|
134
161
|
*/
|
|
135
162
|
async markTaskBlocked(taskId, reason) {
|
|
136
|
-
this.runningTasks.delete(taskId);
|
|
137
163
|
await this.transitionTask(taskId, 'block', {
|
|
138
164
|
error: reason
|
|
139
165
|
});
|
|
@@ -146,7 +172,12 @@ class Scheduler {
|
|
|
146
172
|
// 检测并处理循环依赖
|
|
147
173
|
await this.handleCircularDependencies(tasks);
|
|
148
174
|
// 筛选可执行任务
|
|
149
|
-
const executable =
|
|
175
|
+
const executable = [];
|
|
176
|
+
for (const task of tasks) {
|
|
177
|
+
if (await this.canExecute(task, tasks)) {
|
|
178
|
+
executable.push(task);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
150
181
|
// 按优先级排序(高优先级优先)
|
|
151
182
|
executable.sort((a, b) => this.getPriorityWeight(b.priority) - this.getPriorityWeight(a.priority));
|
|
152
183
|
// 限制并发数
|
|
@@ -208,13 +239,13 @@ class Scheduler {
|
|
|
208
239
|
}
|
|
209
240
|
}
|
|
210
241
|
/**
|
|
211
|
-
*
|
|
242
|
+
* 获取调度状态(从持久化状态读取)
|
|
212
243
|
*/
|
|
213
|
-
getStatus() {
|
|
244
|
+
async getStatus() {
|
|
245
|
+
const running = await this.getRunningCount();
|
|
214
246
|
return {
|
|
215
|
-
running
|
|
216
|
-
maxConcurrent: this.config.maxConcurrentTasks
|
|
217
|
-
runningTaskIds: Array.from(this.runningTasks)
|
|
247
|
+
running,
|
|
248
|
+
maxConcurrent: this.config.maxConcurrentTasks
|
|
218
249
|
};
|
|
219
250
|
}
|
|
220
251
|
}
|
|
@@ -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' },
|
|
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' },
|
|
@@ -1,4 +1,32 @@
|
|
|
1
1
|
import type { ParsedTask, QualityConfig } from '../types/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* 从 plan 中解析出的结构化模块信息
|
|
4
|
+
*/
|
|
5
|
+
export interface PlanModule {
|
|
6
|
+
/** 模块名称,如 "用户域" */
|
|
7
|
+
name: string;
|
|
8
|
+
/** 模块描述 */
|
|
9
|
+
description: string;
|
|
10
|
+
/** 关联的表,如 ["users", "user_profiles"] */
|
|
11
|
+
tables: string[];
|
|
12
|
+
/** 模块类型 */
|
|
13
|
+
type: 'domain' | 'feature' | 'infra' | 'system';
|
|
14
|
+
/** 依赖的其他模块名称 */
|
|
15
|
+
dependsOn: string[];
|
|
16
|
+
/** 预估复杂度 */
|
|
17
|
+
complexity: 'low' | 'medium' | 'high';
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* 从 plan 中解析出的结构化信息
|
|
21
|
+
*/
|
|
22
|
+
export interface ParsedPlan {
|
|
23
|
+
/** 核心模块列表 */
|
|
24
|
+
modules: PlanModule[];
|
|
25
|
+
/** 技术栈摘要 */
|
|
26
|
+
techStack: string[];
|
|
27
|
+
/** 原始 plan 文本 */
|
|
28
|
+
raw: string;
|
|
29
|
+
}
|
|
2
30
|
export interface TaskBreakdown {
|
|
3
31
|
taskId: string;
|
|
4
32
|
title: string;
|
|
@@ -19,8 +47,8 @@ export interface UserAnswers {
|
|
|
19
47
|
additionalContext?: Record<string, string | string[]>;
|
|
20
48
|
/** 是否启用 E2E 测试 */
|
|
21
49
|
e2eTests?: boolean;
|
|
22
|
-
/** E2E 测试类型 (web/mobile/gui) */
|
|
23
|
-
e2eType?: 'web' | 'mobile' | 'gui';
|
|
50
|
+
/** E2E 测试类型 (web/mobile/gui/visual) */
|
|
51
|
+
e2eType?: 'web' | 'mobile' | 'gui' | 'visual';
|
|
24
52
|
/** 质量级别 */
|
|
25
53
|
qualityLevel?: 'fast' | 'balanced' | 'strict';
|
|
26
54
|
}
|
|
@@ -38,16 +66,53 @@ export interface UserAnswers {
|
|
|
38
66
|
*/
|
|
39
67
|
export declare class TaskPlanner {
|
|
40
68
|
private userAnswers;
|
|
41
|
-
|
|
69
|
+
/** 静态计数器,避免多实例生成重复 ID */
|
|
70
|
+
private static taskCounter;
|
|
42
71
|
constructor(userAnswers?: UserAnswers);
|
|
43
72
|
/**
|
|
44
73
|
* 设置用户回答
|
|
45
74
|
*/
|
|
46
75
|
setUserAnswers(answers: UserAnswers): void;
|
|
47
76
|
/**
|
|
48
|
-
*
|
|
77
|
+
* 解析 plan 文本,提取模块、技术栈、数据模型等结构化信息
|
|
49
78
|
*/
|
|
79
|
+
parsePlan(planText: string): ParsedPlan;
|
|
80
|
+
/**
|
|
81
|
+
* 分析模块间依赖关系
|
|
82
|
+
*
|
|
83
|
+
* 策略:从 plan 文本中提取 AI 明确写的依赖信息,不做架构猜测。
|
|
84
|
+
* 如果 plan 中没有指定依赖,模块之间并行执行。
|
|
85
|
+
*/
|
|
86
|
+
private analyzeModuleDependencies;
|
|
87
|
+
/**
|
|
88
|
+
* 从 plan 文本中提取某个模块相关的上下文段落
|
|
89
|
+
*/
|
|
90
|
+
private extractModuleContext;
|
|
91
|
+
/**
|
|
92
|
+
* 预估模块复杂度 — 基于通用架构特征,不依赖具体业务领域
|
|
93
|
+
*/
|
|
94
|
+
private estimateModuleComplexity;
|
|
50
95
|
breakdown(parsedTask: ParsedTask, answers: Record<string, string>, qualityConfig?: QualityConfig, plan?: string): TaskBreakdown[];
|
|
96
|
+
/**
|
|
97
|
+
* 基于 plan 解析出的模块做细粒度任务拆分
|
|
98
|
+
*/
|
|
99
|
+
private breakdownByModules;
|
|
100
|
+
/**
|
|
101
|
+
* 为模块任务添加 phase 级别的跨阶段依赖
|
|
102
|
+
*/
|
|
103
|
+
private enforcePhaseDependenciesForModule;
|
|
104
|
+
/**
|
|
105
|
+
* 确定模块任务优先级
|
|
106
|
+
*/
|
|
107
|
+
private determineModulePriority;
|
|
108
|
+
/**
|
|
109
|
+
* 为模块生成验收标准
|
|
110
|
+
*/
|
|
111
|
+
private generateModuleAcceptanceCriteria;
|
|
112
|
+
/**
|
|
113
|
+
* 按目标拆分的传统方式(fallback)
|
|
114
|
+
*/
|
|
115
|
+
private breakdownByGoals;
|
|
51
116
|
/**
|
|
52
117
|
* 判断是否需要设计阶段
|
|
53
118
|
*
|
|
@@ -84,4 +149,18 @@ export declare class TaskPlanner {
|
|
|
84
149
|
* - other: 其他类型(配置、优化、部署等) → 单个任务
|
|
85
150
|
*/
|
|
86
151
|
private classifyGoal;
|
|
152
|
+
/**
|
|
153
|
+
* 检测顺序阶段目标(如 "Phase 1 基础架构"、"Phase 2 AI创作核心")
|
|
154
|
+
* 返回按阶段排序的索引列表
|
|
155
|
+
*/
|
|
156
|
+
private detectSequentialPhases;
|
|
157
|
+
/**
|
|
158
|
+
* 对顺序阶段建立跨阶段依赖
|
|
159
|
+
*
|
|
160
|
+
* 当 goals 包含 "Phase 1", "Phase 2", "Phase 3" 等顺序阶段时,
|
|
161
|
+
* 后续阶段的所有开发任务必须依赖前一阶段的集成任务(或最后一个开发任务)。
|
|
162
|
+
*
|
|
163
|
+
* 这防止多个阶段同时执行导致文件冲突。
|
|
164
|
+
*/
|
|
165
|
+
private enforcePhaseDependencies;
|
|
87
166
|
}
|