@zhuan-ai/zhuanspec 2.17.15 → 2.19.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +9 -4
- package/dist/commands/artifact-workflow.js +10 -35
- package/dist/commands/design.d.ts +3 -2
- package/dist/commands/design.js +27 -19
- package/dist/core/completions/command-registry.js +2 -7
- package/dist/core/configurators/slash/auggie.js +1 -1
- package/dist/core/configurators/slash/costrict.js +1 -1
- package/dist/core/configurators/slash/factory.js +1 -1
- package/dist/core/hooks/init.js +2 -3
- package/dist/core/hooks/post-apply.d.ts +3 -4
- package/dist/core/hooks/post-apply.js +4 -5
- package/dist/core/hooks/summarize.js +1 -2
- package/dist/core/task-graph/execution-planner.js +3 -2
- package/dist/core/templates/agents-template.d.ts +1 -1
- package/dist/core/templates/agents-template.js +7 -9
- package/dist/core/templates/skill-templates.js +14 -50
- package/dist/core/templates/slash-command-templates.js +63 -16
- package/dist/core/templates/tasks-template.js +3 -3
- package/dist/core/templates/tdd-tasks-template.js +4 -4
- package/package.json +20 -22
- package/schemas/spec-driven/schema.yaml +4 -2
package/dist/cli/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Command } from 'commander';
|
|
1
|
+
import { Command, Option } from 'commander';
|
|
2
2
|
import { createRequire } from 'module';
|
|
3
3
|
import ora from 'ora';
|
|
4
4
|
import path from 'path';
|
|
@@ -104,8 +104,9 @@ const registerTechDesignCommand = (commandName, deprecatedAlias = false) => prog
|
|
|
104
104
|
: 'Prepare technical design generation request (techDesign phase, independent before proposal by default)')
|
|
105
105
|
.option('--change <change-id>', 'Optionally bind generated design source to a change')
|
|
106
106
|
.option('--desc <text>', 'Requirement description used for technical design generation')
|
|
107
|
-
.option('--
|
|
108
|
-
.
|
|
107
|
+
.option('--feishu-url <url>', 'Feishu document URL used as requirement source')
|
|
108
|
+
.addOption(new Option('--dashen-page-id <id>', 'Legacy pageId used as requirement source').hideHelp())
|
|
109
|
+
.addOption(new Option('--dashen-url <url>', 'Legacy document URL used as requirement source').hideHelp())
|
|
109
110
|
.option('--output <path>', 'Output path of generated request markdown (default: doc/*技术方案生成请求.md)')
|
|
110
111
|
.option('--init-template', 'Generate empty design.md template inside a change (legacy mode)')
|
|
111
112
|
.option('--force', 'Overwrite existing output file or bound design.md')
|
|
@@ -117,7 +118,11 @@ const registerTechDesignCommand = (commandName, deprecatedAlias = false) => prog
|
|
|
117
118
|
console.error('Warning: "zhuanspec design" is deprecated. Please use "zhuanspec techDesign".');
|
|
118
119
|
}
|
|
119
120
|
const designCommand = new DesignCommand();
|
|
120
|
-
await designCommand.execute(changeName,
|
|
121
|
+
await designCommand.execute(changeName, {
|
|
122
|
+
...options,
|
|
123
|
+
legacyPageId: options?.dashenPageId,
|
|
124
|
+
legacyUrl: options?.dashenUrl,
|
|
125
|
+
});
|
|
121
126
|
}
|
|
122
127
|
catch (error) {
|
|
123
128
|
console.log();
|
|
@@ -13,7 +13,7 @@ import { createChange, validateChangeName } from '../utils/change-utils.js';
|
|
|
13
13
|
import { discoverSkills } from '../core/skill-discovery.js';
|
|
14
14
|
import { getNewChangeSkillTemplate, getContinueChangeSkillTemplate, getApplyChangeSkillTemplate, getFfChangeSkillTemplate, getSyncSpecsSkillTemplate, getArchiveChangeSkillTemplate, getProposeChangeSkillTemplate, getExploreSkillTemplate, getVerifySkillTemplate, getOpsxNewCommandTemplate, getOpsxContinueCommandTemplate, getOpsxApplyCommandTemplate, getOpsxFfCommandTemplate, getOpsxSyncCommandTemplate, getOpsxArchiveCommandTemplate, getOpsxProposeCommandTemplate, getOpsxExploreCommandTemplate, getOpsxVerifyCommandTemplate } from '../core/templates/skill-templates.js';
|
|
15
15
|
import { FileSystemUtils } from '../utils/file-system.js';
|
|
16
|
-
import { parseTasks, generateExecutionPlan, renderExecutionPlanXml,
|
|
16
|
+
import { parseTasks, generateExecutionPlan, renderExecutionPlanXml, hasDependsAnnotations, } from '../core/task-graph/index.js';
|
|
17
17
|
const DEFAULT_SCHEMA = 'spec-driven';
|
|
18
18
|
/**
|
|
19
19
|
* Checks if color output is disabled via NO_COLOR env or --no-color flag.
|
|
@@ -427,26 +427,6 @@ async function generateApplyInstructions(projectRoot, changeName, schemaName) {
|
|
|
427
427
|
if (hasDependencies && !allTasksComplete) {
|
|
428
428
|
const executionPlan = generateExecutionPlan(parsedTasks);
|
|
429
429
|
executionPlanXml = renderExecutionPlanXml(executionPlan);
|
|
430
|
-
// Generate Mermaid diagram and embed in tasks.md
|
|
431
|
-
if (executionPlan && !executionPlan.hasCycle) {
|
|
432
|
-
const mermaidSection = renderMermaidDiagram(executionPlan, parsedTasks);
|
|
433
|
-
if (mermaidSection) {
|
|
434
|
-
// Read current tasks.md content
|
|
435
|
-
let currentTasksContent = tasksContent;
|
|
436
|
-
// Check if tasks.md already has Workflow Diagram section
|
|
437
|
-
const workflowDiagramRegex = /## Workflow Diagram[\s\S]*?(?=\n## |$)/;
|
|
438
|
-
if (workflowDiagramRegex.test(currentTasksContent)) {
|
|
439
|
-
// Replace existing Workflow Diagram section
|
|
440
|
-
currentTasksContent = currentTasksContent.replace(workflowDiagramRegex, mermaidSection);
|
|
441
|
-
}
|
|
442
|
-
else {
|
|
443
|
-
// Append to file end
|
|
444
|
-
currentTasksContent = currentTasksContent.trimEnd() + '\n\n' + mermaidSection;
|
|
445
|
-
}
|
|
446
|
-
// Write back to tasks.md
|
|
447
|
-
fs.writeFileSync(tracksPath, currentTasksContent, 'utf-8');
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
430
|
}
|
|
451
431
|
}
|
|
452
432
|
}
|
|
@@ -475,17 +455,15 @@ async function generateApplyInstructions(projectRoot, changeName, schemaName) {
|
|
|
475
455
|
}
|
|
476
456
|
else if (tracksFile && remaining === 0 && total > 0) {
|
|
477
457
|
state = 'all_done';
|
|
478
|
-
instruction = `All tasks are complete.
|
|
458
|
+
instruction = `All tasks are complete. Apply is finished.
|
|
479
459
|
|
|
480
460
|
**Required Next Steps**
|
|
481
461
|
|
|
482
|
-
1.
|
|
483
|
-
2.
|
|
484
|
-
3.
|
|
485
|
-
4. If auto review failed/missing, run \`zhuanspec review ${changeName}\` manually
|
|
486
|
-
5. Then continue to \`zhuanspec archive ${changeName}\`
|
|
462
|
+
1. Run \`zhuanspec review ${changeName}\` to verify implementation quality
|
|
463
|
+
2. Fix any review findings if needed
|
|
464
|
+
3. Continue to \`zhuanspec archive ${changeName}\` after review passes
|
|
487
465
|
|
|
488
|
-
Archive may be blocked if review
|
|
466
|
+
Archive may be blocked if review results are missing or failed.`;
|
|
489
467
|
}
|
|
490
468
|
else if (!tracksFile) {
|
|
491
469
|
// No tracking file (e.g., TDD schema) - ready to apply
|
|
@@ -588,16 +566,13 @@ function printApplyInstructionsText(instructions) {
|
|
|
588
566
|
// Code review prompt for all_done state
|
|
589
567
|
if (state === 'all_done') {
|
|
590
568
|
console.log();
|
|
591
|
-
console.log('###
|
|
569
|
+
console.log('### Next Step');
|
|
592
570
|
console.log();
|
|
593
571
|
console.log('All tasks are complete. You should now:');
|
|
594
572
|
console.log();
|
|
595
|
-
console.log('1.
|
|
596
|
-
console.log('2.
|
|
597
|
-
console.log('3.
|
|
598
|
-
console.log('4. Ensure unit tests pass');
|
|
599
|
-
console.log('5. If auto review failed/missing, run `zhuanspec review <change-id>` manually');
|
|
600
|
-
console.log('6. Then proceed to archive workflow');
|
|
573
|
+
console.log('1. Run `zhuanspec review <change-id>`');
|
|
574
|
+
console.log('2. Fix review findings if needed');
|
|
575
|
+
console.log('3. Then proceed to archive workflow');
|
|
601
576
|
}
|
|
602
577
|
}
|
|
603
578
|
async function templatesCommand(options) {
|
|
@@ -4,8 +4,9 @@ export declare class DesignCommand {
|
|
|
4
4
|
initTemplate?: boolean;
|
|
5
5
|
change?: string;
|
|
6
6
|
desc?: string;
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
feishuUrl?: string;
|
|
8
|
+
legacyPageId?: string;
|
|
9
|
+
legacyUrl?: string;
|
|
9
10
|
output?: string;
|
|
10
11
|
validate?: boolean;
|
|
11
12
|
autoFix?: boolean;
|
package/dist/commands/design.js
CHANGED
|
@@ -49,8 +49,9 @@ export class DesignCommand {
|
|
|
49
49
|
if (options?.createChange && !boundChange) {
|
|
50
50
|
const designContext = await this.resolveDesignContext({
|
|
51
51
|
desc: options?.desc,
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
feishuUrl: options?.feishuUrl,
|
|
53
|
+
legacyPageId: options?.legacyPageId,
|
|
54
|
+
legacyUrl: options?.legacyUrl,
|
|
54
55
|
});
|
|
55
56
|
const newChangeName = await this.generateChangeName(designContext);
|
|
56
57
|
await this.createTechDesignChange(newChangeName, designContext);
|
|
@@ -76,11 +77,12 @@ export class DesignCommand {
|
|
|
76
77
|
}
|
|
77
78
|
const designContext = await this.resolveDesignContext({
|
|
78
79
|
desc: options?.desc,
|
|
79
|
-
|
|
80
|
-
|
|
80
|
+
feishuUrl: options?.feishuUrl,
|
|
81
|
+
legacyPageId: options?.legacyPageId,
|
|
82
|
+
legacyUrl: options?.legacyUrl,
|
|
81
83
|
});
|
|
82
|
-
if (!designContext.desc && !designContext.
|
|
83
|
-
throw new Error('Missing design source. Provide --desc
|
|
84
|
+
if (!designContext.desc && !designContext.feishuUrl && !designContext.legacyPageId && !designContext.legacyUrl) {
|
|
85
|
+
throw new Error('Missing design source. Provide --desc or --feishu-url to generate technical design.');
|
|
84
86
|
}
|
|
85
87
|
const outputPath = await this.resolveOutputPath(options?.output, boundChange);
|
|
86
88
|
const exists = await FileSystemUtils.fileExists(outputPath);
|
|
@@ -112,15 +114,16 @@ export class DesignCommand {
|
|
|
112
114
|
async resolveDesignContext(input) {
|
|
113
115
|
const result = {
|
|
114
116
|
desc: input.desc?.trim() ?? '',
|
|
115
|
-
|
|
116
|
-
|
|
117
|
+
feishuUrl: input.feishuUrl?.trim() ?? '',
|
|
118
|
+
legacyPageId: input.legacyPageId?.trim() ?? '',
|
|
119
|
+
legacyUrl: input.legacyUrl?.trim() ?? '',
|
|
117
120
|
};
|
|
118
|
-
if (result.desc || result.
|
|
121
|
+
if (result.desc || result.feishuUrl || result.legacyPageId || result.legacyUrl || !process.stdin.isTTY) {
|
|
119
122
|
return result;
|
|
120
123
|
}
|
|
121
124
|
const { input: askInput } = await import('@inquirer/prompts');
|
|
122
125
|
const source = await askInput({
|
|
123
|
-
message: '
|
|
126
|
+
message: '请输入需求描述或飞书文档 URL(飞书文档将使用飞书 CLI 读取)',
|
|
124
127
|
default: '',
|
|
125
128
|
});
|
|
126
129
|
result.desc = source.trim();
|
|
@@ -147,12 +150,12 @@ export class DesignCommand {
|
|
|
147
150
|
'',
|
|
148
151
|
'## 输入',
|
|
149
152
|
`- 需求描述: ${context.desc || '(未提供)'}`,
|
|
150
|
-
`-
|
|
151
|
-
|
|
153
|
+
`- 飞书文档 URL: ${context.feishuUrl || context.legacyUrl || '(未提供)'}`,
|
|
154
|
+
'- 飞书读取方式: 使用飞书 CLI `lark-cli` 读取正文',
|
|
152
155
|
`- 绑定变更: ${boundChange || '(无,独立于提案阶段)'}`,
|
|
153
156
|
'',
|
|
154
157
|
'## 生成要求',
|
|
155
|
-
'-
|
|
158
|
+
'- 使用飞书文档内容作为需求来源,本地输入兜底',
|
|
156
159
|
'- 输出完整技术方案(含 Mermaid)',
|
|
157
160
|
'- 覆盖需求功能点并附带校验结果与修复记录',
|
|
158
161
|
'',
|
|
@@ -346,12 +349,17 @@ export class DesignCommand {
|
|
|
346
349
|
if (context.desc) {
|
|
347
350
|
base = context.desc;
|
|
348
351
|
}
|
|
349
|
-
else if (context.
|
|
350
|
-
|
|
352
|
+
else if (context.feishuUrl) {
|
|
353
|
+
const docMatch = context.feishuUrl.match(/(?:docx|wiki)\/([^/?#]+)/);
|
|
354
|
+
const storyMatch = context.feishuUrl.match(/story\/detail\/([^/?#]+)/);
|
|
355
|
+
base = `tech-design-${docMatch?.[1] || storyMatch?.[1] || Date.now()}`;
|
|
351
356
|
}
|
|
352
|
-
else if (context.
|
|
357
|
+
else if (context.legacyPageId) {
|
|
358
|
+
base = `tech-design-${context.legacyPageId}`;
|
|
359
|
+
}
|
|
360
|
+
else if (context.legacyUrl) {
|
|
353
361
|
// Extract page ID from URL if possible
|
|
354
|
-
const match = context.
|
|
362
|
+
const match = context.legacyUrl.match(/pages\/(\d+)/);
|
|
355
363
|
base = match ? `tech-design-${match[1]}` : `tech-design-${Date.now()}`;
|
|
356
364
|
}
|
|
357
365
|
else {
|
|
@@ -391,9 +399,9 @@ export class DesignCommand {
|
|
|
391
399
|
// 3. Create techDesign/ directory and tech-spec.md placeholder
|
|
392
400
|
const techDesignDir = path.join(changeDir, 'techDesign');
|
|
393
401
|
await FileSystemUtils.createDirectory(techDesignDir);
|
|
394
|
-
await FileSystemUtils.writeFile(path.join(techDesignDir, 'tech-spec.md'), `# Tech Spec\n\ncreated: ${new Date().toISOString()}\nsource: ${context.desc || context.
|
|
402
|
+
await FileSystemUtils.writeFile(path.join(techDesignDir, 'tech-spec.md'), `# Tech Spec\n\ncreated: ${new Date().toISOString()}\nsource: ${context.desc || context.feishuUrl || context.legacyPageId || 'manual'}\n\n> This file will be populated by the techDesign skill.\n`);
|
|
395
403
|
// 3b. Also create .tech-design marker for backward compatibility
|
|
396
|
-
await FileSystemUtils.writeFile(path.join(changeDir, '.tech-design'), `created: ${new Date().toISOString()}\nsource: ${context.desc || context.
|
|
404
|
+
await FileSystemUtils.writeFile(path.join(changeDir, '.tech-design'), `created: ${new Date().toISOString()}\nsource: ${context.desc || context.feishuUrl || context.legacyPageId || 'manual'}\n`);
|
|
397
405
|
// 4. Create design.md skeleton
|
|
398
406
|
await FileSystemUtils.writeFile(path.join(changeDir, 'design.md'), DEFAULT_DESIGN_TEMPLATE);
|
|
399
407
|
// 5. Create doc/ request file (keep compatibility)
|
|
@@ -86,13 +86,8 @@ export const COMMAND_REGISTRY = [
|
|
|
86
86
|
takesValue: true,
|
|
87
87
|
},
|
|
88
88
|
{
|
|
89
|
-
name: '
|
|
90
|
-
description: '
|
|
91
|
-
takesValue: true,
|
|
92
|
-
},
|
|
93
|
-
{
|
|
94
|
-
name: 'dashen-url',
|
|
95
|
-
description: 'Dashen document URL used as requirement source',
|
|
89
|
+
name: 'feishu-url',
|
|
90
|
+
description: 'Feishu document URL used as requirement source',
|
|
96
91
|
takesValue: true,
|
|
97
92
|
},
|
|
98
93
|
{
|
|
@@ -14,7 +14,7 @@ argument-hint: feature description or request
|
|
|
14
14
|
---`,
|
|
15
15
|
design: `---
|
|
16
16
|
description: 在 proposal 之前独立生成技术设计请求文档。
|
|
17
|
-
argument-hint:
|
|
17
|
+
argument-hint: feishu-url or description
|
|
18
18
|
---`,
|
|
19
19
|
apply: `---
|
|
20
20
|
description: 实施已批准的 ZhuanSpec 变更并保持任务同步。
|
|
@@ -14,7 +14,7 @@ argument-hint: feature description or request
|
|
|
14
14
|
---`,
|
|
15
15
|
design: `---
|
|
16
16
|
description: "在 proposal 之前独立生成技术设计请求文档。"
|
|
17
|
-
argument-hint:
|
|
17
|
+
argument-hint: feishu-url or description
|
|
18
18
|
---`,
|
|
19
19
|
apply: `---
|
|
20
20
|
description: "实施已批准的 ZhuanSpec 变更并保持任务同步。"
|
|
@@ -14,7 +14,7 @@ argument-hint: request or feature description
|
|
|
14
14
|
---`,
|
|
15
15
|
design: `---
|
|
16
16
|
description: 在 proposal 之前独立生成技术设计请求文档。
|
|
17
|
-
argument-hint:
|
|
17
|
+
argument-hint: feishu-url or description
|
|
18
18
|
---`,
|
|
19
19
|
apply: `---
|
|
20
20
|
description: 实施已批准的 ZhuanSpec 变更并保持任务同步。
|
package/dist/core/hooks/init.js
CHANGED
|
@@ -200,8 +200,7 @@ async function runInitHook(_options) {
|
|
|
200
200
|
const versionBanner = await checkVersionUpdate();
|
|
201
201
|
// Build system message — intentionally minimal. Only the version banner
|
|
202
202
|
// and active-change phase line are surfaced. Per-phase guidance lives in
|
|
203
|
-
// the corresponding Skill / slash-command template
|
|
204
|
-
// carries the subagent spawn authorization).
|
|
203
|
+
// the corresponding Skill / slash-command template.
|
|
205
204
|
let systemMessage = '';
|
|
206
205
|
if (versionBanner) {
|
|
207
206
|
systemMessage += `${versionBanner}\n`;
|
|
@@ -220,7 +219,7 @@ async function runInitHook(_options) {
|
|
|
220
219
|
systemMessage += ` (run /zhuanspec:proposal to continue)`;
|
|
221
220
|
}
|
|
222
221
|
else if (phase === 'apply') {
|
|
223
|
-
systemMessage += ` (run zhuanspec-apply
|
|
222
|
+
systemMessage += ` (run zhuanspec-apply to implement pending tasks)`;
|
|
224
223
|
}
|
|
225
224
|
systemMessage += `\n`;
|
|
226
225
|
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Actions after Apply phase completes:
|
|
5
5
|
* 1. Reverse Sync deviation detection
|
|
6
|
-
* 2. Report status (user manually triggers review
|
|
6
|
+
* 2. Report status (user manually triggers review when ready)
|
|
7
7
|
*/
|
|
8
8
|
interface PostApplyOptions {
|
|
9
9
|
json?: boolean;
|
|
@@ -25,9 +25,8 @@ interface HookOutput {
|
|
|
25
25
|
export declare function postApplyHook(options: PostApplyOptions): Promise<void>;
|
|
26
26
|
/**
|
|
27
27
|
* Execute post-apply actions: deviation detection only.
|
|
28
|
-
* Auto review trigger has been removed
|
|
29
|
-
*
|
|
30
|
-
* Users should manually run `zhuanspec review <change-id>` in a new session.
|
|
28
|
+
* Auto review trigger has been removed so apply stays focused on implementation.
|
|
29
|
+
* Users should manually run `zhuanspec review <change-id>` when they want full validation.
|
|
31
30
|
*/
|
|
32
31
|
export declare function executePostApply(changeDir: string, repoRoot: string): Promise<HookOutput>;
|
|
33
32
|
export {};
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Actions after Apply phase completes:
|
|
5
5
|
* 1. Reverse Sync deviation detection
|
|
6
|
-
* 2. Report status (user manually triggers review
|
|
6
|
+
* 2. Report status (user manually triggers review when ready)
|
|
7
7
|
*/
|
|
8
8
|
import path from 'path';
|
|
9
9
|
import { FileSystemUtils } from '../../utils/file-system.js';
|
|
@@ -40,9 +40,8 @@ async function runPostApplyHook(changeId) {
|
|
|
40
40
|
}
|
|
41
41
|
/**
|
|
42
42
|
* Execute post-apply actions: deviation detection only.
|
|
43
|
-
* Auto review trigger has been removed
|
|
44
|
-
*
|
|
45
|
-
* Users should manually run `zhuanspec review <change-id>` in a new session.
|
|
43
|
+
* Auto review trigger has been removed so apply stays focused on implementation.
|
|
44
|
+
* Users should manually run `zhuanspec review <change-id>` when they want full validation.
|
|
46
45
|
*/
|
|
47
46
|
export async function executePostApply(changeDir, repoRoot) {
|
|
48
47
|
// 0. Feature-omission gate(功能点遗漏闸口):检测「范围内但未实现」的 Scenario/功能点
|
|
@@ -84,7 +83,7 @@ export async function executePostApply(changeDir, repoRoot) {
|
|
|
84
83
|
}
|
|
85
84
|
return {
|
|
86
85
|
continue: true,
|
|
87
|
-
systemMessage: 'Post-apply: No deviation detected. Please run `zhuanspec review`
|
|
86
|
+
systemMessage: 'Post-apply: No deviation detected. Please run `zhuanspec review` when you are ready to validate the change.',
|
|
88
87
|
hookSpecificOutput: {
|
|
89
88
|
reviewTriggered: false,
|
|
90
89
|
deviationDetected: false,
|
|
@@ -89,8 +89,7 @@ async function runSummarize() {
|
|
|
89
89
|
}
|
|
90
90
|
}
|
|
91
91
|
// Deviation detection only (no auto review trigger).
|
|
92
|
-
// Apply 完成后不再自动触发 review
|
|
93
|
-
// 用户需在新会话中手动执行 `zhuanspec review <change-id>` 进入 review 阶段。
|
|
92
|
+
// Apply 完成后不再自动触发 review;用户准备验收时再手动执行 `zhuanspec review <change-id>`。
|
|
94
93
|
let deviationDetected = false;
|
|
95
94
|
if (phase === 'apply' && allTasksComplete) {
|
|
96
95
|
// Execute post-apply hook to detect deviations only
|
|
@@ -71,6 +71,7 @@ export function generateExecutionPlan(tasks, options) {
|
|
|
71
71
|
// Create graph and validate
|
|
72
72
|
const graph = new TaskGraph(incompleteTasks);
|
|
73
73
|
const validation = graph.validate();
|
|
74
|
+
const taskById = new Map(incompleteTasks.map(task => [task.id, task]));
|
|
74
75
|
// Check for cycles
|
|
75
76
|
if (validation.errors.some(e => e.includes('Circular dependency'))) {
|
|
76
77
|
return {
|
|
@@ -95,7 +96,7 @@ export function generateExecutionPlan(tasks, options) {
|
|
|
95
96
|
// Initialize
|
|
96
97
|
for (const task of incompleteTasks) {
|
|
97
98
|
// Only count dependencies that exist in incomplete tasks
|
|
98
|
-
const validDeps = task.depends.filter(d =>
|
|
99
|
+
const validDeps = task.depends.filter(d => taskById.has(d));
|
|
99
100
|
inDegree.set(task.id, validDeps.length);
|
|
100
101
|
dependents.set(task.id, []);
|
|
101
102
|
}
|
|
@@ -120,7 +121,7 @@ export function generateExecutionPlan(tasks, options) {
|
|
|
120
121
|
break;
|
|
121
122
|
}
|
|
122
123
|
const readyTasks = readyIds
|
|
123
|
-
.map(id =>
|
|
124
|
+
.map(id => taskById.get(id))
|
|
124
125
|
.filter(Boolean);
|
|
125
126
|
waves.push({
|
|
126
127
|
wave: waveNumber,
|