openmatrix 0.2.23 → 0.2.25
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/cli/commands/start.js +16 -15
- package/dist/orchestrator/task-planner.d.ts +5 -104
- package/dist/orchestrator/task-planner.js +99 -799
- package/dist/test/generator.js +273 -21
- package/dist/types/index.d.ts +3 -1
- package/package.json +61 -61
- package/skills/auto.md +383 -415
- package/skills/brainstorm.md +27 -29
- package/skills/plan.md +261 -0
- package/skills/start.md +552 -707
- package/skills/test.md +34 -5
|
@@ -5,14 +5,8 @@ const answer_mapper_js_1 = require("./answer-mapper.js");
|
|
|
5
5
|
/**
|
|
6
6
|
* TaskPlanner - 任务拆解器
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* 2. 测试任务配对 (每个开发任务自动生成对应测试任务)
|
|
11
|
-
* 3. 验收标准注入 (从用户回答中提取)
|
|
12
|
-
* 4. 用户上下文注入 (将用户回答注入任务描述)
|
|
13
|
-
* 5. 依赖关系分析 (自动分析任务间依赖)
|
|
14
|
-
* 6. 并行执行 (独立任务互不依赖)
|
|
15
|
-
* 7. 质量级别感知 (根据配置调整测试覆盖率)
|
|
8
|
+
* Plan 原文作为原始上下文透传给 AI Agent,由 AI 自行理解和提取
|
|
9
|
+
* 技术栈、数据模型、依赖关系等信息。CLI 层不做语义解析。
|
|
16
10
|
*/
|
|
17
11
|
class TaskPlanner {
|
|
18
12
|
userAnswers;
|
|
@@ -28,666 +22,21 @@ class TaskPlanner {
|
|
|
28
22
|
this.userAnswers = answers;
|
|
29
23
|
}
|
|
30
24
|
/**
|
|
31
|
-
*
|
|
25
|
+
* 任务拆解 — 唯一路径:按目标拆分,plan 作为原始上下文透传
|
|
32
26
|
*/
|
|
33
|
-
parsePlan(planText) {
|
|
34
|
-
const modules = [];
|
|
35
|
-
const techStack = [];
|
|
36
|
-
// 1. 提取技术栈
|
|
37
|
-
const techStackMatch = planText.match(/(?:技术栈|Technology)[\s\S]*?((?:- .+\n?)+)/i);
|
|
38
|
-
if (techStackMatch) {
|
|
39
|
-
techStackMatch[1].split('\n').forEach(line => {
|
|
40
|
-
const trimmed = line.replace(/^- /, '').trim();
|
|
41
|
-
if (trimmed)
|
|
42
|
-
techStack.push(trimmed);
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
// 2. 提取模块定义
|
|
46
|
-
// 模式1: "N领域模块: A、B、C..." 或 "N个领域模块: A, B, C"
|
|
47
|
-
const moduleListMatch = planText.match(/(\d+)\s*(?:个)?领域模块\s*[::]\s*(.+)/i);
|
|
48
|
-
if (moduleListMatch) {
|
|
49
|
-
const moduleNames = moduleListMatch[2]
|
|
50
|
-
.split(/[,,、]/)
|
|
51
|
-
.map(s => s.trim().replace(/域$/, ''))
|
|
52
|
-
.filter(s => s.length > 0 && s.length < 30);
|
|
53
|
-
// 从 plan 中的 "数据模型" 部分提取表信息
|
|
54
|
-
// 通用方式:查找以 "- " 开头的表名/实体名列表,不在模块列表中
|
|
55
|
-
const moduleNamesSet = new Set(moduleNames.map(n => n.toLowerCase()));
|
|
56
|
-
const allTables = [];
|
|
57
|
-
const tableSections = planText.match(/(?:数据模型|database|tables?|schema|实体|模型)[\s\S]*?((?:[-*]\s*.+(?:\n|$))+)/gi);
|
|
58
|
-
if (tableSections) {
|
|
59
|
-
for (const section of tableSections) {
|
|
60
|
-
const tableLines = section.split('\n')
|
|
61
|
-
.map(l => l.replace(/^[-*]\s*/, '').trim())
|
|
62
|
-
.filter(l => l.length > 0 && l.length < 50);
|
|
63
|
-
for (const t of tableLines) {
|
|
64
|
-
// 跳过看起来像模块名的条目
|
|
65
|
-
const tLower = t.toLowerCase();
|
|
66
|
-
if (!moduleNamesSet.has(tLower) && !t.includes(':') && !t.includes('—')) {
|
|
67
|
-
allTables.push(t);
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
for (const modName of moduleNames) {
|
|
73
|
-
// 通用方式:从 plan 中查找该模块相关的表(包含模块名的行附近)
|
|
74
|
-
const modLower = modName.toLowerCase();
|
|
75
|
-
const modTables = allTables.filter(t => t.toLowerCase().includes(modLower) ||
|
|
76
|
-
modLower.includes(t.split(/[_\s]/)[0]?.toLowerCase() || ''));
|
|
77
|
-
modules.push({
|
|
78
|
-
name: modName,
|
|
79
|
-
description: `${modName}模块`,
|
|
80
|
-
tables: modTables,
|
|
81
|
-
type: 'domain',
|
|
82
|
-
dependsOn: [],
|
|
83
|
-
complexity: this.estimateModuleComplexity(modName, modTables)
|
|
84
|
-
});
|
|
85
|
-
}
|
|
86
|
-
// 分析模块间依赖
|
|
87
|
-
this.analyzeModuleDependencies(modules, planText);
|
|
88
|
-
}
|
|
89
|
-
// 模式2: 架构设计部分的编号列表 "1. 用户域:描述"
|
|
90
|
-
if (modules.length === 0) {
|
|
91
|
-
const archMatch = planText.match(/架构设计[\s\S]*?((?:\d+\.\s*.+(?:\n|$))+)/i);
|
|
92
|
-
if (archMatch) {
|
|
93
|
-
const lines = archMatch[1].split('\n').filter(l => l.trim());
|
|
94
|
-
for (const line of lines) {
|
|
95
|
-
const modMatch = line.match(/\d+\.\s*(.+?)[::\s](.*)/);
|
|
96
|
-
if (modMatch) {
|
|
97
|
-
const name = modMatch[1].replace(/域$/, '').trim();
|
|
98
|
-
modules.push({
|
|
99
|
-
name,
|
|
100
|
-
description: modMatch[2].trim(),
|
|
101
|
-
tables: [],
|
|
102
|
-
type: 'domain',
|
|
103
|
-
dependsOn: [],
|
|
104
|
-
complexity: this.estimateModuleComplexity(name, [])
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
this.analyzeModuleDependencies(modules, planText);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
// 模式3: 英文格式 "N modules: A, B, C" 或 "N components: A, B, C"
|
|
112
|
-
if (modules.length === 0) {
|
|
113
|
-
const enModuleMatch = planText.match(/(\d+)\s*(?:modules?|components?|domains?|features?)\s*[::]\s*(.+)/i);
|
|
114
|
-
if (enModuleMatch) {
|
|
115
|
-
const moduleNames = enModuleMatch[2]
|
|
116
|
-
.split(/[,,、]/)
|
|
117
|
-
.map(s => s.trim())
|
|
118
|
-
.filter(s => s.length > 0 && s.length < 30);
|
|
119
|
-
for (const modName of moduleNames) {
|
|
120
|
-
modules.push({
|
|
121
|
-
name: modName,
|
|
122
|
-
description: `${modName} module`,
|
|
123
|
-
tables: [],
|
|
124
|
-
type: 'domain',
|
|
125
|
-
dependsOn: [],
|
|
126
|
-
complexity: this.estimateModuleComplexity(modName, [])
|
|
127
|
-
});
|
|
128
|
-
}
|
|
129
|
-
this.analyzeModuleDependencies(modules, planText);
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
// 模式4: Markdown 标题下的列表 "## Modules\n- A\n- B" 或 "## Architecture\n1. A\n2. B"
|
|
133
|
-
if (modules.length === 0) {
|
|
134
|
-
const mdPatterns = [
|
|
135
|
-
/##\s*(?:模块|Modules?|架构|Architecture|领域|Domains?|功能|Features?)[\s\S]*?((?:[-*\d]+\.\s*.+(?:\n|$))+)/i,
|
|
136
|
-
/##\s*(?:实现|Implementation|开发|Development)[\s\S]*?((?:[-*\d]+\.\s*.+(?:\n|$))+)/i,
|
|
137
|
-
];
|
|
138
|
-
for (const pattern of mdPatterns) {
|
|
139
|
-
const mdMatch = planText.match(pattern);
|
|
140
|
-
if (mdMatch) {
|
|
141
|
-
const lines = mdMatch[1].split('\n').filter(l => l.trim());
|
|
142
|
-
for (const line of lines) {
|
|
143
|
-
// 匹配 "- Name" 或 "1. Name" 或 "1. Name: Description"
|
|
144
|
-
const itemMatch = line.match(/[-*\d]+\.\s*(.+?)(?:[::\s](.*))?/);
|
|
145
|
-
if (itemMatch) {
|
|
146
|
-
const name = itemMatch[1].trim();
|
|
147
|
-
const desc = itemMatch[2]?.trim() || `${name} module`;
|
|
148
|
-
if (name.length > 0 && name.length < 30 && !modules.some(m => m.name === name)) {
|
|
149
|
-
modules.push({
|
|
150
|
-
name,
|
|
151
|
-
description: desc,
|
|
152
|
-
tables: [],
|
|
153
|
-
type: 'domain',
|
|
154
|
-
dependsOn: [],
|
|
155
|
-
complexity: this.estimateModuleComplexity(name, [])
|
|
156
|
-
});
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
if (modules.length > 0) {
|
|
162
|
-
this.analyzeModuleDependencies(modules, planText);
|
|
163
|
-
break;
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
return { modules, techStack, raw: planText };
|
|
168
|
-
}
|
|
169
|
-
/**
|
|
170
|
-
* 从 plan 中提取结构化信息(用于 fallback 时注入任务描述)
|
|
171
|
-
* 即使无法解析模块,也能保留关键技术信息
|
|
172
|
-
*/
|
|
173
|
-
extractPlanMetadata(plan) {
|
|
174
|
-
const techStack = [];
|
|
175
|
-
const interfaces = [];
|
|
176
|
-
const dataModels = [];
|
|
177
|
-
const keyDecisions = [];
|
|
178
|
-
// 1. 提取技术栈
|
|
179
|
-
const techMatch = plan.match(/(?:技术栈|Technology|Tech Stack)[\s\S]*?((?:[-*]\s*.+(?:\n|$))+)/i);
|
|
180
|
-
if (techMatch) {
|
|
181
|
-
techMatch[1].split('\n').forEach(line => {
|
|
182
|
-
const trimmed = line.replace(/^[-*]\s*/, '').trim();
|
|
183
|
-
if (trimmed && trimmed.length < 50)
|
|
184
|
-
techStack.push(trimmed);
|
|
185
|
-
});
|
|
186
|
-
}
|
|
187
|
-
// 2. 提取接口/API
|
|
188
|
-
const interfacePatterns = [
|
|
189
|
-
/(?:接口|API|Endpoints?|接口定义)[\s\S]*?((?:[-*]\s*.+(?:\n|$))+)/i,
|
|
190
|
-
/(?:##\s*(?:接口|API|Endpoints?))[\s\S]*?((?:[-*\d]+\.\s*.+(?:\n|$))+)/i,
|
|
191
|
-
];
|
|
192
|
-
for (const pattern of interfacePatterns) {
|
|
193
|
-
const match = plan.match(pattern);
|
|
194
|
-
if (match) {
|
|
195
|
-
match[1].split('\n').forEach(line => {
|
|
196
|
-
const trimmed = line.replace(/^[-*\d]+\.\s*/, '').trim();
|
|
197
|
-
if (trimmed && trimmed.length < 80 && !interfaces.includes(trimmed)) {
|
|
198
|
-
interfaces.push(trimmed);
|
|
199
|
-
}
|
|
200
|
-
});
|
|
201
|
-
if (interfaces.length > 0)
|
|
202
|
-
break;
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
// 3. 提取数据模型/表
|
|
206
|
-
const dataPatterns = [
|
|
207
|
-
/(?:数据模型|Database|Tables?|Schema|实体|模型)[\s\S]*?((?:[-*]\s*.+(?:\n|$))+)/i,
|
|
208
|
-
/(?:##\s*(?:数据模型|Database|实体))[\s\S]*?((?:[-*\d]+\.\s*.+(?:\n|$))+)/i,
|
|
209
|
-
];
|
|
210
|
-
for (const pattern of dataPatterns) {
|
|
211
|
-
const match = plan.match(pattern);
|
|
212
|
-
if (match) {
|
|
213
|
-
match[1].split('\n').forEach(line => {
|
|
214
|
-
const trimmed = line.replace(/^[-*\d]+\.\s*/, '').trim();
|
|
215
|
-
if (trimmed && trimmed.length < 50 && !dataModels.includes(trimmed)) {
|
|
216
|
-
dataModels.push(trimmed);
|
|
217
|
-
}
|
|
218
|
-
});
|
|
219
|
-
if (dataModels.length > 0)
|
|
220
|
-
break;
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
// 4. 提取关键决策
|
|
224
|
-
const decisionPatterns = [
|
|
225
|
-
/(?:关键决策|Key Decisions|重要决策|决策点)[\s\S]*?((?:[-*]\s*.+(?:\n|$))+)/i,
|
|
226
|
-
/(?:##\s*(?:关键决策|Key Decisions|决策))[\s\S]*?((?:[-*\d]+\.\s*.+(?:\n|$))+)/i,
|
|
227
|
-
];
|
|
228
|
-
for (const pattern of decisionPatterns) {
|
|
229
|
-
const match = plan.match(pattern);
|
|
230
|
-
if (match) {
|
|
231
|
-
match[1].split('\n').forEach(line => {
|
|
232
|
-
const trimmed = line.replace(/^[-*\d]+\.\s*/, '').trim();
|
|
233
|
-
if (trimmed && trimmed.length < 100 && !keyDecisions.includes(trimmed)) {
|
|
234
|
-
keyDecisions.push(trimmed);
|
|
235
|
-
}
|
|
236
|
-
});
|
|
237
|
-
if (keyDecisions.length > 0)
|
|
238
|
-
break;
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
return { techStack, interfaces, dataModels, keyDecisions };
|
|
242
|
-
}
|
|
243
|
-
/**
|
|
244
|
-
* 从 goals 推断模块结构(当 plan 无法解析模块时使用)
|
|
245
|
-
*/
|
|
246
|
-
inferModulesFromGoals(parsedTask) {
|
|
247
|
-
const modules = [];
|
|
248
|
-
if (!parsedTask.goalTypes || parsedTask.goals.length < 3) {
|
|
249
|
-
return modules;
|
|
250
|
-
}
|
|
251
|
-
for (let i = 0; i < parsedTask.goals.length; i++) {
|
|
252
|
-
const goal = parsedTask.goals[i];
|
|
253
|
-
const goalType = parsedTask.goalTypes[i];
|
|
254
|
-
// 只将 development 类型的 goal 作为模块
|
|
255
|
-
if (goalType === 'development') {
|
|
256
|
-
modules.push({
|
|
257
|
-
name: goal.replace(/^(实现|开发|完成|Develop|Implement)\s*:?\s*/i, '').trim(),
|
|
258
|
-
description: goal,
|
|
259
|
-
tables: [],
|
|
260
|
-
type: 'domain',
|
|
261
|
-
dependsOn: [],
|
|
262
|
-
complexity: this.estimateComplexity(goal)
|
|
263
|
-
});
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
// 添加基础依赖:第一个模块无依赖,后续模块依赖前一模块
|
|
267
|
-
for (let i = 1; i < modules.length; i++) {
|
|
268
|
-
// 默认并行,除非 goal 描述中明确提到依赖
|
|
269
|
-
if (parsedTask.goals[i].toLowerCase().includes('基于') ||
|
|
270
|
-
parsedTask.goals[i].toLowerCase().includes('依赖') ||
|
|
271
|
-
parsedTask.goals[i].toLowerCase().includes('需要') ||
|
|
272
|
-
parsedTask.goals[i].toLowerCase().includes('after')) {
|
|
273
|
-
modules[i].dependsOn.push(modules[i - 1].name);
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
return modules;
|
|
277
|
-
}
|
|
278
|
-
/**
|
|
279
|
-
* 分析模块间依赖关系
|
|
280
|
-
*
|
|
281
|
-
* 策略:从 plan 文本中提取 AI 明确写的依赖信息,不做架构猜测。
|
|
282
|
-
* 如果 plan 中没有指定依赖,模块之间并行执行。
|
|
283
|
-
*/
|
|
284
|
-
analyzeModuleDependencies(modules, planText) {
|
|
285
|
-
for (let i = 0; i < modules.length; i++) {
|
|
286
|
-
const mod = modules[i];
|
|
287
|
-
if (mod.dependsOn.length > 0)
|
|
288
|
-
continue; // parsePlan 中已提取的显式依赖,保留
|
|
289
|
-
// 从 plan 文本中查找该模块是否提到依赖其他模块
|
|
290
|
-
// 匹配模式:"X 依赖 Y", "X 基于 Y", "X 使用 Y", "after X", "depends on X"
|
|
291
|
-
const modContext = this.extractModuleContext(planText, mod.name);
|
|
292
|
-
if (modContext) {
|
|
293
|
-
for (const other of modules) {
|
|
294
|
-
if (other.name === mod.name)
|
|
295
|
-
continue;
|
|
296
|
-
// 如果上下文中提到其他模块且暗示依赖关系
|
|
297
|
-
const depPatterns = [
|
|
298
|
-
new RegExp(`${other.name}.*(?:依赖|基于|需要|使用|after|depends)`, 'i'),
|
|
299
|
-
new RegExp(`(?:依赖|基于|需要|使用|after|depends).*${other.name}`, 'i'),
|
|
300
|
-
];
|
|
301
|
-
for (const pattern of depPatterns) {
|
|
302
|
-
if (pattern.test(modContext)) {
|
|
303
|
-
mod.dependsOn.push(other.name);
|
|
304
|
-
break;
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
// 如果 plan 中模块是按顺序编号的,后面编号的模块依赖前面的
|
|
310
|
-
// 这只在模块名称是编号格式时适用(如 "1. 基础架构" → "2. 用户模块")
|
|
311
|
-
// 不做自动推断,保持模块间独立
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
/**
|
|
315
|
-
* 从 plan 文本中提取某个模块相关的上下文段落
|
|
316
|
-
*/
|
|
317
|
-
extractModuleContext(planText, moduleName) {
|
|
318
|
-
const lines = planText.split('\n');
|
|
319
|
-
const startIdx = lines.findIndex(l => l.includes(moduleName));
|
|
320
|
-
if (startIdx === -1)
|
|
321
|
-
return null;
|
|
322
|
-
// 从匹配行开始,收集到下一个编号标题或空行为止
|
|
323
|
-
const contextLines = [];
|
|
324
|
-
for (let i = startIdx; i < lines.length && i < startIdx + 20; i++) {
|
|
325
|
-
const line = lines[i];
|
|
326
|
-
if (contextLines.length > 0 && line.trim() === '')
|
|
327
|
-
break;
|
|
328
|
-
if (contextLines.length > 0 && /^\d+\./.test(line.trim()))
|
|
329
|
-
break;
|
|
330
|
-
contextLines.push(line);
|
|
331
|
-
}
|
|
332
|
-
return contextLines.join('\n');
|
|
333
|
-
}
|
|
334
|
-
/**
|
|
335
|
-
* 预估模块复杂度 — 基于通用架构特征,不依赖具体业务领域
|
|
336
|
-
*/
|
|
337
|
-
estimateModuleComplexity(name, tables) {
|
|
338
|
-
// 表数量是最直接的复杂度指标
|
|
339
|
-
if (tables.length >= 5)
|
|
340
|
-
return 'high';
|
|
341
|
-
if (tables.length >= 3)
|
|
342
|
-
return 'medium';
|
|
343
|
-
// 通用架构关键词
|
|
344
|
-
const highKws = ['核心', '基础', '架构', '主循环', '引擎', '框架', '平台', '系统', 'orchestrator', 'engine', 'core', 'framework'];
|
|
345
|
-
const mediumKws = ['管理', '服务', 'api', '接口', '控制器', '处理器', '管理器', 'manager', 'service', 'handler', 'processor', 'controller'];
|
|
346
|
-
const lowKws = ['工具', '脚本', '配置', '样式', '工具', 'helper', 'util', 'config', 'style', 'theme'];
|
|
347
|
-
const n = name.toLowerCase();
|
|
348
|
-
if (highKws.some(kw => n.includes(kw)))
|
|
349
|
-
return 'high';
|
|
350
|
-
if (lowKws.some(kw => n.includes(kw)))
|
|
351
|
-
return 'low';
|
|
352
|
-
if (mediumKws.some(kw => n.includes(kw)))
|
|
353
|
-
return 'medium';
|
|
354
|
-
// 默认 medium(比默认 low 更保守,避免并行执行冲突)
|
|
355
|
-
return 'medium';
|
|
356
|
-
}
|
|
357
27
|
breakdown(parsedTask, answers, qualityConfig, plan) {
|
|
358
|
-
|
|
359
|
-
if (plan) {
|
|
360
|
-
const parsed = this.parsePlan(plan);
|
|
361
|
-
if (parsed.modules.length > 0) {
|
|
362
|
-
return this.breakdownByModules(parsedTask, answers, qualityConfig, parsed, plan);
|
|
363
|
-
}
|
|
364
|
-
// plan 无法解析模块时,尝试从 goals 推断
|
|
365
|
-
const inferredModules = this.inferModulesFromGoals(parsedTask);
|
|
366
|
-
if (inferredModules.length > 0) {
|
|
367
|
-
const inferredPlan = {
|
|
368
|
-
modules: inferredModules,
|
|
369
|
-
techStack: parsed.techStack,
|
|
370
|
-
raw: plan
|
|
371
|
-
};
|
|
372
|
-
return this.breakdownByModules(parsedTask, answers, qualityConfig, inferredPlan, plan);
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
// fallback: 按目标拆分,但保留 plan metadata
|
|
376
|
-
const planMetadata = plan ? this.extractPlanMetadata(plan) : undefined;
|
|
377
|
-
return this.breakdownByGoals(parsedTask, answers, qualityConfig, plan, planMetadata);
|
|
378
|
-
}
|
|
379
|
-
/**
|
|
380
|
-
* 基于 plan 解析出的模块做细粒度任务拆分
|
|
381
|
-
*/
|
|
382
|
-
breakdownByModules(parsedTask, answers, qualityConfig, parsedPlan, plan) {
|
|
383
|
-
const breakdowns = [];
|
|
384
|
-
const userContext = this.extractUserContext(answers);
|
|
385
|
-
if (qualityConfig?.e2eTests) {
|
|
386
|
-
userContext.e2eTests = true;
|
|
387
|
-
if (!userContext.e2eType) {
|
|
388
|
-
userContext.e2eType = 'web';
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
const coverageTarget = this.getCoverageTarget(qualityConfig, userContext);
|
|
392
|
-
const globalContext = this.buildGlobalContext(parsedTask, userContext, plan);
|
|
393
|
-
// 提取 planMetadata 用于测试任务描述注入
|
|
394
|
-
const planMetadata = this.extractPlanMetadata(plan);
|
|
395
|
-
// 1. 为每个模块创建开发 + 测试任务对
|
|
396
|
-
const devTaskIds = [];
|
|
397
|
-
const moduleIdToTaskIds = new Map();
|
|
398
|
-
for (const mod of parsedPlan.modules) {
|
|
399
|
-
const modTaskId = this.generateTaskId();
|
|
400
|
-
devTaskIds.push(modTaskId);
|
|
401
|
-
moduleIdToTaskIds.set(mod.name, [modTaskId]);
|
|
402
|
-
// 构建模块描述 — 包含表名、依赖等具体信息
|
|
403
|
-
let modDescription = `## 模块实现: ${mod.name}\n\n${mod.description}\n\n${globalContext}`;
|
|
404
|
-
if (mod.tables.length > 0) {
|
|
405
|
-
modDescription += `\n\n## 数据模型\n需要实现以下数据库表:\n${mod.tables.map(t => `- \`${t}\``).join('\n')}`;
|
|
406
|
-
}
|
|
407
|
-
if (mod.dependsOn.length > 0) {
|
|
408
|
-
modDescription += `\n\n## 模块依赖\n本模块依赖以下模块: ${mod.dependsOn.join(', ')}\n请确保依赖模块的接口已定义并可调用。`;
|
|
409
|
-
}
|
|
410
|
-
modDescription += `\n\n## 输出要求\n- 完成模块实现\n- 代码可编译\n- 遵循项目规范\n- 添加必要注释`;
|
|
411
|
-
// 计算模块任务的实际依赖(转换为 taskId)
|
|
412
|
-
const modDeps = [];
|
|
413
|
-
for (const depName of mod.dependsOn) {
|
|
414
|
-
const depTaskIds = moduleIdToTaskIds.get(depName);
|
|
415
|
-
if (depTaskIds) {
|
|
416
|
-
modDeps.push(...depTaskIds);
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
// 同时保留 phase 级别的依赖
|
|
420
|
-
this.enforcePhaseDependenciesForModule(breakdowns, parsedTask, mod, modDeps);
|
|
421
|
-
const complexity = this.estimateModuleComplexity(mod.name, mod.tables);
|
|
422
|
-
breakdowns.push({
|
|
423
|
-
taskId: modTaskId,
|
|
424
|
-
title: `实现: ${mod.name}`,
|
|
425
|
-
description: modDescription,
|
|
426
|
-
priority: this.determineModulePriority(mod, parsedPlan.modules.indexOf(mod)),
|
|
427
|
-
dependencies: modDeps,
|
|
428
|
-
estimatedComplexity: complexity,
|
|
429
|
-
assignedAgent: 'coder',
|
|
430
|
-
phase: 'develop',
|
|
431
|
-
acceptanceCriteria: this.generateModuleAcceptanceCriteria(mod, coverageTarget, userContext),
|
|
432
|
-
testTaskId: undefined
|
|
433
|
-
});
|
|
434
|
-
// 配对的测试任务
|
|
435
|
-
const testTaskId = this.generateTaskId();
|
|
436
|
-
breakdowns.push({
|
|
437
|
-
taskId: testTaskId,
|
|
438
|
-
title: `测试: ${mod.name}`,
|
|
439
|
-
description: this.buildTestDescription(mod.name, modTaskId, coverageTarget, globalContext, planMetadata),
|
|
440
|
-
priority: this.determineModulePriority(mod, parsedPlan.modules.indexOf(mod)),
|
|
441
|
-
dependencies: [modTaskId],
|
|
442
|
-
estimatedComplexity: 'medium',
|
|
443
|
-
assignedAgent: 'tester',
|
|
444
|
-
phase: 'verify',
|
|
445
|
-
acceptanceCriteria: [
|
|
446
|
-
`单元测试覆盖率 >= ${coverageTarget}%`,
|
|
447
|
-
'边界情况已测试',
|
|
448
|
-
'异常处理已验证',
|
|
449
|
-
'所有测试通过'
|
|
450
|
-
]
|
|
451
|
-
});
|
|
452
|
-
breakdowns[breakdowns.length - 2].testTaskId = testTaskId;
|
|
453
|
-
moduleIdToTaskIds.get(mod.name).push(testTaskId);
|
|
454
|
-
}
|
|
455
|
-
// 2. 系统集成任务
|
|
456
|
-
let integrationTaskId;
|
|
457
|
-
if (devTaskIds.length > 1) {
|
|
458
|
-
integrationTaskId = this.generateTaskId();
|
|
459
|
-
breakdowns.push({
|
|
460
|
-
taskId: integrationTaskId,
|
|
461
|
-
title: '系统集成: 将所有模块组装到主入口,确保可运行',
|
|
462
|
-
description: `将前面所有模块连接在一起,使应用可以完整运行
|
|
463
|
-
|
|
464
|
-
${globalContext}
|
|
465
|
-
|
|
466
|
-
## 集成要求
|
|
467
|
-
- 确定项目的主入口文件并实例化所有核心模块
|
|
468
|
-
- 建立模块间通信和数据流
|
|
469
|
-
- 确保应用可以启动并正常运行
|
|
470
|
-
|
|
471
|
-
## 已完成的模块
|
|
472
|
-
${parsedPlan.modules.map(m => `- ${m.name} (${m.tables.length > 0 ? '表: ' + m.tables.join(', ') : '无数据模型'})`).join('\n')}
|
|
473
|
-
|
|
474
|
-
## 输出
|
|
475
|
-
- 更新后的主入口文件
|
|
476
|
-
- 模块连接正确,应用可运行
|
|
477
|
-
- 无运行时错误`,
|
|
478
|
-
priority: 'P0',
|
|
479
|
-
dependencies: [...devTaskIds],
|
|
480
|
-
estimatedComplexity: 'high',
|
|
481
|
-
assignedAgent: 'coder',
|
|
482
|
-
phase: 'develop',
|
|
483
|
-
acceptanceCriteria: [
|
|
484
|
-
'主入口文件已更新',
|
|
485
|
-
'所有核心模块已实例化并连接',
|
|
486
|
-
'应用可以正常启动',
|
|
487
|
-
'无运行时错误',
|
|
488
|
-
'模块间通信正常'
|
|
489
|
-
]
|
|
490
|
-
});
|
|
491
|
-
}
|
|
492
|
-
// 3. 代码审查任务
|
|
493
|
-
if (devTaskIds.length > 0) {
|
|
494
|
-
const reviewDeps = integrationTaskId
|
|
495
|
-
? [...devTaskIds, integrationTaskId]
|
|
496
|
-
: [...devTaskIds];
|
|
497
|
-
breakdowns.push({
|
|
498
|
-
taskId: this.generateTaskId(),
|
|
499
|
-
title: '代码审查',
|
|
500
|
-
description: `对所有开发任务进行代码审查
|
|
501
|
-
|
|
502
|
-
${globalContext}
|
|
503
|
-
|
|
504
|
-
## 审查范围
|
|
505
|
-
${parsedPlan.modules.map(m => `- ${m.name}: ${m.description}`).join('\n')}
|
|
506
|
-
|
|
507
|
-
## 审查要点
|
|
508
|
-
- 代码质量
|
|
509
|
-
- 安全性
|
|
510
|
-
- 性能
|
|
511
|
-
- 最佳实践`,
|
|
512
|
-
priority: 'P1',
|
|
513
|
-
dependencies: reviewDeps,
|
|
514
|
-
estimatedComplexity: 'medium',
|
|
515
|
-
assignedAgent: 'reviewer',
|
|
516
|
-
phase: 'verify',
|
|
517
|
-
acceptanceCriteria: [
|
|
518
|
-
'无严重代码问题',
|
|
519
|
-
'无安全隐患',
|
|
520
|
-
'代码符合规范',
|
|
521
|
-
'审查报告已生成'
|
|
522
|
-
]
|
|
523
|
-
});
|
|
524
|
-
}
|
|
525
|
-
// 4. 集成测试任务 (如果有多个交付物)
|
|
526
|
-
if (parsedTask.deliverables.length > 1) {
|
|
527
|
-
const integrationTestDeps = integrationTaskId
|
|
528
|
-
? [...devTaskIds, integrationTaskId]
|
|
529
|
-
: [...devTaskIds];
|
|
530
|
-
breakdowns.push({
|
|
531
|
-
taskId: this.generateTaskId(),
|
|
532
|
-
title: '集成测试',
|
|
533
|
-
description: `验证所有交付物正确集成
|
|
534
|
-
|
|
535
|
-
${globalContext}
|
|
536
|
-
|
|
537
|
-
## 测试范围
|
|
538
|
-
- 模块间接口
|
|
539
|
-
- 端到端流程
|
|
540
|
-
- 数据流验证`,
|
|
541
|
-
priority: 'P1',
|
|
542
|
-
dependencies: integrationTestDeps,
|
|
543
|
-
estimatedComplexity: 'medium',
|
|
544
|
-
assignedAgent: 'tester',
|
|
545
|
-
phase: 'verify',
|
|
546
|
-
acceptanceCriteria: [
|
|
547
|
-
'所有模块正确集成',
|
|
548
|
-
'端到端流程通过',
|
|
549
|
-
'接口兼容性验证',
|
|
550
|
-
'集成测试报告完整'
|
|
551
|
-
]
|
|
552
|
-
});
|
|
553
|
-
}
|
|
554
|
-
// 5. E2E 测试任务
|
|
555
|
-
if (userContext.e2eTests) {
|
|
556
|
-
const e2eTaskId = this.generateTaskId();
|
|
557
|
-
const e2eType = userContext.e2eType || 'web';
|
|
558
|
-
const allTestDeps = [...devTaskIds];
|
|
559
|
-
breakdowns.forEach(b => {
|
|
560
|
-
if (b.phase === 'verify' && b.title.startsWith('测试:')) {
|
|
561
|
-
allTestDeps.push(b.taskId);
|
|
562
|
-
}
|
|
563
|
-
});
|
|
564
|
-
breakdowns.push({
|
|
565
|
-
taskId: e2eTaskId,
|
|
566
|
-
title: '端到端(E2E)测试',
|
|
567
|
-
description: this.buildE2ETestDescription(e2eType, parsedTask, userContext),
|
|
568
|
-
priority: 'P0',
|
|
569
|
-
dependencies: allTestDeps,
|
|
570
|
-
estimatedComplexity: 'high',
|
|
571
|
-
assignedAgent: 'tester',
|
|
572
|
-
phase: 'verify',
|
|
573
|
-
acceptanceCriteria: [
|
|
574
|
-
'所有 E2E 测试用例通过',
|
|
575
|
-
'关键用户流程验证完成',
|
|
576
|
-
'跨浏览器/设备兼容性验证',
|
|
577
|
-
'E2E 测试报告已生成',
|
|
578
|
-
'无阻塞级别的缺陷'
|
|
579
|
-
]
|
|
580
|
-
});
|
|
581
|
-
}
|
|
582
|
-
// 6. 文档任务
|
|
583
|
-
if (userContext.documentationLevel && userContext.documentationLevel !== '无需文档') {
|
|
584
|
-
breakdowns.push({
|
|
585
|
-
taskId: this.generateTaskId(),
|
|
586
|
-
title: '文档编写',
|
|
587
|
-
description: `编写项目文档
|
|
588
|
-
|
|
589
|
-
## 文档级别
|
|
590
|
-
${userContext.documentationLevel}
|
|
591
|
-
|
|
592
|
-
## 文档内容
|
|
593
|
-
- README 更新
|
|
594
|
-
- API 文档
|
|
595
|
-
- 使用说明`,
|
|
596
|
-
priority: 'P2',
|
|
597
|
-
dependencies: devTaskIds,
|
|
598
|
-
estimatedComplexity: 'low',
|
|
599
|
-
assignedAgent: 'executor',
|
|
600
|
-
phase: 'accept',
|
|
601
|
-
acceptanceCriteria: [
|
|
602
|
-
'README 已更新',
|
|
603
|
-
'API 文档完整',
|
|
604
|
-
'使用说明清晰'
|
|
605
|
-
]
|
|
606
|
-
});
|
|
607
|
-
}
|
|
608
|
-
return breakdowns;
|
|
609
|
-
}
|
|
610
|
-
/**
|
|
611
|
-
* 为模块任务添加 phase 级别的跨阶段依赖
|
|
612
|
-
*/
|
|
613
|
-
enforcePhaseDependenciesForModule(breakdowns, parsedTask, mod, modDeps) {
|
|
614
|
-
// 检测是否有顺序阶段,如果有则添加跨阶段依赖
|
|
615
|
-
const phaseIndices = this.detectSequentialPhases(parsedTask.goals);
|
|
616
|
-
if (phaseIndices.length < 2)
|
|
617
|
-
return;
|
|
618
|
-
// 找出当前模块属于哪个 phase goal
|
|
619
|
-
let currentPhaseIndex = -1;
|
|
620
|
-
for (let pi = 0; pi < phaseIndices.length; pi++) {
|
|
621
|
-
const goalIndex = phaseIndices[pi];
|
|
622
|
-
const goal = parsedTask.goals[goalIndex];
|
|
623
|
-
if (mod.name.toLowerCase().includes(goal.toLowerCase().slice(0, 10)) ||
|
|
624
|
-
goal.toLowerCase().includes(mod.name.toLowerCase().slice(0, 6))) {
|
|
625
|
-
currentPhaseIndex = pi;
|
|
626
|
-
break;
|
|
627
|
-
}
|
|
628
|
-
}
|
|
629
|
-
if (currentPhaseIndex <= 0)
|
|
630
|
-
return;
|
|
631
|
-
// 添加对前一阶段所有模块任务的依赖
|
|
632
|
-
const prevGoalIndex = phaseIndices[currentPhaseIndex - 1];
|
|
633
|
-
for (const b of breakdowns) {
|
|
634
|
-
if (b.phase === 'develop' && b.title.startsWith('实现: ')) {
|
|
635
|
-
// 检查这个任务是否属于前一阶段
|
|
636
|
-
const prevGoal = parsedTask.goals[prevGoalIndex];
|
|
637
|
-
if (b.description.includes(prevGoal)) {
|
|
638
|
-
if (!modDeps.includes(b.taskId)) {
|
|
639
|
-
modDeps.push(b.taskId);
|
|
640
|
-
}
|
|
641
|
-
}
|
|
642
|
-
}
|
|
643
|
-
}
|
|
644
|
-
}
|
|
645
|
-
/**
|
|
646
|
-
* 确定模块任务优先级
|
|
647
|
-
*/
|
|
648
|
-
determineModulePriority(mod, index) {
|
|
649
|
-
// 基础设施模块优先级更高
|
|
650
|
-
if (mod.type === 'infra')
|
|
651
|
-
return 'P0';
|
|
652
|
-
// 有依赖的模块通常更核心
|
|
653
|
-
if (mod.dependsOn.length === 0 && index === 0)
|
|
654
|
-
return 'P0';
|
|
655
|
-
if (mod.complexity === 'high')
|
|
656
|
-
return 'P1';
|
|
657
|
-
return 'P2';
|
|
28
|
+
return this.breakdownByGoals(parsedTask, answers, qualityConfig, plan);
|
|
658
29
|
}
|
|
659
30
|
/**
|
|
660
|
-
*
|
|
31
|
+
* 按目标拆分子任务
|
|
661
32
|
*/
|
|
662
|
-
|
|
663
|
-
const criteria = [
|
|
664
|
-
`模块 "${mod.name}" 功能已实现`,
|
|
665
|
-
'代码可编译,无错误',
|
|
666
|
-
'代码符合项目规范'
|
|
667
|
-
];
|
|
668
|
-
if (mod.tables.length > 0) {
|
|
669
|
-
criteria.push(`数据库表 ${mod.tables.join(', ')} 已实现并可访问`);
|
|
670
|
-
}
|
|
671
|
-
criteria.push(`测试覆盖率 >= ${coverageTarget}%`);
|
|
672
|
-
criteria.push('必要的注释已添加');
|
|
673
|
-
criteria.push('无安全隐患');
|
|
674
|
-
criteria.push('边界情况已处理');
|
|
675
|
-
if (userContext.techStack?.length) {
|
|
676
|
-
criteria.push(`使用指定技术栈: ${userContext.techStack.join(', ')}`);
|
|
677
|
-
}
|
|
678
|
-
return criteria;
|
|
679
|
-
}
|
|
680
|
-
/**
|
|
681
|
-
* 按目标拆分的传统方式(fallback)
|
|
682
|
-
*/
|
|
683
|
-
breakdownByGoals(parsedTask, answers, qualityConfig, plan, planMetadata) {
|
|
33
|
+
breakdownByGoals(parsedTask, answers, qualityConfig, plan) {
|
|
684
34
|
const breakdowns = [];
|
|
685
35
|
const seenTitles = new Set();
|
|
686
36
|
const userContext = this.extractUserContext(answers);
|
|
687
37
|
// qualityConfig 中的 e2eTests 优先级高于 answers 中的推断
|
|
688
38
|
if (qualityConfig?.e2eTests) {
|
|
689
39
|
userContext.e2eTests = true;
|
|
690
|
-
// 如果 e2eType 未指定,从质量配置推断
|
|
691
40
|
if (!userContext.e2eType) {
|
|
692
41
|
userContext.e2eType = 'web';
|
|
693
42
|
}
|
|
@@ -729,13 +78,11 @@ ${globalContext}
|
|
|
729
78
|
const devTaskIds = [];
|
|
730
79
|
for (let i = 0; i < parsedTask.goals.length; i++) {
|
|
731
80
|
const goal = parsedTask.goals[i];
|
|
732
|
-
|
|
733
|
-
if (seenTitles.has(goal)) {
|
|
81
|
+
if (seenTitles.has(goal))
|
|
734
82
|
continue;
|
|
735
|
-
}
|
|
736
83
|
seenTitles.add(goal);
|
|
737
|
-
//
|
|
738
|
-
const goalType = parsedTask.goalTypes?.[i] ??
|
|
84
|
+
// goalTypes 由 AI 在 tasks-input.json 中必填
|
|
85
|
+
const goalType = parsedTask.goalTypes?.[i] ?? 'development';
|
|
739
86
|
const deps = designTaskId ? [designTaskId] : [];
|
|
740
87
|
if (goalType === 'development') {
|
|
741
88
|
// 开发类目标: 拆分为实现 + 测试 对
|
|
@@ -745,10 +92,10 @@ ${globalContext}
|
|
|
745
92
|
breakdowns.push({
|
|
746
93
|
taskId: devTaskId,
|
|
747
94
|
title: `实现: ${goal}`,
|
|
748
|
-
description: this.buildTaskDescription(goal, globalContext
|
|
95
|
+
description: this.buildTaskDescription(goal, globalContext),
|
|
749
96
|
priority: this.determinePriority(i),
|
|
750
97
|
dependencies: deps,
|
|
751
|
-
estimatedComplexity: this.estimateComplexity(goal),
|
|
98
|
+
estimatedComplexity: this.estimateComplexity(goal, i, parsedTask),
|
|
752
99
|
assignedAgent: 'coder',
|
|
753
100
|
phase: 'develop',
|
|
754
101
|
acceptanceCriteria,
|
|
@@ -759,7 +106,7 @@ ${globalContext}
|
|
|
759
106
|
breakdowns.push({
|
|
760
107
|
taskId: testTaskId,
|
|
761
108
|
title: `测试: ${goal}`,
|
|
762
|
-
description: this.buildTestDescription(goal, devTaskId, coverageTarget, globalContext
|
|
109
|
+
description: this.buildTestDescription(goal, devTaskId, coverageTarget, globalContext),
|
|
763
110
|
priority: this.determinePriority(i),
|
|
764
111
|
dependencies: [devTaskId],
|
|
765
112
|
estimatedComplexity: 'medium',
|
|
@@ -780,7 +127,21 @@ ${globalContext}
|
|
|
780
127
|
breakdowns.push({
|
|
781
128
|
taskId,
|
|
782
129
|
title: goal,
|
|
783
|
-
description: `##
|
|
130
|
+
description: `## 测试目标
|
|
131
|
+
${goal}
|
|
132
|
+
|
|
133
|
+
${globalContext}
|
|
134
|
+
|
|
135
|
+
## 测试要求
|
|
136
|
+
- 单元测试覆盖率 >= ${coverageTarget}%
|
|
137
|
+
- 测试正常流程
|
|
138
|
+
- 测试边界情况
|
|
139
|
+
- 测试异常处理
|
|
140
|
+
|
|
141
|
+
## 输出
|
|
142
|
+
- 测试文件
|
|
143
|
+
- 测试报告
|
|
144
|
+
- 覆盖率报告`,
|
|
784
145
|
priority: this.determinePriority(i),
|
|
785
146
|
dependencies: deps,
|
|
786
147
|
estimatedComplexity: 'medium',
|
|
@@ -800,7 +161,15 @@ ${globalContext}
|
|
|
800
161
|
breakdowns.push({
|
|
801
162
|
taskId,
|
|
802
163
|
title: goal,
|
|
803
|
-
description: `##
|
|
164
|
+
description: `## 文档目标
|
|
165
|
+
${goal}
|
|
166
|
+
|
|
167
|
+
${globalContext}
|
|
168
|
+
|
|
169
|
+
## 输出要求
|
|
170
|
+
- 文档内容完整
|
|
171
|
+
- 格式清晰
|
|
172
|
+
- 示例代码可运行`,
|
|
804
173
|
priority: this.determinePriority(i),
|
|
805
174
|
dependencies: deps,
|
|
806
175
|
estimatedComplexity: 'low',
|
|
@@ -819,10 +188,17 @@ ${globalContext}
|
|
|
819
188
|
breakdowns.push({
|
|
820
189
|
taskId,
|
|
821
190
|
title: goal,
|
|
822
|
-
description: `##
|
|
191
|
+
description: `## 目标
|
|
192
|
+
${goal}
|
|
193
|
+
|
|
194
|
+
${globalContext}
|
|
195
|
+
|
|
196
|
+
## 输出要求
|
|
197
|
+
- 完成目标
|
|
198
|
+
- 验证结果正确`,
|
|
823
199
|
priority: this.determinePriority(i),
|
|
824
200
|
dependencies: deps,
|
|
825
|
-
estimatedComplexity: this.estimateComplexity(goal),
|
|
201
|
+
estimatedComplexity: this.estimateComplexity(goal, i, parsedTask),
|
|
826
202
|
assignedAgent: 'coder',
|
|
827
203
|
phase: 'develop',
|
|
828
204
|
acceptanceCriteria: [
|
|
@@ -872,7 +248,6 @@ ${devTaskIds.map(id => `- ${id}: ${breakdowns.find(b => b.taskId === id)?.title
|
|
|
872
248
|
}
|
|
873
249
|
// 3. 代码审查任务 (仅在有开发任务时创建)
|
|
874
250
|
if (devTaskIds.length > 0) {
|
|
875
|
-
// 审查依赖所有开发任务 + 系统集成任务(如果有)
|
|
876
251
|
const reviewDeps = integrationTaskId
|
|
877
252
|
? [...devTaskIds, integrationTaskId]
|
|
878
253
|
: [...devTaskIds];
|
|
@@ -904,7 +279,7 @@ ${devTaskIds.map(id => `- ${id}`).join('\n')}
|
|
|
904
279
|
]
|
|
905
280
|
});
|
|
906
281
|
}
|
|
907
|
-
//
|
|
282
|
+
// 4. 集成测试任务 (如果有多个交付物)
|
|
908
283
|
if (parsedTask.deliverables.length > 1) {
|
|
909
284
|
const integrationTestDeps = integrationTaskId
|
|
910
285
|
? [...devTaskIds, integrationTaskId]
|
|
@@ -933,7 +308,7 @@ ${globalContext}
|
|
|
933
308
|
]
|
|
934
309
|
});
|
|
935
310
|
}
|
|
936
|
-
//
|
|
311
|
+
// 5. E2E 测试任务 (如果启用)
|
|
937
312
|
if (userContext.e2eTests) {
|
|
938
313
|
const e2eTaskId = this.generateTaskId();
|
|
939
314
|
const e2eType = userContext.e2eType || 'web';
|
|
@@ -961,8 +336,8 @@ ${globalContext}
|
|
|
961
336
|
]
|
|
962
337
|
});
|
|
963
338
|
}
|
|
964
|
-
//
|
|
965
|
-
if (userContext.documentationLevel && userContext.documentationLevel !== '
|
|
339
|
+
// 6. 文档任务 (如果需要)
|
|
340
|
+
if (userContext.documentationLevel && userContext.documentationLevel !== '无需') {
|
|
966
341
|
breakdowns.push({
|
|
967
342
|
taskId: this.generateTaskId(),
|
|
968
343
|
title: '文档编写',
|
|
@@ -987,26 +362,12 @@ ${userContext.documentationLevel}
|
|
|
987
362
|
]
|
|
988
363
|
});
|
|
989
364
|
}
|
|
990
|
-
//
|
|
365
|
+
// 7. 检测顺序阶段并建立跨阶段依赖(Phase 1 → Phase 2 → Phase 3 → ...)
|
|
991
366
|
this.enforcePhaseDependencies(breakdowns, parsedTask);
|
|
992
367
|
return breakdowns;
|
|
993
368
|
}
|
|
994
|
-
/**
|
|
995
|
-
* 判断是否需要设计阶段
|
|
996
|
-
*
|
|
997
|
-
* 条件: 多个 goal,或 goal 包含复杂关键词
|
|
998
|
-
*/
|
|
999
369
|
needsDesignPhase(parsedTask) {
|
|
1000
|
-
|
|
1001
|
-
return true;
|
|
1002
|
-
// 单 goal 但包含复杂关键词
|
|
1003
|
-
const complexKeywords = [
|
|
1004
|
-
'系统', '架构', '模块', '集成', '完整', '平台',
|
|
1005
|
-
'体系', '框架', '全栈', '端到端', '多个', '一系列',
|
|
1006
|
-
'system', 'architecture', 'framework', 'fullstack', 'integration'
|
|
1007
|
-
];
|
|
1008
|
-
const allText = `${parsedTask.title} ${parsedTask.goals.join(' ')} ${parsedTask.description}`.toLowerCase();
|
|
1009
|
-
return complexKeywords.some(kw => allText.includes(kw.toLowerCase()));
|
|
370
|
+
return parsedTask.goals.length > 1;
|
|
1010
371
|
}
|
|
1011
372
|
/**
|
|
1012
373
|
* 获取测试覆盖率目标
|
|
@@ -1026,23 +387,24 @@ ${userContext.documentationLevel}
|
|
|
1026
387
|
return 60;
|
|
1027
388
|
}
|
|
1028
389
|
/**
|
|
1029
|
-
* 提取用户上下文
|
|
390
|
+
* 提取用户上下文 — 使用 translateBrainstormAnswers 映射后的规范化键
|
|
1030
391
|
*/
|
|
1031
392
|
extractUserContext(answers) {
|
|
1032
|
-
// 先翻译 brainstorm 规范键为 planner 期望的键
|
|
1033
393
|
const translated = (0, answer_mapper_js_1.translateBrainstormAnswers)(answers);
|
|
1034
394
|
const merged = { ...answers, ...translated };
|
|
1035
|
-
// 辅助函数:提取字符串值(处理 string[] 情况)
|
|
1036
395
|
const str = (v) => Array.isArray(v) ? v.join(', ') : v;
|
|
1037
|
-
const e2eAnswer = str(merged['
|
|
1038
|
-
const e2eTypeAnswer = str(merged['
|
|
1039
|
-
const isE2EEnabled = e2eAnswer === '
|
|
1040
|
-
|
|
396
|
+
const e2eAnswer = str(merged['e2e_tests'] || merged['e2eTests']);
|
|
397
|
+
const e2eTypeAnswer = str(merged['e2e_type'] || merged['e2eType']);
|
|
398
|
+
const isE2EEnabled = e2eAnswer === 'functional' || e2eAnswer === 'visual' || e2eAnswer === 'true';
|
|
399
|
+
const e2eTypeValue = e2eAnswer === 'visual' ? 'visual' :
|
|
400
|
+
e2eTypeAnswer === 'visual' ? 'visual' :
|
|
401
|
+
e2eTypeAnswer === 'mobile' ? 'mobile' :
|
|
402
|
+
e2eTypeAnswer === 'gui' ? 'gui' : 'web';
|
|
1041
403
|
return {
|
|
1042
|
-
objective: str(merged['
|
|
1043
|
-
techStack: this.parseArrayAnswer(str(merged['
|
|
1044
|
-
testCoverage: str(merged['
|
|
1045
|
-
documentationLevel: str(merged['
|
|
404
|
+
objective: str(merged['objective']),
|
|
405
|
+
techStack: this.parseArrayAnswer(str(merged['tech_stack'] || merged['techStack'])),
|
|
406
|
+
testCoverage: str(merged['test_coverage'] || merged['testCoverage']),
|
|
407
|
+
documentationLevel: str(merged['documentation_level'] || merged['documentationLevel']),
|
|
1046
408
|
e2eTests: isE2EEnabled,
|
|
1047
409
|
e2eType: e2eTypeValue,
|
|
1048
410
|
additionalContext: merged
|
|
@@ -1059,72 +421,66 @@ ${userContext.documentationLevel}
|
|
|
1059
421
|
buildGlobalContext(parsedTask, userContext, plan) {
|
|
1060
422
|
const parts = [];
|
|
1061
423
|
if (parsedTask.title) {
|
|
1062
|
-
parts.push(`##
|
|
424
|
+
parts.push(`## 整体任务
|
|
425
|
+
${parsedTask.title}`);
|
|
1063
426
|
}
|
|
1064
427
|
if (parsedTask.description) {
|
|
1065
|
-
parts.push(`##
|
|
428
|
+
parts.push(`## 任务描述
|
|
429
|
+
${parsedTask.description}`);
|
|
1066
430
|
}
|
|
431
|
+
// Plan 原文完整透传,由 AI Agent 自行理解提取
|
|
1067
432
|
if (plan) {
|
|
1068
|
-
parts.push(`##
|
|
433
|
+
parts.push(`## 执行计划
|
|
434
|
+
${plan}`);
|
|
1069
435
|
}
|
|
1070
436
|
if (parsedTask.goals.length > 0) {
|
|
1071
|
-
parts.push(`##
|
|
437
|
+
parts.push(`## 所有目标
|
|
438
|
+
${parsedTask.goals.map((g, i) => `${i + 1}. ${g}`).join('\n')}`);
|
|
1072
439
|
}
|
|
1073
440
|
if (parsedTask.constraints.length > 0) {
|
|
1074
|
-
parts.push(`##
|
|
441
|
+
parts.push(`## 约束条件
|
|
442
|
+
${parsedTask.constraints.map(c => `- ${c}`).join('\n')}`);
|
|
1075
443
|
}
|
|
1076
444
|
if (parsedTask.deliverables.length > 0) {
|
|
1077
|
-
parts.push(`##
|
|
445
|
+
parts.push(`## 交付物
|
|
446
|
+
${parsedTask.deliverables.map(d => `- ${d}`).join('\n')}`);
|
|
1078
447
|
}
|
|
1079
448
|
if (userContext.techStack && userContext.techStack.length > 0) {
|
|
1080
|
-
parts.push(`##
|
|
449
|
+
parts.push(`## 技术栈
|
|
450
|
+
${userContext.techStack.map(t => `- ${t}`).join('\n')}`);
|
|
1081
451
|
}
|
|
1082
452
|
if (userContext.additionalContext) {
|
|
1083
|
-
const
|
|
453
|
+
const skipKeys = new Set(['objective', 'tech_stack', 'test_coverage', 'documentation_level', 'e2e_tests', 'e2e_type', 'quality_level', 'execution_mode']);
|
|
454
|
+
const relevantAnswers = Object.entries(userContext.additionalContext).filter(([key]) => !skipKeys.has(key));
|
|
1084
455
|
if (relevantAnswers.length > 0) {
|
|
1085
|
-
parts.push(`##
|
|
456
|
+
parts.push(`## 其他要求
|
|
457
|
+
${relevantAnswers.map(([k, v]) => `- ${k}: ${v}`).join('\n')}`);
|
|
1086
458
|
}
|
|
1087
459
|
}
|
|
1088
460
|
return parts.join('\n');
|
|
1089
461
|
}
|
|
1090
|
-
buildTaskDescription(goal, globalContext
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
desc += `\n\n## 相关数据模型\n${planMetadata.dataModels.slice(0, 10).map(d => `- ${d}`).join('\n')}`;
|
|
1098
|
-
}
|
|
1099
|
-
if (planMetadata && planMetadata.keyDecisions.length > 0) {
|
|
1100
|
-
desc += `\n\n## 关键决策参考\n${planMetadata.keyDecisions.slice(0, 5).map(d => `- ${d}`).join('\n')}`;
|
|
1101
|
-
}
|
|
1102
|
-
desc += `\n\n## 输出要求
|
|
462
|
+
buildTaskDescription(goal, globalContext) {
|
|
463
|
+
return `## 当前子任务目标
|
|
464
|
+
${goal}
|
|
465
|
+
|
|
466
|
+
${globalContext}
|
|
467
|
+
|
|
468
|
+
## 输出要求
|
|
1103
469
|
- 完成功能实现
|
|
1104
470
|
- 代码可编译
|
|
1105
471
|
- 遵循项目规范
|
|
1106
472
|
- 添加必要注释`;
|
|
1107
|
-
return desc;
|
|
1108
473
|
}
|
|
1109
|
-
buildTestDescription(goal, devTaskId, coverageTarget, globalContext
|
|
1110
|
-
|
|
474
|
+
buildTestDescription(goal, devTaskId, coverageTarget, globalContext) {
|
|
475
|
+
return `## 测试目标
|
|
1111
476
|
为 "${goal}" 编写测试用例
|
|
1112
477
|
|
|
1113
478
|
${globalContext}
|
|
1114
479
|
|
|
1115
480
|
## 关联开发任务
|
|
1116
|
-
${devTaskId}
|
|
1117
|
-
|
|
1118
|
-
|
|
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## 测试要求
|
|
481
|
+
${devTaskId}
|
|
482
|
+
|
|
483
|
+
## 测试要求
|
|
1128
484
|
- 单元测试覆盖率 >= ${coverageTarget}%
|
|
1129
485
|
- 测试正常流程
|
|
1130
486
|
- 测试边界情况
|
|
@@ -1138,7 +494,6 @@ ${devTaskId}`;
|
|
|
1138
494
|
- 测试文件
|
|
1139
495
|
- 测试报告
|
|
1140
496
|
- 覆盖率报告`;
|
|
1141
|
-
return desc;
|
|
1142
497
|
}
|
|
1143
498
|
buildE2ETestDescription(e2eType, parsedTask, userContext) {
|
|
1144
499
|
const typeConfig = this.getE2ETypeConfig(e2eType);
|
|
@@ -1151,7 +506,7 @@ ${devTaskId}`;
|
|
|
1151
506
|
5. **截图对比**: 关键页面应生成截图,便于人工审核
|
|
1152
507
|
` : '';
|
|
1153
508
|
return `## E2E 测试目标
|
|
1154
|
-
|
|
509
|
+
执行完整的端到端测试,验证完整用户流程
|
|
1155
510
|
${e2eType === 'visual' ? '(需可视化验证,检查页面样式和布局)' : ''}
|
|
1156
511
|
|
|
1157
512
|
## 应用类型
|
|
@@ -1266,60 +621,12 @@ ${typeConfig.runCommand}
|
|
|
1266
621
|
return 'P1';
|
|
1267
622
|
return 'P2';
|
|
1268
623
|
}
|
|
1269
|
-
estimateComplexity(
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
return 'medium';
|
|
1274
|
-
if (goal.includes('设计') || goal.includes('研究'))
|
|
1275
|
-
return 'high';
|
|
1276
|
-
if (goal.includes('文档') || goal.includes('说明'))
|
|
1277
|
-
return 'low';
|
|
624
|
+
estimateComplexity(_goal, goalIndex, task) {
|
|
625
|
+
// 优先使用 AI 标注的复杂度
|
|
626
|
+
if (task.goalComplexity?.[goalIndex])
|
|
627
|
+
return task.goalComplexity[goalIndex];
|
|
1278
628
|
return 'medium';
|
|
1279
629
|
}
|
|
1280
|
-
/**
|
|
1281
|
-
* 分类目标类型
|
|
1282
|
-
* - development: 需要编写代码的功能实现 → 拆分为实现+测试对
|
|
1283
|
-
* - testing: 已明确是测试任务 → 单个测试任务
|
|
1284
|
-
* - documentation: 文档编写 → 单个文档任务
|
|
1285
|
-
* - other: 其他类型(配置、优化、部署等) → 单个任务
|
|
1286
|
-
*/
|
|
1287
|
-
classifyGoal(goal) {
|
|
1288
|
-
const g = goal.toLowerCase();
|
|
1289
|
-
// 测试类关键词
|
|
1290
|
-
const testKeywords = [
|
|
1291
|
-
'测试', 'test', 'testing', 'tdd', 'e2e', 'e2e测试',
|
|
1292
|
-
'单元测试', '集成测试', '端到端', '覆盖率', 'coverage',
|
|
1293
|
-
'vitest', 'jest', 'mocha', 'playwright', 'cypress',
|
|
1294
|
-
];
|
|
1295
|
-
if (testKeywords.some(kw => g.includes(kw))) {
|
|
1296
|
-
return 'testing';
|
|
1297
|
-
}
|
|
1298
|
-
// 文档类关键词
|
|
1299
|
-
const docKeywords = [
|
|
1300
|
-
'文档', 'document', 'documentation', 'readme', '说明',
|
|
1301
|
-
'指南', 'guide', 'tutorial', 'api文档',
|
|
1302
|
-
];
|
|
1303
|
-
if (docKeywords.some(kw => g.includes(kw))) {
|
|
1304
|
-
return 'documentation';
|
|
1305
|
-
}
|
|
1306
|
-
// 非开发类关键词(配置、部署等)
|
|
1307
|
-
const nonDevKeywords = [
|
|
1308
|
-
'配置', 'config', 'deploy', '部署', 'ci/cd', '发布',
|
|
1309
|
-
'release', '优化', '监控', 'monitor', '日志',
|
|
1310
|
-
];
|
|
1311
|
-
// 如果只包含非开发关键词而不包含开发关键词,归为 other
|
|
1312
|
-
const devKeywords = [
|
|
1313
|
-
'实现', '开发', '编写', '创建', '构建', '添加', '修复',
|
|
1314
|
-
'implement', 'develop', 'build', 'create', 'add', 'fix',
|
|
1315
|
-
'功能', 'feature', '模块', '组件', 'component', '系统',
|
|
1316
|
-
];
|
|
1317
|
-
if (nonDevKeywords.some(kw => g.includes(kw)) && !devKeywords.some(kw => g.includes(kw))) {
|
|
1318
|
-
return 'other';
|
|
1319
|
-
}
|
|
1320
|
-
// 默认为开发类
|
|
1321
|
-
return 'development';
|
|
1322
|
-
}
|
|
1323
630
|
/**
|
|
1324
631
|
* 检测顺序阶段目标(如 "Phase 1 基础架构"、"Phase 2 AI创作核心")
|
|
1325
632
|
* 返回按阶段排序的索引列表
|
|
@@ -1351,7 +658,6 @@ ${typeConfig.runCommand}
|
|
|
1351
658
|
if (phaseIndices.length < 2)
|
|
1352
659
|
return; // 少于 2 个阶段,不需要建立依赖
|
|
1353
660
|
// 收集每个阶段的开发任务 ID 和集成任务 ID
|
|
1354
|
-
// 按阶段索引在 goals 中的原始位置分组
|
|
1355
661
|
const phaseTaskMap = new Map();
|
|
1356
662
|
for (let pi = 0; pi < phaseIndices.length; pi++) {
|
|
1357
663
|
const goalIndex = phaseIndices[pi];
|
|
@@ -1366,14 +672,12 @@ ${typeConfig.runCommand}
|
|
|
1366
672
|
}
|
|
1367
673
|
// 集成任务标题包含 "系统集成"
|
|
1368
674
|
if (b.title.startsWith('系统集成:') && devTaskIds.length > 0) {
|
|
1369
|
-
// 集成任务依赖当前阶段的所有开发任务
|
|
1370
675
|
const currentDeps = b.dependencies;
|
|
1371
676
|
if (currentDeps.length === 0 || currentDeps.some(d => devTaskIds.includes(d))) {
|
|
1372
677
|
integrationTaskId = b.taskId;
|
|
1373
678
|
}
|
|
1374
679
|
}
|
|
1375
680
|
}
|
|
1376
|
-
// 如果没有集成任务,使用最后一个开发任务 ID
|
|
1377
681
|
phaseTaskMap.set(goalIndex, { devTaskIds, integrationTaskId });
|
|
1378
682
|
}
|
|
1379
683
|
// 建立跨阶段依赖:Phase N 的所有任务依赖 Phase N-1 的集成任务(或最后开发任务)
|
|
@@ -1383,18 +687,14 @@ ${typeConfig.runCommand}
|
|
|
1383
687
|
const prevPhase = phaseTaskMap.get(prevGoalIndex);
|
|
1384
688
|
if (!prevPhase || prevPhase.devTaskIds.length === 0)
|
|
1385
689
|
continue;
|
|
1386
|
-
// 前一阶段的核心依赖点(优先集成任务,其次最后开发任务)
|
|
1387
690
|
const prevAnchor = prevPhase.integrationTaskId || prevPhase.devTaskIds[prevPhase.devTaskIds.length - 1];
|
|
1388
|
-
// 找出当前阶段的所有开发任务和集成任务
|
|
1389
691
|
for (const b of breakdowns) {
|
|
1390
692
|
const isCurrentPhaseDev = b.title.startsWith('实现: ') &&
|
|
1391
693
|
parsedTask.goals[currentGoalIndex] &&
|
|
1392
694
|
b.description.includes(parsedTask.goals[currentGoalIndex]);
|
|
1393
695
|
const isCurrentPhaseIntegration = b.title.startsWith('系统集成:');
|
|
1394
|
-
// 简化判断:通过依赖关系推断——集成任务依赖当前阶段开发任务
|
|
1395
696
|
const isCurrentPhaseTest = b.title.startsWith('测试: ') &&
|
|
1396
697
|
b.dependencies.some(dep => {
|
|
1397
|
-
// 测试任务依赖的开发任务属于当前阶段
|
|
1398
698
|
const depTask = breakdowns.find(x => x.taskId === dep);
|
|
1399
699
|
return depTask && depTask.title.startsWith('实现: ') &&
|
|
1400
700
|
parsedTask.goals[currentGoalIndex] &&
|