@zhuan-ai/zhuanspec 1.3.0 → 2.1.0

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.
Files changed (36) hide show
  1. package/README.zh.md +1 -1
  2. package/dist/cli/index.js +1 -1
  3. package/dist/commands/artifact-workflow.js +45 -2
  4. package/dist/commands/validate.d.ts +14 -0
  5. package/dist/commands/validate.js +160 -4
  6. package/dist/core/skill-discovery.d.ts +2 -2
  7. package/dist/core/skill-discovery.js +16 -3
  8. package/dist/core/task-graph/execution-planner.d.ts +17 -0
  9. package/dist/core/task-graph/execution-planner.js +119 -0
  10. package/dist/core/task-graph/index.d.ts +13 -0
  11. package/dist/core/task-graph/index.js +16 -0
  12. package/dist/core/task-graph/mermaid-renderer.d.ts +22 -0
  13. package/dist/core/task-graph/mermaid-renderer.js +128 -0
  14. package/dist/core/task-graph/task-graph.d.ts +74 -0
  15. package/dist/core/task-graph/task-graph.js +219 -0
  16. package/dist/core/task-graph/task-parser.d.ts +14 -0
  17. package/dist/core/task-graph/task-parser.js +111 -0
  18. package/dist/core/task-graph/types.d.ts +53 -0
  19. package/dist/core/task-graph/types.js +7 -0
  20. package/dist/core/task-graph/xml-renderer.d.ts +21 -0
  21. package/dist/core/task-graph/xml-renderer.js +81 -0
  22. package/dist/core/templates/agents-template.d.ts +1 -1
  23. package/dist/core/templates/agents-template.js +23 -0
  24. package/dist/core/templates/skill-templates.js +42 -0
  25. package/dist/core/templates/slash-command-templates.js +25 -2
  26. package/dist/core/templates/tasks-template.d.ts +23 -0
  27. package/dist/core/templates/tasks-template.js +79 -0
  28. package/dist/core/templates/tdd-tasks-template.d.ts +24 -0
  29. package/dist/core/templates/tdd-tasks-template.js +116 -0
  30. package/dist/core/validation/strict-rules.d.ts +60 -0
  31. package/dist/core/validation/strict-rules.js +287 -0
  32. package/dist/core/validation/types.d.ts +10 -0
  33. package/dist/core/validation/validator.d.ts +5 -0
  34. package/dist/core/validation/validator.js +103 -1
  35. package/package.json +22 -20
  36. package/schemas/spec-driven/templates/tasks.md +17 -5
package/README.zh.md CHANGED
@@ -415,7 +415,7 @@ npm install -g ./distributions/fission-ai-zhuanspec-1.0.0.tgz
415
415
 
416
416
  1. **升级包**
417
417
  ```bash
418
- npm install -g @fission-ai/zhuanspec@latest
418
+ npm install -g @zhuan-ai/zhuanspec@latest
419
419
  ```
420
420
  2. **刷新代理说明**
421
421
  - 在每个项目内运行 `zhuanspec update` 以重新生成 AI 指导并确保最新的斜杠命令处于活动状态。
package/dist/cli/index.js CHANGED
@@ -227,7 +227,7 @@ skillsCmd
227
227
  .option('--json', 'Output as JSON for programmatic use')
