openmatrix 0.2.15 → 0.2.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +83 -1
- package/dist/agents/agent-runner.d.ts +4 -0
- package/dist/agents/agent-runner.js +86 -0
- package/dist/cli/commands/brainstorm.d.ts +2 -2
- package/dist/cli/commands/brainstorm.js +2 -8
- package/dist/cli/commands/deploy.d.ts +16 -0
- package/dist/cli/commands/deploy.js +205 -0
- package/dist/cli/index.js +2 -2
- package/dist/orchestrator/answer-mapper.d.ts +0 -8
- package/dist/orchestrator/answer-mapper.js +1 -27
- package/dist/orchestrator/environment-detector.d.ts +51 -0
- package/dist/orchestrator/environment-detector.js +1110 -0
- package/dist/orchestrator/executor.d.ts +32 -2
- package/dist/orchestrator/executor.js +119 -1
- package/dist/orchestrator/meeting-manager.d.ts +16 -1
- package/dist/orchestrator/meeting-manager.js +113 -0
- package/dist/orchestrator/phase-executor.d.ts +1 -0
- package/dist/orchestrator/phase-executor.js +3 -0
- package/dist/orchestrator/smart-question-analyzer.js +38 -10
- package/dist/orchestrator/task-planner.js +19 -7
- package/dist/types/index.d.ts +194 -1
- package/package.json +2 -1
- package/skills/auto.md +64 -3
- package/skills/debug.md +168 -33
- package/skills/deploy.md +338 -0
- package/skills/feature.md +165 -53
- package/skills/resume.md +260 -68
- package/skills/start.md +66 -3
|
@@ -4,6 +4,7 @@ import type { SubagentTask } from '../types/index.js';
|
|
|
4
4
|
import { StateManager } from '../storage/state-manager.js';
|
|
5
5
|
import { ApprovalManager } from './approval-manager.js';
|
|
6
6
|
import { PhaseExecutor } from './phase-executor.js';
|
|
7
|
+
import type { AmbiguityReport } from '../types/index.js';
|
|
7
8
|
export interface ExecutorConfig {
|
|
8
9
|
maxConcurrent: number;
|
|
9
10
|
taskTimeout: number;
|
|
@@ -16,7 +17,7 @@ export interface ExecutorStatus {
|
|
|
16
17
|
waitingApprovals: number;
|
|
17
18
|
}
|
|
18
19
|
export interface ExecutionResult {
|
|
19
|
-
status: 'continue' | 'waiting_approval' | 'completed' | 'failed';
|
|
20
|
+
status: 'continue' | 'waiting_approval' | 'completed' | 'failed' | 'ambiguity_ask_user';
|
|
20
21
|
subagentTasks: SubagentTask[];
|
|
21
22
|
message: string;
|
|
22
23
|
statistics: {
|
|
@@ -26,6 +27,8 @@ export interface ExecutionResult {
|
|
|
26
27
|
pending: number;
|
|
27
28
|
failed: number;
|
|
28
29
|
};
|
|
30
|
+
/** 歧义报告 (仅 status 为 'ambiguity_ask_user' 时存在) */
|
|
31
|
+
ambiguityReport?: AmbiguityReport;
|
|
29
32
|
}
|
|
30
33
|
/**
|
|
31
34
|
* OrchestratorExecutor - 执行循环核心
|
|
@@ -41,6 +44,7 @@ export declare class OrchestratorExecutor {
|
|
|
41
44
|
private phaseExecutor;
|
|
42
45
|
private retryManager;
|
|
43
46
|
private aiReviewer;
|
|
47
|
+
private meetingManager;
|
|
44
48
|
private config;
|
|
45
49
|
private taskTimers;
|
|
46
50
|
constructor(stateManager: StateManager, approvalManager: ApprovalManager, config?: Partial<ExecutorConfig>);
|
|
@@ -94,7 +98,9 @@ export declare class OrchestratorExecutor {
|
|
|
94
98
|
/**
|
|
95
99
|
* 标记任务完成
|
|
96
100
|
*
|
|
97
|
-
*
|
|
101
|
+
* 增强:
|
|
102
|
+
* 1. 对于 reviewer 任务,自动解析 Review 报告并生成修复任务
|
|
103
|
+
* 2. 解析歧义报告并根据执行模式处理
|
|
98
104
|
*/
|
|
99
105
|
completeTask(taskId: string, result: {
|
|
100
106
|
success: boolean;
|
|
@@ -102,6 +108,10 @@ export declare class OrchestratorExecutor {
|
|
|
102
108
|
error?: string;
|
|
103
109
|
}): Promise<{
|
|
104
110
|
createdFixTasks?: string[];
|
|
111
|
+
ambiguityResult?: {
|
|
112
|
+
status: 'ambiguity_ask_user' | 'ambiguity_handled';
|
|
113
|
+
report: AmbiguityReport;
|
|
114
|
+
};
|
|
105
115
|
}>;
|
|
106
116
|
/**
|
|
107
117
|
* 处理 Review 结果:解析报告,如有 critical/major 问题则自动创建修复任务
|
|
@@ -111,6 +121,26 @@ export declare class OrchestratorExecutor {
|
|
|
111
121
|
* 获取当前阶段
|
|
112
122
|
*/
|
|
113
123
|
private getCurrentPhase;
|
|
124
|
+
/**
|
|
125
|
+
* 解析歧义报告
|
|
126
|
+
*
|
|
127
|
+
* 从 Agent 输出中提取歧义报告 JSON
|
|
128
|
+
* 支持多种标记格式:
|
|
129
|
+
* - <ambiguity_report>...</ambiguity_report>
|
|
130
|
+
* - AMBIGUITY_REPORT: {...}
|
|
131
|
+
* - 直接 JSON 块
|
|
132
|
+
*/
|
|
133
|
+
private parseAmbiguityReport;
|
|
134
|
+
/**
|
|
135
|
+
* 处理歧义
|
|
136
|
+
*
|
|
137
|
+
* 根据执行模式和严重程度选择处理策略:
|
|
138
|
+
* - auto 模式:所有歧义写入 Meeting,继续执行
|
|
139
|
+
* - 其他模式:
|
|
140
|
+
* - Critical/High:返回 ambiguity_ask_user 状态,让 Skill 层用 AskUserQuestion 处理
|
|
141
|
+
* - Medium/Low:写入 Meeting,继续执行
|
|
142
|
+
*/
|
|
143
|
+
private handleAmbiguity;
|
|
114
144
|
/**
|
|
115
145
|
* 获取执行器状态
|
|
116
146
|
*/
|
|
@@ -8,6 +8,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
10
|
const ai_reviewer_js_1 = require("./ai-reviewer.js");
|
|
11
|
+
const meeting_manager_js_1 = require("./meeting-manager.js");
|
|
11
12
|
const logger_js_1 = require("../utils/logger.js");
|
|
12
13
|
/**
|
|
13
14
|
* OrchestratorExecutor - 执行循环核心
|
|
@@ -23,11 +24,13 @@ class OrchestratorExecutor {
|
|
|
23
24
|
phaseExecutor;
|
|
24
25
|
retryManager;
|
|
25
26
|
aiReviewer;
|
|
27
|
+
meetingManager;
|
|
26
28
|
config;
|
|
27
29
|
taskTimers = new Map();
|
|
28
30
|
constructor(stateManager, approvalManager, config) {
|
|
29
31
|
this.stateManager = stateManager;
|
|
30
32
|
this.approvalManager = approvalManager;
|
|
33
|
+
this.meetingManager = new meeting_manager_js_1.MeetingManager(stateManager, approvalManager);
|
|
31
34
|
// 从 state.config 读取 taskTimeout,如果未定义则使用默认值
|
|
32
35
|
const stateConfig = stateManager.getState().then(s => s.config).catch(() => null);
|
|
33
36
|
const defaultTaskTimeout = 600000; // 10 分钟(毫秒)
|
|
@@ -265,13 +268,25 @@ class OrchestratorExecutor {
|
|
|
265
268
|
/**
|
|
266
269
|
* 标记任务完成
|
|
267
270
|
*
|
|
268
|
-
*
|
|
271
|
+
* 增强:
|
|
272
|
+
* 1. 对于 reviewer 任务,自动解析 Review 报告并生成修复任务
|
|
273
|
+
* 2. 解析歧义报告并根据执行模式处理
|
|
269
274
|
*/
|
|
270
275
|
async completeTask(taskId, result) {
|
|
271
276
|
const task = await this.stateManager.getTask(taskId);
|
|
272
277
|
if (!task) {
|
|
273
278
|
throw new Error(`Task ${taskId} not found`);
|
|
274
279
|
}
|
|
280
|
+
// 检查输出中是否包含歧义报告(仅匹配 XML 标签作为唯一触发标记)
|
|
281
|
+
if (result.output?.includes('<ambiguity_report>')) {
|
|
282
|
+
const ambiguityReport = this.parseAmbiguityReport(taskId, result.output);
|
|
283
|
+
if (ambiguityReport?.hasAmbiguity) {
|
|
284
|
+
const ambiguityResult = await this.handleAmbiguity(task, ambiguityReport);
|
|
285
|
+
if (ambiguityResult) {
|
|
286
|
+
return { ambiguityResult };
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
275
290
|
if (result.success) {
|
|
276
291
|
// 清除超时计时器
|
|
277
292
|
this.clearTaskTimeout(taskId);
|
|
@@ -370,6 +385,109 @@ class OrchestratorExecutor {
|
|
|
370
385
|
return 'verify';
|
|
371
386
|
return 'accept';
|
|
372
387
|
}
|
|
388
|
+
// ============ Ambiguity Management ============
|
|
389
|
+
/**
|
|
390
|
+
* 解析歧义报告
|
|
391
|
+
*
|
|
392
|
+
* 从 Agent 输出中提取歧义报告 JSON
|
|
393
|
+
* 支持多种标记格式:
|
|
394
|
+
* - <ambiguity_report>...</ambiguity_report>
|
|
395
|
+
* - AMBIGUITY_REPORT: {...}
|
|
396
|
+
* - 直接 JSON 块
|
|
397
|
+
*/
|
|
398
|
+
parseAmbiguityReport(taskId, output) {
|
|
399
|
+
try {
|
|
400
|
+
// 尝试提取 XML 标记格式
|
|
401
|
+
const xmlMatch = output.match(/<ambiguity_report>([\s\S]*?)<\/ambiguity_report>/);
|
|
402
|
+
if (xmlMatch) {
|
|
403
|
+
const jsonStr = xmlMatch[1].trim();
|
|
404
|
+
const report = JSON.parse(jsonStr);
|
|
405
|
+
return { ...report, taskId };
|
|
406
|
+
}
|
|
407
|
+
// 尝试提取 AMBIGUITY_REPORT: 格式
|
|
408
|
+
const prefixMatch = output.match(/AMBIGUITY_REPORT:\s*([\s\S]*?)(?:\n\n|\n---|$)/);
|
|
409
|
+
if (prefixMatch) {
|
|
410
|
+
const jsonStr = prefixMatch[1].trim();
|
|
411
|
+
const report = JSON.parse(jsonStr);
|
|
412
|
+
return { ...report, taskId };
|
|
413
|
+
}
|
|
414
|
+
// 尝试查找包含 hasAmbiguity 的 JSON 块(平衡括号匹配,作为最后兜底)
|
|
415
|
+
const hasAmbiguityIdx = output.indexOf('"hasAmbiguity"');
|
|
416
|
+
if (hasAmbiguityIdx !== -1) {
|
|
417
|
+
// 向前扫描找到深度为 1 的最外层 {
|
|
418
|
+
let start = -1;
|
|
419
|
+
let depth = 0;
|
|
420
|
+
for (let i = hasAmbiguityIdx; i >= 0; i--) {
|
|
421
|
+
if (output[i] === '}')
|
|
422
|
+
depth++;
|
|
423
|
+
else if (output[i] === '{') {
|
|
424
|
+
depth--;
|
|
425
|
+
if (depth < 0) {
|
|
426
|
+
start = i;
|
|
427
|
+
break;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
if (start !== -1) {
|
|
432
|
+
let d = 0;
|
|
433
|
+
let end = -1;
|
|
434
|
+
for (let i = start; i < output.length; i++) {
|
|
435
|
+
if (output[i] === '{')
|
|
436
|
+
d++;
|
|
437
|
+
else if (output[i] === '}') {
|
|
438
|
+
d--;
|
|
439
|
+
if (d === 0) {
|
|
440
|
+
end = i;
|
|
441
|
+
break;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
if (end !== -1) {
|
|
446
|
+
const report = JSON.parse(output.slice(start, end + 1));
|
|
447
|
+
return { ...report, taskId };
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
return null;
|
|
452
|
+
}
|
|
453
|
+
catch (error) {
|
|
454
|
+
console.warn(`⚠️ 解析歧义报告失败: ${taskId}`, error);
|
|
455
|
+
return null;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* 处理歧义
|
|
460
|
+
*
|
|
461
|
+
* 根据执行模式和严重程度选择处理策略:
|
|
462
|
+
* - auto 模式:所有歧义写入 Meeting,继续执行
|
|
463
|
+
* - 其他模式:
|
|
464
|
+
* - Critical/High:返回 ambiguity_ask_user 状态,让 Skill 层用 AskUserQuestion 处理
|
|
465
|
+
* - Medium/Low:写入 Meeting,继续执行
|
|
466
|
+
*/
|
|
467
|
+
async handleAmbiguity(task, report) {
|
|
468
|
+
const state = await this.stateManager.getState();
|
|
469
|
+
const mode = state.config.approvalPoints.length === 0 ? 'auto' : 'interactive';
|
|
470
|
+
console.log(`🔍 检测到歧义: ${task.id} - 最高严重程度: ${report.maxSeverity || 'unknown'}`);
|
|
471
|
+
// auto 模式:所有歧义写入 Meeting,继续执行
|
|
472
|
+
if (mode === 'auto') {
|
|
473
|
+
await this.meetingManager.createAmbiguityMeeting(task.id, report);
|
|
474
|
+
console.log(`📝 [auto模式] 歧义已写入 Meeting,继续执行`);
|
|
475
|
+
return { status: 'ambiguity_handled', report };
|
|
476
|
+
}
|
|
477
|
+
// 其他模式:根据严重程度处理
|
|
478
|
+
const severity = report.maxSeverity;
|
|
479
|
+
if (severity === 'critical' || severity === 'high') {
|
|
480
|
+
// Critical/High: 返回特殊状态让 Skill 层用 AskUserQuestion 处理
|
|
481
|
+
console.log(`⚠️ [interactive模式] Critical/High 歧义,需用户确认`);
|
|
482
|
+
return { status: 'ambiguity_ask_user', report };
|
|
483
|
+
}
|
|
484
|
+
else {
|
|
485
|
+
// Medium/Low: 写入 Meeting,继续执行
|
|
486
|
+
await this.meetingManager.createAmbiguityMeeting(task.id, report);
|
|
487
|
+
console.log(`📝 [interactive模式] Medium/Low 歧义已写入 Meeting,继续执行`);
|
|
488
|
+
return { status: 'ambiguity_handled', report };
|
|
489
|
+
}
|
|
490
|
+
}
|
|
373
491
|
/**
|
|
374
492
|
* 获取执行器状态
|
|
375
493
|
*/
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { StateManager } from '../storage/state-manager.js';
|
|
2
2
|
import { ApprovalManager } from './approval-manager.js';
|
|
3
|
-
import type { Approval, Meeting } from '../types/index.js';
|
|
3
|
+
import type { Approval, Meeting, AmbiguityReport } from '../types/index.js';
|
|
4
4
|
/**
|
|
5
5
|
* MeetingManager - Meeting 管理器
|
|
6
6
|
*
|
|
@@ -28,6 +28,21 @@ export declare class MeetingManager {
|
|
|
28
28
|
meeting: Meeting;
|
|
29
29
|
approval: Approval;
|
|
30
30
|
}>;
|
|
31
|
+
/**
|
|
32
|
+
* 获取严重程度对应的图标和标题前缀
|
|
33
|
+
*/
|
|
34
|
+
private getSeverityPrefix;
|
|
35
|
+
/**
|
|
36
|
+
* 格式化歧义报告为 Markdown 描述
|
|
37
|
+
*/
|
|
38
|
+
private formatAmbiguityReport;
|
|
39
|
+
/**
|
|
40
|
+
* 创建歧义 Meeting
|
|
41
|
+
*/
|
|
42
|
+
createAmbiguityMeeting(taskId: string, ambiguityReport: AmbiguityReport): Promise<{
|
|
43
|
+
meeting: Meeting;
|
|
44
|
+
approval: Approval;
|
|
45
|
+
}>;
|
|
31
46
|
/**
|
|
32
47
|
* 开始 Meeting
|
|
33
48
|
*/
|
|
@@ -104,6 +104,119 @@ ${options.map((opt, i) => `${i + 1}. ${opt}`).join('\n')}
|
|
|
104
104
|
});
|
|
105
105
|
return { meeting, approval };
|
|
106
106
|
}
|
|
107
|
+
/**
|
|
108
|
+
* 获取严重程度对应的图标和标题前缀
|
|
109
|
+
*/
|
|
110
|
+
getSeverityPrefix(severity) {
|
|
111
|
+
const prefixes = {
|
|
112
|
+
critical: '🔴 Critical',
|
|
113
|
+
high: '🟠 High',
|
|
114
|
+
medium: '🟡 Medium',
|
|
115
|
+
low: '🟢 Low'
|
|
116
|
+
};
|
|
117
|
+
return prefixes[severity];
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* 格式化歧义报告为 Markdown 描述
|
|
121
|
+
*/
|
|
122
|
+
formatAmbiguityReport(report) {
|
|
123
|
+
const lines = [];
|
|
124
|
+
lines.push('## 歧义检测结果');
|
|
125
|
+
lines.push('');
|
|
126
|
+
lines.push(`**检测阶段**: ${report.detectionPhase === 'pre_execution' ? '执行前' : '执行中'}`);
|
|
127
|
+
lines.push(`**检测时间**: ${report.detectedAt}`);
|
|
128
|
+
lines.push(`**歧义数量**: ${report.ambiguities.length}`);
|
|
129
|
+
if (report.maxSeverity) {
|
|
130
|
+
lines.push(`**最高严重程度**: ${this.getSeverityPrefix(report.maxSeverity)}`);
|
|
131
|
+
}
|
|
132
|
+
lines.push('');
|
|
133
|
+
lines.push('## 歧义详情');
|
|
134
|
+
lines.push('');
|
|
135
|
+
for (const ambiguity of report.ambiguities) {
|
|
136
|
+
const severityIcon = this.getSeverityPrefix(ambiguity.severity);
|
|
137
|
+
lines.push(`### ${severityIcon} ${ambiguity.type}`);
|
|
138
|
+
lines.push('');
|
|
139
|
+
lines.push(`**描述**: ${ambiguity.description}`);
|
|
140
|
+
lines.push('');
|
|
141
|
+
if (ambiguity.impactScope.length > 0) {
|
|
142
|
+
lines.push('**影响范围**:');
|
|
143
|
+
for (const scope of ambiguity.impactScope) {
|
|
144
|
+
lines.push(`- ${scope}`);
|
|
145
|
+
}
|
|
146
|
+
lines.push('');
|
|
147
|
+
}
|
|
148
|
+
if (ambiguity.possibleSolutions && ambiguity.possibleSolutions.length > 0) {
|
|
149
|
+
lines.push('**可能的解决方案**:');
|
|
150
|
+
for (let i = 0; i < ambiguity.possibleSolutions.length; i++) {
|
|
151
|
+
lines.push(`${i + 1}. ${ambiguity.possibleSolutions[i]}`);
|
|
152
|
+
}
|
|
153
|
+
lines.push('');
|
|
154
|
+
}
|
|
155
|
+
if (ambiguity.relatedFiles && ambiguity.relatedFiles.length > 0) {
|
|
156
|
+
lines.push(`**相关文件**: ${ambiguity.relatedFiles.join(', ')}`);
|
|
157
|
+
lines.push('');
|
|
158
|
+
}
|
|
159
|
+
if (ambiguity.relatedTaskIds && ambiguity.relatedTaskIds.length > 0) {
|
|
160
|
+
lines.push(`**相关任务**: ${ambiguity.relatedTaskIds.join(', ')}`);
|
|
161
|
+
lines.push('');
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
if (report.suggestedStrategy) {
|
|
165
|
+
lines.push('## 建议处理策略');
|
|
166
|
+
lines.push('');
|
|
167
|
+
const strategyMap = {
|
|
168
|
+
ask_immediate: '立即提问 (ask_immediate)',
|
|
169
|
+
write_meeting: '写入 Meeting (write_meeting)',
|
|
170
|
+
continue: '继续执行 (continue)'
|
|
171
|
+
};
|
|
172
|
+
lines.push(strategyMap[report.suggestedStrategy] || report.suggestedStrategy);
|
|
173
|
+
lines.push('');
|
|
174
|
+
}
|
|
175
|
+
if (report.suggestedQuestions && report.suggestedQuestions.length > 0) {
|
|
176
|
+
lines.push('## 建议的问题');
|
|
177
|
+
lines.push('');
|
|
178
|
+
for (let i = 0; i < report.suggestedQuestions.length; i++) {
|
|
179
|
+
lines.push(`${i + 1}. ${report.suggestedQuestions[i]}`);
|
|
180
|
+
}
|
|
181
|
+
lines.push('');
|
|
182
|
+
}
|
|
183
|
+
return lines.join('\n');
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* 创建歧义 Meeting
|
|
187
|
+
*/
|
|
188
|
+
async createAmbiguityMeeting(taskId, ambiguityReport) {
|
|
189
|
+
const meetingId = `meeting-${Date.now().toString(36)}`;
|
|
190
|
+
const now = new Date().toISOString();
|
|
191
|
+
// 根据最高严重程度生成标题
|
|
192
|
+
const severity = ambiguityReport.maxSeverity || 'medium';
|
|
193
|
+
const severityPrefix = this.getSeverityPrefix(severity);
|
|
194
|
+
const titleSummary = ambiguityReport.ambiguities[0]?.description.slice(0, 50) || '歧义检测';
|
|
195
|
+
const meeting = {
|
|
196
|
+
id: meetingId,
|
|
197
|
+
type: 'ambiguity',
|
|
198
|
+
status: 'pending',
|
|
199
|
+
taskId,
|
|
200
|
+
title: `${severityPrefix}: ${titleSummary}...`,
|
|
201
|
+
description: `任务 ${taskId} 检测到歧义,需要用户确认处理方式`,
|
|
202
|
+
impactScope: ambiguityReport.ambiguities.flatMap(a => a.impactScope),
|
|
203
|
+
participants: ['user'],
|
|
204
|
+
createdAt: now,
|
|
205
|
+
ambiguityReport,
|
|
206
|
+
suggestedQuestions: ambiguityReport.suggestedQuestions
|
|
207
|
+
};
|
|
208
|
+
// 保存 Meeting
|
|
209
|
+
await this.stateManager.saveMeeting(meeting);
|
|
210
|
+
// 创建审批
|
|
211
|
+
const approval = await this.approvalManager.createApproval({
|
|
212
|
+
type: 'meeting',
|
|
213
|
+
taskId,
|
|
214
|
+
title: meeting.title,
|
|
215
|
+
description: this.formatAmbiguityReport(ambiguityReport),
|
|
216
|
+
content: JSON.stringify({ meetingId, ambiguityReport })
|
|
217
|
+
});
|
|
218
|
+
return { meeting, approval };
|
|
219
|
+
}
|
|
107
220
|
/**
|
|
108
221
|
* 开始 Meeting
|
|
109
222
|
*/
|
|
@@ -58,9 +58,14 @@ class SmartQuestionAnalyzer {
|
|
|
58
58
|
async analyze(taskDescription, parsedTask) {
|
|
59
59
|
// 1. 获取项目上下文
|
|
60
60
|
const projectContext = await this.getProjectContext();
|
|
61
|
-
// 2.
|
|
62
|
-
|
|
63
|
-
|
|
61
|
+
// 2. 如果 parsedTask 有 goalTypes,优先使用
|
|
62
|
+
let goalTypeOverride;
|
|
63
|
+
if (parsedTask?.goalTypes && parsedTask.goalTypes.length > 0) {
|
|
64
|
+
goalTypeOverride = parsedTask.goalTypes[0];
|
|
65
|
+
}
|
|
66
|
+
// 3. 执行推断
|
|
67
|
+
const inferences = this.inferAnswers(taskDescription, projectContext, goalTypeOverride);
|
|
68
|
+
// 4. 筛选需要提问的问题
|
|
64
69
|
const questionsToAsk = inferences
|
|
65
70
|
.filter(i => i.confidence === 'low' || !i.inferredAnswer)
|
|
66
71
|
.map(i => i.questionId);
|
|
@@ -163,7 +168,7 @@ class SmartQuestionAnalyzer {
|
|
|
163
168
|
/**
|
|
164
169
|
* 推断问题答案
|
|
165
170
|
*/
|
|
166
|
-
inferAnswers(taskDescription, context) {
|
|
171
|
+
inferAnswers(taskDescription, context, goalTypeOverride) {
|
|
167
172
|
const inferences = [];
|
|
168
173
|
const desc = taskDescription.toLowerCase();
|
|
169
174
|
// 1. 推断质量级别
|
|
@@ -180,6 +185,15 @@ class SmartQuestionAnalyzer {
|
|
|
180
185
|
inferences.push(this.inferObjective(desc, context));
|
|
181
186
|
// 7. 推断测试覆盖率级别
|
|
182
187
|
inferences.push(this.inferTestCoverage(desc, context));
|
|
188
|
+
// 8. 如果有 goalTypeOverride,覆盖 objective 推断
|
|
189
|
+
if (goalTypeOverride) {
|
|
190
|
+
const objectiveInference = inferences.find(i => i.questionId === 'objective');
|
|
191
|
+
if (objectiveInference) {
|
|
192
|
+
objectiveInference.inferredAnswer = goalTypeOverride;
|
|
193
|
+
objectiveInference.confidence = 'high';
|
|
194
|
+
objectiveInference.reason = '来自 AI 任务解析的目标类型标注';
|
|
195
|
+
}
|
|
196
|
+
}
|
|
183
197
|
return inferences;
|
|
184
198
|
}
|
|
185
199
|
/**
|
|
@@ -336,6 +350,13 @@ class SmartQuestionAnalyzer {
|
|
|
336
350
|
confidence: 'low',
|
|
337
351
|
reason: ''
|
|
338
352
|
};
|
|
353
|
+
// 文档/skill 文件 -> 不需要 E2E
|
|
354
|
+
if (/(skill|\.md|文档|readme|document|说明|指南)/i.test(desc)) {
|
|
355
|
+
inference.inferredAnswer = 'false';
|
|
356
|
+
inference.confidence = 'high';
|
|
357
|
+
inference.reason = '文档或 skill 文件修改不需要 E2E 测试';
|
|
358
|
+
return inference;
|
|
359
|
+
}
|
|
339
360
|
// CLI/后端 -> 不需要 E2E
|
|
340
361
|
if (/(cli|api|backend|后端|命令行)/i.test(desc) && !context.hasFrontend) {
|
|
341
362
|
inference.inferredAnswer = 'false';
|
|
@@ -397,23 +418,30 @@ class SmartQuestionAnalyzer {
|
|
|
397
418
|
confidence: 'low',
|
|
398
419
|
reason: ''
|
|
399
420
|
};
|
|
421
|
+
// 文档类任务 -> documentation
|
|
422
|
+
if (/(文档|document|readme|skill|\.md|说明|指南)/i.test(desc)) {
|
|
423
|
+
inference.inferredAnswer = 'documentation';
|
|
424
|
+
inference.confidence = 'high';
|
|
425
|
+
inference.reason = '任务涉及文档或 skill 文件';
|
|
426
|
+
return inference;
|
|
427
|
+
}
|
|
400
428
|
if (/(fix|bug|修复|hotfix|patch)/i.test(desc)) {
|
|
401
|
-
inference.inferredAnswer = '
|
|
429
|
+
inference.inferredAnswer = 'development';
|
|
402
430
|
inference.confidence = 'medium';
|
|
403
|
-
inference.reason = '
|
|
431
|
+
inference.reason = '任务描述包含修复关键词(bug 修复属于开发)';
|
|
404
432
|
}
|
|
405
433
|
else if (/(implement|add|新功能|实现|开发|feature|新增)/i.test(desc)) {
|
|
406
|
-
inference.inferredAnswer = '
|
|
434
|
+
inference.inferredAnswer = 'development';
|
|
407
435
|
inference.confidence = 'medium';
|
|
408
436
|
inference.reason = '任务描述包含新功能关键词';
|
|
409
437
|
}
|
|
410
438
|
else if (/(refactor|优化|重构|improve|性能)/i.test(desc)) {
|
|
411
|
-
inference.inferredAnswer = '
|
|
439
|
+
inference.inferredAnswer = 'development';
|
|
412
440
|
inference.confidence = 'medium';
|
|
413
|
-
inference.reason = '
|
|
441
|
+
inference.reason = '任务描述包含重构关键词(重构属于开发)';
|
|
414
442
|
}
|
|
415
443
|
else if (/(test|测试|coverage|覆盖)/i.test(desc)) {
|
|
416
|
-
inference.inferredAnswer = '
|
|
444
|
+
inference.inferredAnswer = 'testing';
|
|
417
445
|
inference.confidence = 'medium';
|
|
418
446
|
inference.reason = '任务描述包含测试关键词';
|
|
419
447
|
}
|
|
@@ -390,6 +390,8 @@ class TaskPlanner {
|
|
|
390
390
|
}
|
|
391
391
|
const coverageTarget = this.getCoverageTarget(qualityConfig, userContext);
|
|
392
392
|
const globalContext = this.buildGlobalContext(parsedTask, userContext, plan);
|
|
393
|
+
// 提取 planMetadata 用于测试任务描述注入
|
|
394
|
+
const planMetadata = this.extractPlanMetadata(plan);
|
|
393
395
|
// 1. 为每个模块创建开发 + 测试任务对
|
|
394
396
|
const devTaskIds = [];
|
|
395
397
|
const moduleIdToTaskIds = new Map();
|
|
@@ -434,7 +436,7 @@ class TaskPlanner {
|
|
|
434
436
|
breakdowns.push({
|
|
435
437
|
taskId: testTaskId,
|
|
436
438
|
title: `测试: ${mod.name}`,
|
|
437
|
-
description:
|
|
439
|
+
description: this.buildTestDescription(mod.name, modTaskId, coverageTarget, globalContext, planMetadata),
|
|
438
440
|
priority: this.determineModulePriority(mod, parsedPlan.modules.indexOf(mod)),
|
|
439
441
|
dependencies: [modTaskId],
|
|
440
442
|
estimatedComplexity: 'medium',
|
|
@@ -757,7 +759,7 @@ ${globalContext}
|
|
|
757
759
|
breakdowns.push({
|
|
758
760
|
taskId: testTaskId,
|
|
759
761
|
title: `测试: ${goal}`,
|
|
760
|
-
description: this.buildTestDescription(goal, devTaskId, coverageTarget, globalContext),
|
|
762
|
+
description: this.buildTestDescription(goal, devTaskId, coverageTarget, globalContext, planMetadata),
|
|
761
763
|
priority: this.determinePriority(i),
|
|
762
764
|
dependencies: [devTaskId],
|
|
763
765
|
estimatedComplexity: 'medium',
|
|
@@ -1104,16 +1106,25 @@ ${userContext.documentationLevel}
|
|
|
1104
1106
|
- 添加必要注释`;
|
|
1105
1107
|
return desc;
|
|
1106
1108
|
}
|
|
1107
|
-
buildTestDescription(goal, devTaskId, coverageTarget, globalContext) {
|
|
1108
|
-
|
|
1109
|
+
buildTestDescription(goal, devTaskId, coverageTarget, globalContext, planMetadata) {
|
|
1110
|
+
let desc = `## 测试目标
|
|
1109
1111
|
为 "${goal}" 编写测试用例
|
|
1110
1112
|
|
|
1111
1113
|
${globalContext}
|
|
1112
1114
|
|
|
1113
1115
|
## 关联开发任务
|
|
1114
|
-
${devTaskId}
|
|
1115
|
-
|
|
1116
|
-
|
|
1116
|
+
${devTaskId}`;
|
|
1117
|
+
// 注入 plan 提取的关键信息(参考 buildTaskDescription)
|
|
1118
|
+
if (planMetadata && planMetadata.interfaces.length > 0) {
|
|
1119
|
+
desc += `\n\n## 相关接口/API\n${planMetadata.interfaces.slice(0, 10).map(i => `- ${i}`).join('\n')}`;
|
|
1120
|
+
}
|
|
1121
|
+
if (planMetadata && planMetadata.dataModels.length > 0) {
|
|
1122
|
+
desc += `\n\n## 相关数据模型\n${planMetadata.dataModels.slice(0, 10).map(d => `- ${d}`).join('\n')}`;
|
|
1123
|
+
}
|
|
1124
|
+
if (planMetadata && planMetadata.keyDecisions.length > 0) {
|
|
1125
|
+
desc += `\n\n## 关键决策参考\n${planMetadata.keyDecisions.slice(0, 5).map(d => `- ${d}`).join('\n')}`;
|
|
1126
|
+
}
|
|
1127
|
+
desc += `\n\n## 测试要求
|
|
1117
1128
|
- 单元测试覆盖率 >= ${coverageTarget}%
|
|
1118
1129
|
- 测试正常流程
|
|
1119
1130
|
- 测试边界情况
|
|
@@ -1127,6 +1138,7 @@ ${devTaskId}
|
|
|
1127
1138
|
- 测试文件
|
|
1128
1139
|
- 测试报告
|
|
1129
1140
|
- 覆盖率报告`;
|
|
1141
|
+
return desc;
|
|
1130
1142
|
}
|
|
1131
1143
|
buildE2ETestDescription(e2eType, parsedTask, userContext) {
|
|
1132
1144
|
const typeConfig = this.getE2ETypeConfig(e2eType);
|