228
228
  .action(async (options) => {
229
229
  try {
230
- const skills = discoverSkills();
230
+ const skills = discoverSkills(process.cwd());
231
231
  console.log(formatSkillsList(skills, !!options?.json));
232
232
  }
233
233
  catch (error) {
@@ -13,6 +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, renderMermaidDiagram, hasDependsAnnotations, } from '../core/task-graph/index.js';
16
17
  const DEFAULT_SCHEMA = 'spec-driven';
17
18
  /**
18
19
  * Checks if color output is disabled via NO_COLOR env or --no-color flag.
@@ -176,7 +177,7 @@ async function instructionsCommand(artifactId, options) {
176
177
  let discoveredSkillsJson;
177
178
  if (artifactId === 'tasks' || artifactId === 'proposal') {
178
179
  try {
179
- const skills = await discoverSkills();
180
+ const skills = discoverSkills(projectRoot);
180
181
  if (skills.length > 0) {
181
182
  discoveredSkillsJson = JSON.stringify(skills.map((s) => ({ name: s.name, description: s.description })), null, 2);
182
183
  }
@@ -408,12 +409,45 @@ async function generateApplyInstructions(projectRoot, changeName, schemaName) {
408
409
  // Parse tasks if tracking file exists
409
410
  let tasks = [];
410
411
  let tracksFileExists = false;
412
+ let executionPlanXml;
413
+ let hasDependencies = false;
411
414
  if (tracksFile) {
412
415
  const tracksPath = path.join(changeDir, tracksFile);
413
416
  tracksFileExists = fs.existsSync(tracksPath);
414
417
  if (tracksFileExists) {
415
418
  const tasksContent = await fs.promises.readFile(tracksPath, 'utf-8');
416
419
  tasks = parseTasksFile(tasksContent);
420
+ // Parse tasks using task-graph module for dependency analysis
421
+ const parsedTasks = parseTasks(tasksContent);
422
+ hasDependencies = hasDependsAnnotations(parsedTasks);
423
+ // Only generate execution plan if:
424
+ // 1. Tasks have @depends annotations
425
+ // 2. Not all tasks are complete
426
+ const allTasksComplete = parsedTasks.length > 0 && parsedTasks.every(t => t.completed);
427
+ if (hasDependencies && !allTasksComplete) {
428
+ const executionPlan = generateExecutionPlan(parsedTasks);
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
+ }
417
451
  }
418
452
  }
419
453
  // Calculate progress
@@ -480,6 +514,8 @@ See the apply command template for detailed code review workflow instructions.`;
480
514
  state,
481
515
  missingArtifacts: missingArtifacts.length > 0 ? missingArtifacts : undefined,
482
516
  instruction,
517
+ executionPlanXml,
518
+ hasDependencies,
483
519
  };
484
520
  }
485
521
  async function applyInstructionsCommand(options) {
@@ -506,7 +542,7 @@ async function applyInstructionsCommand(options) {
506
542
  }
507
543
  }
508
544
  function printApplyInstructionsText(instructions) {
509
- const { changeName, schemaName, contextFiles, progress, tasks, state, missingArtifacts, instruction } = instructions;
545
+ const { changeName, schemaName, contextFiles, progress, tasks, state, missingArtifacts, instruction, executionPlanXml } = instructions;
510
546
  console.log(`## Apply: ${changeName}`);
511
547
  console.log(`Schema: ${schemaName}`);
512
548
  console.log();
@@ -547,6 +583,13 @@ function printApplyInstructionsText(instructions) {
547
583
  }
548
584
  console.log();
549
585
  }
586
+ // Execution Plan (only if tasks have dependencies and not all complete)
587
+ if (executionPlanXml) {
588
+ console.log('### Execution Plan');
589
+ console.log();
590
+ console.log(executionPlanXml);
591
+ console.log();
592
+ }
550
593
  // Instruction
551
594
  console.log('### Instruction');
552
595
  console.log(instruction);
@@ -18,6 +18,20 @@ export declare class ValidateCommand {
18
18
  private validateByType;
19
19
  private printReport;
20
20
  private printNextSteps;
21
+ /**
22
+ * Validate task dependencies in tasks.md if present.
23
+ * Returns a report with validation status and execution plan.
24
+ */
25
+ private validateTaskDependencies;
26
+ /**
27
+ * Print execution plan summary for tasks with dependencies.
28
+ */
29
+ private printExecutionPlanSummary;
30
+ /**
31
+ * Write Mermaid diagram to tasks.md file.
32
+ * Replaces existing "## Workflow Diagram" section or appends at end.
33
+ */
34
+ private writeMermaidDiagramToTasks;
21
35
  private runBulkValidation;
22
36
  }
23
37
  export {};
@@ -1,9 +1,12 @@
1
1
  import ora from 'ora';
2
2
  import path from 'path';
3
+ import chalk from 'chalk';
4
+ import { promises as fs } from 'fs';
3
5
  import { Validator } from '../core/validation/validator.js';
4
6
  import { isInteractive, resolveNoInteractive } from '../utils/interactive.js';
5
7
  import { getActiveChangeIds, getSpecIds } from '../utils/item-discovery.js';
6
8
  import { nearestMatches } from '../utils/match.js';
9
+ import { parseTasks, TaskGraph, generateExecutionPlan } from '../core/task-graph/index.js';
7
10
  export class ValidateCommand {
8
11
  async execute(itemName, options = {}) {
9
12
  const interactive = isInteractive(options);
@@ -102,8 +105,19 @@ export class ValidateCommand {
102
105
  const changeDir = path.join(process.cwd(), 'zhuanspec', 'changes', id);
103
106
  const start = Date.now();
104
107
  const report = await validator.validateChangeDeltaSpecs(changeDir);
108
+ // Validate tasks.md dependencies if present
109
+ const taskReport = await this.validateTaskDependencies(changeDir);
110
+ // Merge task dependency issues into report
111
+ if (taskReport.issues.length > 0) {
112
+ report.issues.push(...taskReport.issues);
113
+ report.valid = report.valid && taskReport.valid;
114
+ }
115
+ // Write Mermaid diagram to tasks.md if generated
116
+ if (report.mermaidDiagram) {
117
+ await this.writeMermaidDiagramToTasks(changeDir, report.mermaidDiagram);
118
+ }
105
119
  const durationMs = Date.now() - start;
106
- this.printReport('change', id, report, durationMs, opts.json);
120
+ this.printReport('change', id, report, durationMs, opts.json, taskReport, { strict: opts.strict });
107
121
  // Non-zero exit if invalid (keeps enriched output test semantics)
108
122
  process.exitCode = report.valid ? 0 : 1;
109
123
  return;
@@ -112,17 +126,41 @@ export class ValidateCommand {
112
126
  const start = Date.now();
113
127
  const report = await validator.validateSpec(file);
114
128
  const durationMs = Date.now() - start;
115
- this.printReport('spec', id, report, durationMs, opts.json);
129
+ this.printReport('spec', id, report, durationMs, opts.json, undefined, { strict: opts.strict });
116
130
  process.exitCode = report.valid ? 0 : 1;
117
131
  }
118
- printReport(type, id, report, durationMs, json) {
132
+ printReport(type, id, report, durationMs, json, taskReport, opts) {
119
133
  if (json) {
120
- const out = { items: [{ id, type, valid: report.valid, issues: report.issues, durationMs }], summary: { totals: { items: 1, passed: report.valid ? 1 : 0, failed: report.valid ? 0 : 1 }, byType: { [type]: { items: 1, passed: report.valid ? 1 : 0, failed: report.valid ? 0 : 1 } } }, version: '1.0' };
134
+ const out = {
135
+ items: [{ id, type, valid: report.valid, issues: report.issues, durationMs }],
136
+ summary: { totals: { items: 1, passed: report.valid ? 1 : 0, failed: report.valid ? 0 : 1 }, byType: { [type]: { items: 1, passed: report.valid ? 1 : 0, failed: report.valid ? 0 : 1 } } },
137
+ version: '1.0'
138
+ };
139
+ // Add execution plan to JSON output if available
140
+ if (taskReport?.hasDependencies && taskReport.executionPlan) {
141
+ out.executionPlan = taskReport.executionPlan;
142
+ }
143
+ // Add strict checks to JSON output if available
144
+ if (report.strictChecks) {
145
+ out.strictChecks = report.strictChecks;
146
+ }
147
+ // Add mermaid diagram flag to JSON output if generated
148
+ if (report.mermaidDiagram) {
149
+ out.workflowDiagramGenerated = true;
150
+ }
121
151
  console.log(JSON.stringify(out, null, 2));
122
152
  return;
123
153
  }
124
154
  if (report.valid) {
125
155
  console.log(`${type === 'change' ? 'Change' : 'Specification'} '${id}' is valid`);
156
+ // Print execution plan summary if available
157
+ if (taskReport?.hasDependencies && taskReport.executionPlan && !taskReport.executionPlan.hasCycle) {
158
+ this.printExecutionPlanSummary(taskReport.executionPlan);
159
+ }
160
+ // Print Mermaid diagram generated message
161
+ if (report.mermaidDiagram) {
162
+ console.log(' Workflow diagram generated');
163
+ }
126
164
  }
127
165
  else {
128
166
  console.error(`${type === 'change' ? 'Change' : 'Specification'} '${id}' has issues`);
@@ -133,6 +171,21 @@ export class ValidateCommand {
133
171
  }
134
172
  this.printNextSteps(type);
135
173
  }
174
+ // Print strict mode checks summary if available
175
+ if (opts?.strict && report.strictChecks && report.strictChecks.length > 0) {
176
+ console.log('');
177
+ console.log('Strict Mode Checks:');
178
+ for (const check of report.strictChecks) {
179
+ if (check.passed) {
180
+ console.log(` ${chalk.green('✓')} ${check.ruleName}`);
181
+ }
182
+ else {
183
+ const firstError = check.errors[0] || check.warnings[0] || '';
184
+ const errorSuffix = firstError ? ` — ${firstError}` : '';
185
+ console.log(` ${chalk.red('✗')} ${check.ruleName}${errorSuffix}`);
186
+ }
187
+ }
188
+ }
136
189
  }
137
190
  printNextSteps(type) {
138
191
  const bullets = [];
@@ -149,6 +202,109 @@ export class ValidateCommand {
149
202
  console.error('Next steps:');
150
203
  bullets.forEach(b => console.error(` ${b}`));
151
204
  }
205
+ /**
206
+ * Validate task dependencies in tasks.md if present.
207
+ * Returns a report with validation status and execution plan.
208
+ */
209
+ async validateTaskDependencies(changeDir) {
210
+ const tasksFile = path.join(changeDir, 'tasks.md');
211
+ // Check if tasks.md exists
212
+ let content;
213
+ try {
214
+ content = await fs.readFile(tasksFile, 'utf-8');
215
+ }
216
+ catch {
217
+ // No tasks.md file - skip dependency validation
218
+ return { valid: true, hasDependencies: false, issues: [] };
219
+ }
220
+ // Parse tasks
221
+ const tasks = parseTasks(content);
222
+ if (tasks.length === 0) {
223
+ return { valid: true, hasDependencies: false, issues: [] };
224
+ }
225
+ // Check if any task has @depends annotation
226
+ const hasDependencies = tasks.some(t => t.depends.length > 0);
227
+ if (!hasDependencies) {
228
+ // No dependencies - skip validation, no execution plan output
229
+ return { valid: true, hasDependencies: false, issues: [] };
230
+ }
231
+ // Validate task dependencies
232
+ const graph = new TaskGraph(tasks);
233
+ const validation = graph.validate();
234
+ const issues = [];
235
+ if (!validation.valid) {
236
+ for (const error of validation.errors) {
237
+ issues.push({
238
+ level: 'ERROR',
239
+ path: 'tasks.md',
240
+ message: error,
241
+ });
242
+ }
243
+ }
244
+ // Generate execution plan
245
+ const executionPlan = generateExecutionPlan(tasks);
246
+ // Add cycle-related errors from execution plan
247
+ if (executionPlan.hasCycle && executionPlan.errors.length > 0) {
248
+ for (const error of executionPlan.errors) {
249
+ // Avoid duplicates
250
+ if (!issues.some(i => i.message === error)) {
251
+ issues.push({
252
+ level: 'ERROR',
253
+ path: 'tasks.md',
254
+ message: error,
255
+ });
256
+ }
257
+ }
258
+ }
259
+ return {
260
+ valid: issues.filter(i => i.level === 'ERROR').length === 0,
261
+ hasDependencies: true,
262
+ issues,
263
+ executionPlan,
264
+ };
265
+ }
266
+ /**
267
+ * Print execution plan summary for tasks with dependencies.
268
+ */
269
+ printExecutionPlanSummary(plan) {
270
+ const { waves, totalTasks, parallelTasks, serialTasks } = plan;
271
+ console.log(` Execution Plan: ${waves.length} waves, ${totalTasks} tasks (${parallelTasks} parallel, ${serialTasks} serial)`);
272
+ for (const wave of waves) {
273
+ const taskIds = wave.tasks.map(t => t.id).join(', ');
274
+ const parallelLabel = wave.parallel ? ' (parallel)' : '';
275
+ console.log(` Wave ${wave.wave}: [${taskIds}]${parallelLabel}`);
276
+ }
277
+ }
278
+ /**
279
+ * Write Mermaid diagram to tasks.md file.
280
+ * Replaces existing "## Workflow Diagram" section or appends at end.
281
+ */
282
+ async writeMermaidDiagramToTasks(changeDir, mermaidDiagram) {
283
+ const tasksFile = path.join(changeDir, 'tasks.md');
284
+ let content;
285
+ try {
286
+ content = await fs.readFile(tasksFile, 'utf-8');
287
+ }
288
+ catch {
289
+ // tasks.md doesn't exist, nothing to write to
290
+ return;
291
+ }
292
+ // Pattern to match existing "## Workflow Diagram" section
293
+ const workflowSectionPattern = /^## Workflow Diagram[\s\S]*?(?=^## |\z)/gm;
294
+ // Check if section exists
295
+ const hasExistingSection = workflowSectionPattern.test(content);
296
+ workflowSectionPattern.lastIndex = 0; // Reset regex state
297
+ let newContent;
298
+ if (hasExistingSection) {
299
+ // Replace existing section
300
+ newContent = content.replace(workflowSectionPattern, mermaidDiagram + '\n\n');
301
+ }
302
+ else {
303
+ // Append at end
304
+ newContent = content.trimEnd() + '\n\n' + mermaidDiagram + '\n';
305
+ }
306
+ await fs.writeFile(tasksFile, newContent, 'utf-8');
307
+ }
152
308
  async runBulkValidation(scope, opts) {
153
309
  const spinner = !opts.json && !opts.noInteractive ? ora('Validating...').start() : undefined;
154
310
  const [changeIds, specIds] = await Promise.all([
@@ -13,10 +13,10 @@ export interface DiscoveredSkill {
13
13
  * Discover available skills by scanning priority paths.
14
14
  * Uses short-circuit strategy: once a non-empty directory is found, stops scanning.
15
15
  *
16
- * @param _projectRoot - Project root path (reserved for future project-level skill scanning)
16
+ * @param projectRoot - Project root path for scanning project-local skills directory
17
17
  * @returns Array of discovered skills
18
18
  */
19
- export declare function discoverSkills(_projectRoot?: string): DiscoveredSkill[];
19
+ export declare function discoverSkills(projectRoot?: string): DiscoveredSkill[];
20
20
  /**
21
21
  * Format discovered skills for display.
22
22
  */
@@ -73,15 +73,28 @@ function findSkillFiles(dir) {
73
73
  }
74
74
  return results;
75
75
  }
76
+ /**
77
+ * Build the list of paths to scan for skills.
78
+ * Project-local skills directory is scanned first if projectRoot is provided.
79
+ */
80
+ function buildScanPaths(projectRoot) {
81
+ const paths = [];
82
+ if (projectRoot) {
83
+ paths.push(path.join(projectRoot, 'skills'));
84
+ }
85
+ paths.push(...SCAN_PRIORITY);
86
+ return paths;
87
+ }
76
88
  /**
77
89
  * Discover available skills by scanning priority paths.
78
90
  * Uses short-circuit strategy: once a non-empty directory is found, stops scanning.
79
91
  *
80
- * @param _projectRoot - Project root path (reserved for future project-level skill scanning)
92
+ * @param projectRoot - Project root path for scanning project-local skills directory
81
93
  * @returns Array of discovered skills
82
94
  */
83
- export function discoverSkills(_projectRoot) {
84
- for (const scanPath of SCAN_PRIORITY) {
95
+ export function discoverSkills(projectRoot) {
96
+ const scanPaths = buildScanPaths(projectRoot);
97
+ for (const scanPath of scanPaths) {
85
98
  const resolved = resolvePath(scanPath);
86
99
  // Check if directory exists
87
100
  try {
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Execution Planner
3
+ *
4
+ * Generates wave-based execution plans from parsed tasks.
5
+ * Uses a layer-by-layer approach: each wave contains tasks with in-degree 0.
6
+ */
7
+ import type { ParsedTask, ExecutionPlan } from './types.js';
8
+ /**
9
+ * Generates an execution plan from parsed tasks.
10
+ *
11
+ * Algorithm: Iteratively peel off nodes with in-degree 0 to form waves.
12
+ *
13
+ * @param tasks - Array of parsed tasks
14
+ * @returns Execution plan with waves
15
+ */
16
+ export declare function generateExecutionPlan(tasks: ParsedTask[]): ExecutionPlan;
17
+ //# sourceMappingURL=execution-planner.d.ts.map
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Execution Planner
3
+ *
4
+ * Generates wave-based execution plans from parsed tasks.
5
+ * Uses a layer-by-layer approach: each wave contains tasks with in-degree 0.
6
+ */
7
+ import { TaskGraph } from './task-graph.js';
8
+ /**
9
+ * Generates an execution plan from parsed tasks.
10
+ *
11
+ * Algorithm: Iteratively peel off nodes with in-degree 0 to form waves.
12
+ *
13
+ * @param tasks - Array of parsed tasks
14
+ * @returns Execution plan with waves
15
+ */
16
+ export function generateExecutionPlan(tasks) {
17
+ // Filter out already completed tasks
18
+ const incompleteTasks = tasks.filter(t => !t.completed);
19
+ // Handle empty case
20
+ if (incompleteTasks.length === 0) {
21
+ return {
22
+ waves: [],
23
+ totalTasks: 0,
24
+ parallelTasks: 0,
25
+ serialTasks: 0,
26
+ hasCycle: false,
27
+ errors: [],
28
+ };
29
+ }
30
+ // Create graph and validate
31
+ const graph = new TaskGraph(incompleteTasks);
32
+ const validation = graph.validate();
33
+ // Check for cycles
34
+ if (validation.errors.some(e => e.includes('Circular dependency'))) {
35
+ return {
36
+ waves: [],
37
+ totalTasks: incompleteTasks.length,
38
+ parallelTasks: 0,
39
+ serialTasks: 0,
40
+ hasCycle: true,
41
+ errors: validation.errors,
42
+ };
43
+ }
44
+ // Collect non-cycle errors
45
+ const errors = validation.errors.filter(e => !e.includes('Circular dependency'));
46
+ // Build waves using modified Kahn's algorithm
47
+ const waves = [];
48
+ const completed = new Set();
49
+ // Track in-degrees locally
50
+ const inDegree = new Map();
51
+ const dependents = new Map();
52
+ // Initialize
53
+ for (const task of incompleteTasks) {
54
+ // Only count dependencies that exist in incomplete tasks
55
+ const validDeps = task.depends.filter(d => incompleteTasks.some(t => t.id === d));
56
+ inDegree.set(task.id, validDeps.length);
57
+ dependents.set(task.id, []);
58
+ }
59
+ // Build reverse adjacency
60
+ for (const task of incompleteTasks) {
61
+ for (const dep of task.depends) {
62
+ if (dependents.has(dep)) {
63
+ dependents.get(dep).push(task.id);
64
+ }
65
+ }
66
+ }
67
+ let waveNumber = 0;
68
+ let remainingTasks = incompleteTasks.length;
69
+ while (remainingTasks > 0) {
70
+ waveNumber++;
71
+ // Find all tasks with in-degree 0
72
+ const readyIds = [...inDegree.keys()]
73
+ .filter(id => !completed.has(id) && inDegree.get(id) === 0)
74
+ .sort();
75
+ if (readyIds.length === 0) {
76
+ // Shouldn't happen if no cycles, but handle gracefully
77
+ break;
78
+ }
79
+ const readyTasks = readyIds
80
+ .map(id => incompleteTasks.find(t => t.id === id))
81
+ .filter(Boolean);
82
+ waves.push({
83
+ wave: waveNumber,
84
+ tasks: readyTasks,
85
+ parallel: readyTasks.length > 1,
86
+ });
87
+ // Mark as completed and update in-degrees
88
+ for (const id of readyIds) {
89
+ completed.add(id);
90
+ remainingTasks--;
91
+ // Decrease in-degree of dependents
92
+ for (const dep of dependents.get(id) || []) {
93
+ if (!completed.has(dep)) {
94
+ inDegree.set(dep, inDegree.get(dep) - 1);
95
+ }
96
+ }
97
+ }
98
+ }
99
+ // Calculate statistics
100
+ let parallelTasks = 0;
101
+ let serialTasks = 0;
102
+ for (const wave of waves) {
103
+ if (wave.parallel) {
104
+ parallelTasks += wave.tasks.length;
105
+ }
106
+ else {
107
+ serialTasks += wave.tasks.length;
108
+ }
109
+ }
110
+ return {
111
+ waves,
112
+ totalTasks: incompleteTasks.length,
113
+ parallelTasks,
114
+ serialTasks,
115
+ hasCycle: false,
116
+ errors,
117
+ };
118
+ }
119
+ //# sourceMappingURL=execution-planner.js.map
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Task Graph Module
3
+ *
4
+ * Provides task dependency graph operations for tasks.md processing.
5
+ */
6
+ export type { ParsedTask, ExecutionWave, ExecutionPlan } from './types.js';
7
+ export { parseTasks } from './task-parser.js';
8
+ export { TaskGraph, type ValidationResult } from './task-graph.js';
9
+ export { generateExecutionPlan } from './execution-planner.js';
10
+ export { renderExecutionPlanXml, hasDependsAnnotations } from './xml-renderer.js';
11
+ export { renderMermaidDiagram } from './mermaid-renderer.js';
12
+ export type { MermaidRenderOptions } from './mermaid-renderer.js';
13
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Task Graph Module
3
+ *
4
+ * Provides task dependency graph operations for tasks.md processing.
5
+ */
6
+ // Parser
7
+ export { parseTasks } from './task-parser.js';
8
+ // Graph
9
+ export { TaskGraph } from './task-graph.js';
10
+ // Execution planner
11
+ export { generateExecutionPlan } from './execution-planner.js';
12
+ // XML Renderer
13
+ export { renderExecutionPlanXml, hasDependsAnnotations } from './xml-renderer.js';
14
+ // Mermaid Renderer
15
+ export { renderMermaidDiagram } from './mermaid-renderer.js';
16
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Mermaid Renderer for Execution Plan
3
+ *
4
+ * Renders ExecutionPlan to Mermaid flowchart format for visualization in tasks.md.
5
+ */
6
+ import type { ExecutionPlan, ParsedTask } from './types.js';
7
+ export interface MermaidRenderOptions {
8
+ /** Maximum parallelism threshold. Default: 3 */
9
+ maxParallelism?: number;
10
+ /** Whether to show @skill annotations in node labels. Default: true */
11
+ showSkills?: boolean;
12
+ }
13
+ /**
14
+ * Renders an ExecutionPlan to Mermaid flowchart format.
15
+ *
16
+ * @param plan - The execution plan to render
17
+ * @param tasks - The parsed tasks (used for task details)
18
+ * @param options - Render options
19
+ * @returns Complete markdown section with Mermaid diagram
20
+ */
21
+ export declare function renderMermaidDiagram(plan: ExecutionPlan, tasks: ParsedTask[], options?: MermaidRenderOptions): string;
22
+ //# sourceMappingURL=mermaid-renderer.d.ts.map