@webpresso/agent-kit 0.21.3 → 0.21.5

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 (99) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/README.md +105 -41
  4. package/catalog/AGENTS.md.tpl +3 -1
  5. package/catalog/agent/rules/changeset-release.md +13 -16
  6. package/catalog/agent/skills/plan-refine/SKILL.md +5 -4
  7. package/catalog/base-kit/commitlint.config.ts.tmpl +1 -3
  8. package/catalog/base-kit/e2e/fixtures/smoke.html.tmpl +13 -0
  9. package/catalog/base-kit/e2e/smoke.spec.ts.tmpl +13 -0
  10. package/catalog/base-kit/oxlint.config.ts.tmpl +26 -0
  11. package/catalog/base-kit/playwright.config.ts.tmpl +10 -0
  12. package/catalog/base-kit/src/quality-sample.test.ts.tmpl +19 -0
  13. package/catalog/base-kit/src/quality-sample.ts.tmpl +11 -0
  14. package/catalog/base-kit/stryker.config.ts.tmpl +14 -0
  15. package/catalog/base-kit/tsconfig.json.tmpl +9 -0
  16. package/catalog/base-kit/vitest.config.ts.tmpl +10 -0
  17. package/catalog/docs/templates/adr.md +1 -1
  18. package/catalog/docs/templates/blueprint.md +1 -0
  19. package/catalog/docs/templates/blueprint.yaml +6 -3
  20. package/catalog/docs/templates/guide.md +1 -1
  21. package/catalog/docs/templates/postmortem.md +1 -1
  22. package/catalog/docs/templates/research.md +1 -1
  23. package/catalog/docs/templates/runbook.md +1 -1
  24. package/catalog/docs/templates/system.md +12 -3
  25. package/catalog/docs/templates/tech-debt.md +1 -0
  26. package/commands/blueprint.md +37 -4
  27. package/dist/esm/audit/resolve-audit-script.d.ts +24 -0
  28. package/dist/esm/audit/resolve-audit-script.js +27 -0
  29. package/dist/esm/blueprint/db/enums.d.ts +1 -1
  30. package/dist/esm/blueprint/index.d.ts +0 -1
  31. package/dist/esm/blueprint/index.js +0 -2
  32. package/dist/esm/blueprint/local.d.ts +0 -3
  33. package/dist/esm/blueprint/local.js +0 -2
  34. package/dist/esm/blueprint/service/BlueprintCreationService.js +5 -2
  35. package/dist/esm/blueprint/utils/package-assets.d.ts +11 -0
  36. package/dist/esm/blueprint/utils/package-assets.js +33 -4
  37. package/dist/esm/build/sync-catalog-doc-templates.d.ts +23 -0
  38. package/dist/esm/build/sync-catalog-doc-templates.js +93 -0
  39. package/dist/esm/cli/commands/audit.js +2 -7
  40. package/dist/esm/cli/commands/blueprint/router.js +5 -2
  41. package/dist/esm/cli/commands/blueprint/template-resolver.js +8 -4
  42. package/dist/esm/cli/commands/init/host-visibility.js +4 -2
  43. package/dist/esm/cli/commands/init/index.js +46 -7
  44. package/dist/esm/cli/commands/init/scaffold-base-kit.d.ts +12 -0
  45. package/dist/esm/cli/commands/init/scaffold-base-kit.js +141 -6
  46. package/dist/esm/cli/commands/typecheck.js +10 -4
  47. package/dist/esm/e2e/command-builder.js +26 -7
  48. package/dist/esm/e2e/execution.js +4 -0
  49. package/dist/esm/e2e/run-planner.js +1 -0
  50. package/dist/esm/e2e/types.d.ts +1 -0
  51. package/dist/esm/format/index.js +7 -1
  52. package/dist/esm/lint/index.js +3 -1
  53. package/dist/esm/mcp/blueprint-server.js +361 -66
  54. package/dist/esm/mcp/tools/audit.js +2 -8
  55. package/dist/esm/mcp/tools/e2e.d.ts +1 -1
  56. package/dist/esm/package.json +3 -0
  57. package/dist/esm/secret-gate/runner.js +4 -0
  58. package/dist/esm/test/command-builder.d.ts +1 -0
  59. package/dist/esm/test/command-builder.js +8 -2
  60. package/dist/esm/test-helpers/hermetic-env.d.ts +25 -0
  61. package/dist/esm/test-helpers/hermetic-env.js +31 -0
  62. package/dist/esm/tool-runtime/index.d.ts +5 -0
  63. package/dist/esm/tool-runtime/index.js +23 -0
  64. package/dist/esm/tool-runtime/resolve-runner.d.ts +13 -0
  65. package/dist/esm/tool-runtime/resolve-runner.js +40 -0
  66. package/package.json +12 -18
  67. package/skills/plan-refine/SKILL.md +5 -4
  68. package/dist/esm/blueprint/dag/cycle-detector.d.ts +0 -12
  69. package/dist/esm/blueprint/dag/cycle-detector.js +0 -46
  70. package/dist/esm/blueprint/dag/executor.d.ts +0 -140
  71. package/dist/esm/blueprint/dag/executor.js +0 -292
  72. package/dist/esm/blueprint/dag/index.d.ts +0 -20
  73. package/dist/esm/blueprint/dag/index.js +0 -17
  74. package/dist/esm/blueprint/dag/interfaces.d.ts +0 -56
  75. package/dist/esm/blueprint/dag/interfaces.js +0 -13
  76. package/dist/esm/blueprint/dag/local/independence.d.ts +0 -107
  77. package/dist/esm/blueprint/dag/local/independence.js +0 -231
  78. package/dist/esm/blueprint/dag/local/index.d.ts +0 -14
  79. package/dist/esm/blueprint/dag/local/index.js +0 -14
  80. package/dist/esm/blueprint/dag/local/package-graph.d.ts +0 -66
  81. package/dist/esm/blueprint/dag/local/package-graph.js +0 -148
  82. package/dist/esm/blueprint/dag/plan-parser.d.ts +0 -54
  83. package/dist/esm/blueprint/dag/plan-parser.js +0 -236
  84. package/dist/esm/blueprint/dag/task-graph-algorithms.d.ts +0 -13
  85. package/dist/esm/blueprint/dag/task-graph-algorithms.js +0 -236
  86. package/dist/esm/blueprint/dag/task-graph.d.ts +0 -171
  87. package/dist/esm/blueprint/dag/task-graph.js +0 -370
  88. package/dist/esm/blueprint/dag/types.d.ts +0 -17
  89. package/dist/esm/blueprint/dag/types.js +0 -2
  90. package/dist/esm/blueprint/graph/index.d.ts +0 -5
  91. package/dist/esm/blueprint/graph/index.js +0 -5
  92. package/dist/esm/blueprint/graph/mermaid-parser.d.ts +0 -3
  93. package/dist/esm/blueprint/graph/mermaid-parser.js +0 -93
  94. package/dist/esm/blueprint/graph/mermaid-serializer.d.ts +0 -3
  95. package/dist/esm/blueprint/graph/mermaid-serializer.js +0 -20
  96. package/dist/esm/blueprint/graph/schema.d.ts +0 -89
  97. package/dist/esm/blueprint/graph/schema.js +0 -104
  98. package/dist/esm/blueprint/graph/task-graph-adapter.d.ts +0 -6
  99. package/dist/esm/blueprint/graph/task-graph-adapter.js +0 -30
@@ -1,54 +0,0 @@
1
- import type { Task } from './types.js';
2
- /**
3
- * Parsed task from an implementation plan
4
- */
5
- export interface PlanTask {
6
- id: string;
7
- title: string;
8
- description: string;
9
- type: 'lint-fix' | 'typecheck-fix' | 'test-fix' | 'implement' | 'research' | 'verify';
10
- package?: string;
11
- file?: string;
12
- dependsOn: string[];
13
- metadata?: Record<string, unknown>;
14
- }
15
- /**
16
- * Plan parsing result
17
- */
18
- export interface ParsedPlan {
19
- title: string;
20
- tasks: PlanTask[];
21
- metadata: {
22
- totalTasks: number;
23
- maxParallelism: number;
24
- criticalPathLength: number;
25
- };
26
- }
27
- /**
28
- * Parse implementation plan markdown into structured tasks.
29
- *
30
- * Supports formats:
31
- * - Numbered lists with dependencies: `1. [depends: 2,3] Task description`
32
- * - Task blocks with metadata
33
- * - Checkbox lists: `- [ ] Task description`
34
- *
35
- * @example
36
- * ```markdown
37
- * # Implementation Plan
38
- *
39
- * ## Tasks
40
- * 1. Fix lint errors in cli2
41
- * 2. [depends: 1] Fix typecheck errors in cli2
42
- * 3. [depends: 1] Fix typecheck errors in schema-engine
43
- * 4. [depends: 2,3] Run full test suite
44
- * ```
45
- */
46
- export declare function parsePlan(markdown: string): ParsedPlan;
47
- /**
48
- * Convert parsed plan tasks to Task format for executor
49
- */
50
- export declare function planTasksToGraphTasks(planTasks: PlanTask[]): Array<{
51
- task: Task<PlanTask>;
52
- dependsOn?: string[];
53
- }>;
54
- //# sourceMappingURL=plan-parser.d.ts.map
@@ -1,236 +0,0 @@
1
- /** Pattern for numbered list format: `1. [depends: 2,3] Task description` */
2
- const TASK_PATTERN = /^(\d+)\.\s*(?:\[depends:\s*([^\]]+)\])?\s*(.+)$/;
3
- /** Pattern for checkbox format: `- [ ] [depends: 1] Task description` */
4
- const CHECKBOX_PATTERN = /^-\s*\[[ x]\]\s*(?:\[depends:\s*([^\]]+)\])?\s*(.+)$/i;
5
- /**
6
- * Extract title from markdown heading
7
- */
8
- function extractTitle(lines) {
9
- for (const line of lines) {
10
- if (line.startsWith('# ')) {
11
- return line.slice(2).trim();
12
- }
13
- }
14
- return '';
15
- }
16
- /**
17
- * Parse dependencies string into array
18
- */
19
- function parseDependencies(deps) {
20
- return deps ? deps.split(',').map((d) => d.trim()) : [];
21
- }
22
- /**
23
- * Try to parse a numbered task from a line
24
- */
25
- function parseNumberedTask(line) {
26
- const match = line.match(TASK_PATTERN);
27
- if (!match)
28
- return null;
29
- const [, num, deps, desc] = match;
30
- if (!num || !desc)
31
- return null;
32
- return {
33
- id: num,
34
- title: desc,
35
- description: desc,
36
- type: inferTaskType(desc),
37
- dependsOn: parseDependencies(deps),
38
- ...extractTaskMetadata(desc),
39
- };
40
- }
41
- /**
42
- * Try to parse a checkbox task from a line
43
- */
44
- function parseCheckboxTask(line, index) {
45
- const match = line.match(CHECKBOX_PATTERN);
46
- if (!match)
47
- return null;
48
- const [, deps, desc] = match;
49
- if (!desc)
50
- return null;
51
- return {
52
- id: String(index),
53
- title: desc,
54
- description: desc,
55
- type: inferTaskType(desc),
56
- dependsOn: parseDependencies(deps),
57
- ...extractTaskMetadata(desc),
58
- };
59
- }
60
- /**
61
- * Parse all tasks from markdown lines
62
- */
63
- function parseAllTasks(lines) {
64
- const tasks = [];
65
- let checkboxIndex = 0;
66
- for (const line of lines) {
67
- const trimmed = line.trim();
68
- const numberedTask = parseNumberedTask(trimmed);
69
- if (numberedTask) {
70
- tasks.push(numberedTask);
71
- continue;
72
- }
73
- checkboxIndex++;
74
- const checkboxTask = parseCheckboxTask(trimmed, checkboxIndex);
75
- if (checkboxTask) {
76
- tasks.push(checkboxTask);
77
- }
78
- }
79
- return tasks;
80
- }
81
- /**
82
- * Parse implementation plan markdown into structured tasks.
83
- *
84
- * Supports formats:
85
- * - Numbered lists with dependencies: `1. [depends: 2,3] Task description`
86
- * - Task blocks with metadata
87
- * - Checkbox lists: `- [ ] Task description`
88
- *
89
- * @example
90
- * ```markdown
91
- * # Implementation Plan
92
- *
93
- * ## Tasks
94
- * 1. Fix lint errors in cli2
95
- * 2. [depends: 1] Fix typecheck errors in cli2
96
- * 3. [depends: 1] Fix typecheck errors in schema-engine
97
- * 4. [depends: 2,3] Run full test suite
98
- * ```
99
- */
100
- export function parsePlan(markdown) {
101
- const lines = markdown.split('\n');
102
- const title = extractTitle(lines);
103
- const tasks = parseAllTasks(lines);
104
- return {
105
- title,
106
- tasks,
107
- metadata: {
108
- totalTasks: tasks.length,
109
- maxParallelism: calculateMaxParallelism(tasks),
110
- criticalPathLength: calculateCriticalPath(tasks),
111
- },
112
- };
113
- }
114
- /**
115
- * Task type detection rules - keywords map to task types
116
- */
117
- const TASK_TYPE_RULES = [
118
- { keywords: ['lint', 'biome'], type: 'lint-fix' },
119
- { keywords: ['type', 'tsc', 'tsgo'], type: 'typecheck-fix' },
120
- { keywords: ['test', 'vitest'], type: 'test-fix' },
121
- { keywords: ['research', 'investigate'], type: 'research' },
122
- { keywords: ['verify', 'check'], type: 'verify' },
123
- ];
124
- /**
125
- * Check if description contains any of the keywords
126
- */
127
- function matchesKeywords(lower, keywords) {
128
- return keywords.some((kw) => lower.includes(kw));
129
- }
130
- /**
131
- * Infer task type from description
132
- */
133
- function inferTaskType(desc) {
134
- const lower = desc.toLowerCase();
135
- for (const rule of TASK_TYPE_RULES) {
136
- if (matchesKeywords(lower, rule.keywords)) {
137
- return rule.type;
138
- }
139
- }
140
- return 'implement';
141
- }
142
- /**
143
- * Extract package and file from task description
144
- */
145
- function extractTaskMetadata(desc) {
146
- const result = {};
147
- // Extract package name: "in cli2", "for schema-engine", "@myorg/cli2"
148
- const pkgPatterns = [
149
- /\bin\s+(?:@myorg\/)?(\w[\w-]*)/i,
150
- /\bfor\s+(?:@myorg\/)?(\w[\w-]*)/i,
151
- /@myorg\/([\w-]+)/,
152
- ];
153
- for (const pattern of pkgPatterns) {
154
- const match = desc.match(pattern);
155
- if (match) {
156
- result.package = match[1];
157
- break;
158
- }
159
- }
160
- // Extract file path
161
- const filePattern = /\b([\w/.-]+\.(?:ts|tsx|js|jsx|json|md))\b/;
162
- const fileMatch = desc.match(filePattern);
163
- if (fileMatch) {
164
- result.file = fileMatch[1];
165
- }
166
- return result;
167
- }
168
- /**
169
- * Calculate maximum parallelism (tasks with no dependencies)
170
- */
171
- function calculateMaxParallelism(tasks) {
172
- // Group by dependency depth
173
- const depths = new Map();
174
- function getDepth(taskId) {
175
- const cached = depths.get(taskId);
176
- if (cached !== undefined)
177
- return cached;
178
- const task = tasks.find((t) => t.id === taskId);
179
- if (!task || !task.dependsOn.length) {
180
- depths.set(taskId, 0);
181
- return 0;
182
- }
183
- const maxDepDep = Math.max(...task.dependsOn.map(getDepth));
184
- const depth = maxDepDep + 1;
185
- depths.set(taskId, depth);
186
- return depth;
187
- }
188
- // Calculate depths for all tasks
189
- for (const task of tasks) {
190
- getDepth(task.id);
191
- }
192
- // Count tasks at each depth level
193
- const levelCounts = new Map();
194
- for (const depth of depths.values()) {
195
- levelCounts.set(depth, (levelCounts.get(depth) ?? 0) + 1);
196
- }
197
- return Math.max(...levelCounts.values(), 0);
198
- }
199
- /**
200
- * Calculate critical path length (longest dependency chain)
201
- */
202
- function calculateCriticalPath(tasks) {
203
- const depths = new Map();
204
- function getDepth(taskId) {
205
- const cached = depths.get(taskId);
206
- if (cached !== undefined)
207
- return cached;
208
- const task = tasks.find((t) => t.id === taskId);
209
- if (!task || !task.dependsOn.length) {
210
- depths.set(taskId, 1);
211
- return 1;
212
- }
213
- const maxDepDep = Math.max(...task.dependsOn.map(getDepth));
214
- const depth = maxDepDep + 1;
215
- depths.set(taskId, depth);
216
- return depth;
217
- }
218
- for (const task of tasks) {
219
- getDepth(task.id);
220
- }
221
- return Math.max(...depths.values(), 0);
222
- }
223
- /**
224
- * Convert parsed plan tasks to Task format for executor
225
- */
226
- export function planTasksToGraphTasks(planTasks) {
227
- return planTasks.map((pt) => ({
228
- task: {
229
- id: pt.id,
230
- data: pt,
231
- dependencies: pt.dependsOn,
232
- },
233
- dependsOn: pt.dependsOn.length > 0 ? pt.dependsOn : undefined,
234
- }));
235
- }
236
- //# sourceMappingURL=plan-parser.js.map
@@ -1,13 +0,0 @@
1
- import type { GraphStats, ValidationResult } from './interfaces.js';
2
- import type { Task, TaskNode } from './types.js';
3
- type NodesMap<T> = Map<string, TaskNode<T>>;
4
- type EdgesMap = Map<string, Set<string>>;
5
- export declare function topologicalSortTasksInput<T>(tasks: Task<T>[], taskMap: Map<string, Task<T>>): Task<T>[];
6
- export declare function getTopologicalOrderForGraph<T>(nodes: NodesMap<T>, edges: EdgesMap): Task<T>[];
7
- export declare function detectCyclesInGraph<T>(nodes: NodesMap<T>, edges: EdgesMap): string[][] | null;
8
- export declare function getCriticalPathForGraph<T>(nodes: NodesMap<T>, edges: EdgesMap): Task<T>[];
9
- export declare function getWavesForGraph<T>(nodes: NodesMap<T>, edges: EdgesMap): Task<T>[][];
10
- export declare function validateGraph<T>(nodes: NodesMap<T>, edges: EdgesMap, reverseEdges: EdgesMap): ValidationResult;
11
- export declare function getGraphStatsForGraph<T>(nodes: NodesMap<T>, edges: EdgesMap): GraphStats;
12
- export {};
13
- //# sourceMappingURL=task-graph-algorithms.d.ts.map
@@ -1,236 +0,0 @@
1
- import { CycleDetector } from './cycle-detector.js';
2
- export function topologicalSortTasksInput(tasks, taskMap) {
3
- const visited = new Set();
4
- const visiting = new Set();
5
- const result = [];
6
- for (const task of tasks) {
7
- visitTask(task, taskMap, visited, visiting, result);
8
- }
9
- return result;
10
- }
11
- function visitTask(task, taskMap, visited, visiting, result) {
12
- if (visited.has(task.id))
13
- return;
14
- if (visiting.has(task.id)) {
15
- throw new Error(`Circular dependency detected involving task "${task.id}"`);
16
- }
17
- visiting.add(task.id);
18
- for (const depId of task.dependencies) {
19
- const dep = taskMap.get(depId);
20
- if (dep)
21
- visitTask(dep, taskMap, visited, visiting, result);
22
- }
23
- visiting.delete(task.id);
24
- visited.add(task.id);
25
- result.push(task);
26
- }
27
- function initializeInDegrees(nodes) {
28
- const inDegree = new Map();
29
- for (const [id, node] of nodes) {
30
- inDegree.set(id, node.inDegree);
31
- }
32
- return inDegree;
33
- }
34
- function findZeroInDegreeNodes(inDegree) {
35
- const queue = [];
36
- for (const [id, degree] of inDegree) {
37
- if (degree === 0) {
38
- queue.push(id);
39
- }
40
- }
41
- return queue;
42
- }
43
- function processTopologicalNode(taskId, nodes, edges, inDegree, queue) {
44
- const node = nodes.get(taskId);
45
- if (!node)
46
- return null;
47
- const dependents = edges.get(taskId);
48
- if (dependents) {
49
- for (const dependentId of dependents) {
50
- const currentDegree = inDegree.get(dependentId) ?? 0;
51
- inDegree.set(dependentId, currentDegree - 1);
52
- if (inDegree.get(dependentId) === 0) {
53
- queue.push(dependentId);
54
- }
55
- }
56
- }
57
- return node.task;
58
- }
59
- export function getTopologicalOrderForGraph(nodes, edges) {
60
- const result = [];
61
- const inDegree = initializeInDegrees(nodes);
62
- const queue = findZeroInDegreeNodes(inDegree);
63
- while (queue.length > 0) {
64
- const taskId = queue.shift();
65
- if (!taskId)
66
- break;
67
- const task = processTopologicalNode(taskId, nodes, edges, inDegree, queue);
68
- if (task) {
69
- result.push(task);
70
- }
71
- }
72
- if (result.length !== nodes.size) {
73
- throw new Error('Circular dependency detected');
74
- }
75
- return result;
76
- }
77
- export function detectCyclesInGraph(nodes, edges) {
78
- const detector = new CycleDetector(edges);
79
- return detector.detect(nodes.keys());
80
- }
81
- function initializeDepthMaps(nodes) {
82
- const depth = new Map();
83
- const parent = new Map();
84
- for (const nodeId of nodes.keys()) {
85
- depth.set(nodeId, 0);
86
- parent.set(nodeId, null);
87
- }
88
- return { depth, parent };
89
- }
90
- function updateNeighborDepths(task, currentDepth, edges, depth, parent) {
91
- const taskEdges = edges.get(task.id);
92
- if (!taskEdges)
93
- return;
94
- for (const neighbor of taskEdges) {
95
- const newDepth = currentDepth + 1;
96
- if (newDepth > (depth.get(neighbor) ?? 0)) {
97
- depth.set(neighbor, newDepth);
98
- parent.set(neighbor, task.id);
99
- }
100
- }
101
- }
102
- function calculateDepths(nodes, edges, topOrder) {
103
- const { depth, parent } = initializeDepthMaps(nodes);
104
- for (const task of topOrder) {
105
- const currentDepth = depth.get(task.id) ?? 0;
106
- updateNeighborDepths(task, currentDepth, edges, depth, parent);
107
- }
108
- return { depth, parent };
109
- }
110
- function findDeepestNode(depth) {
111
- let maxDepth = -1;
112
- let endNode = null;
113
- for (const [nodeId, nodeDepth] of depth) {
114
- if (nodeDepth > maxDepth) {
115
- maxDepth = nodeDepth;
116
- endNode = nodeId;
117
- }
118
- }
119
- return endNode;
120
- }
121
- function reconstructPath(nodes, endNode, parent) {
122
- const path = [];
123
- let current = endNode;
124
- while (current !== null) {
125
- const node = nodes.get(current);
126
- if (node) {
127
- path.unshift(node.task);
128
- }
129
- current = parent.get(current) ?? null;
130
- }
131
- return path;
132
- }
133
- export function getCriticalPathForGraph(nodes, edges) {
134
- if (nodes.size === 0) {
135
- return [];
136
- }
137
- if (nodes.size === 1) {
138
- const task = nodes.values().next().value?.task;
139
- return task ? [task] : [];
140
- }
141
- const topOrder = getTopologicalOrderForGraph(nodes, edges);
142
- const { depth, parent } = calculateDepths(nodes, edges, topOrder);
143
- const endNode = findDeepestNode(depth);
144
- return reconstructPath(nodes, endNode, parent);
145
- }
146
- function processNodeDependents(node, edges, inDegree, nextQueue) {
147
- const dependents = edges.get(node.task.id);
148
- if (dependents) {
149
- for (const dependentId of dependents) {
150
- const currentDegree = inDegree.get(dependentId) ?? 0;
151
- inDegree.set(dependentId, currentDegree - 1);
152
- if (inDegree.get(dependentId) === 0) {
153
- nextQueue.push(dependentId);
154
- }
155
- }
156
- }
157
- }
158
- function processWave(nodes, edges, queue, inDegree) {
159
- const wave = [];
160
- const nextQueue = [];
161
- for (const taskId of queue) {
162
- const node = nodes.get(taskId);
163
- if (!node)
164
- continue;
165
- wave.push(node.task);
166
- processNodeDependents(node, edges, inDegree, nextQueue);
167
- }
168
- return { wave, nextQueue };
169
- }
170
- export function getWavesForGraph(nodes, edges) {
171
- const waves = [];
172
- const inDegree = initializeInDegrees(nodes);
173
- let queue = findZeroInDegreeNodes(inDegree);
174
- while (queue.length > 0) {
175
- const { wave, nextQueue } = processWave(nodes, edges, queue, inDegree);
176
- waves.push(wave);
177
- queue = nextQueue;
178
- }
179
- return waves;
180
- }
181
- function getIsolatedNodes(nodes, edges) {
182
- if (nodes.size <= 1)
183
- return [];
184
- const isolatedNodes = [];
185
- for (const [id, node] of nodes) {
186
- const hasIncoming = node.inDegree > 0;
187
- const hasOutgoing = (edges.get(id)?.size ?? 0) > 0;
188
- if (!hasIncoming && !hasOutgoing) {
189
- isolatedNodes.push(id);
190
- }
191
- }
192
- return isolatedNodes;
193
- }
194
- export function validateGraph(nodes, edges, reverseEdges) {
195
- const cycles = detectCyclesInGraph(nodes, edges);
196
- const errors = cycles?.length
197
- ? cycles.map((cycle) => `Circular dependency: ${cycle.join(' -> ')}`)
198
- : [];
199
- const warnings = getIsolatedNodes(nodes, edges).map((id) => `Task "${id}" is isolated (no dependencies or dependents)`);
200
- for (const [id, node] of nodes) {
201
- const actualDeps = reverseEdges.get(id) ?? new Set();
202
- for (const dep of node.task.dependencies) {
203
- if (!actualDeps.has(dep)) {
204
- warnings.push(`Task "${id}" declares dependency on "${dep}" but it's not wired in the graph`);
205
- }
206
- }
207
- }
208
- return {
209
- valid: !errors.length,
210
- errors,
211
- warnings,
212
- };
213
- }
214
- export function getGraphStatsForGraph(nodes, edges) {
215
- const waves = getWavesForGraph(nodes, edges);
216
- const isolatedNodes = getIsolatedNodes(nodes, edges);
217
- let edgeCount = 0;
218
- for (const edgeSet of edges.values()) {
219
- edgeCount += edgeSet.size;
220
- }
221
- let maxWidth = 0;
222
- for (const wave of waves) {
223
- maxWidth = Math.max(maxWidth, wave.length);
224
- }
225
- const cycles = detectCyclesInGraph(nodes, edges);
226
- return {
227
- nodeCount: nodes.size,
228
- edgeCount,
229
- maxDepth: getCriticalPathForGraph(nodes, edges).length,
230
- maxWidth,
231
- waveCount: waves.length,
232
- hasCycles: cycles !== null && cycles.length > 0,
233
- isolatedNodes,
234
- };
235
- }
236
- //# sourceMappingURL=task-graph-algorithms.js.map
@@ -1,171 +0,0 @@
1
- import type { GraphStats, ValidationResult } from './interfaces.js';
2
- import type { Task } from './types.js';
3
- import { CycleDetector } from './cycle-detector.js';
4
- /**
5
- * DAG (Directed Acyclic Graph) analysis for parallel task execution.
6
- *
7
- * Provides:
8
- * - Topological sorting (Kahn's Algorithm)
9
- * - Cycle detection (DFS)
10
- * - Critical path analysis (longest dependency chain)
11
- * - Wave grouping (parallel execution batches)
12
- * - Parallelization metrics
13
- * - Validation and statistics
14
- *
15
- * Zero dependencies. Works with Bun, Node.js, Deno.
16
- *
17
- * @template T - The type of additional data attached to tasks
18
- */
19
- export declare class TaskGraph<T = unknown> {
20
- private nodes;
21
- private edges;
22
- private reverseEdges;
23
- constructor();
24
- /**
25
- * Add a task to the graph.
26
- * Must be called before adding dependencies.
27
- */
28
- addTask(task: Task<T>): void;
29
- /**
30
- * Add a task and automatically wire up its dependencies.
31
- * This is the preferred method for LLM agents - single call instead of addTask + addDependency.
32
- *
33
- * @param task - Task with dependencies array populated
34
- * @throws {Error} If task already exists or dependencies reference non-existent tasks
35
- */
36
- addTaskWithDependencies(task: Task<T>): void;
37
- /**
38
- * Bulk add tasks with automatic dependency wiring.
39
- * Tasks are sorted by dependency depth before adding.
40
- *
41
- * @param tasks - Array of tasks to add
42
- * @throws {Error} If circular dependencies detected or tasks reference non-existent dependencies
43
- */
44
- addTasksWithDependencies(tasks: Task<T>[]): void;
45
- /**
46
- * Add a dependency between two tasks.
47
- * @param from - The task that must complete first
48
- * @param to - The task that depends on `from`
49
- * @throws {Error} If tasks don't exist, self-loop detected
50
- */
51
- addDependency(from: string, to: string): void;
52
- private validateDependencyNodes;
53
- private edgeExists;
54
- private addForwardEdge;
55
- private addReverseEdge;
56
- private incrementInDegree;
57
- /**
58
- * Remove a dependency between two tasks.
59
- * @param from - The source task
60
- * @param to - The dependent task
61
- * @returns true if dependency was removed, false if it didn't exist
62
- */
63
- removeDependency(from: string, to: string): boolean;
64
- /**
65
- * Get the in-degree (number of dependencies) for a task.
66
- */
67
- getInDegree(taskId: string): number;
68
- /**
69
- * Get the out-degree (number of dependents) for a task.
70
- */
71
- getOutDegree(taskId: string): number;
72
- /**
73
- * Get all task IDs in the graph.
74
- */
75
- getTaskIds(): string[];
76
- /**
77
- * Get a task by ID.
78
- */
79
- getTask(taskId: string): Task<T> | undefined;
80
- /**
81
- * Check if a task exists in the graph.
82
- */
83
- hasTask(taskId: string): boolean;
84
- /**
85
- * Get the number of tasks in the graph.
86
- */
87
- get size(): number;
88
- /**
89
- * Get the number of edges in the graph.
90
- */
91
- get edgeCount(): number;
92
- /**
93
- * Validate the graph structure.
94
- * Checks for cycles, missing dependencies, and other issues.
95
- */
96
- validate(): ValidationResult;
97
- /**
98
- * Get comprehensive statistics about the graph.
99
- */
100
- getStats(): GraphStats;
101
- /**
102
- * Get tasks in topological order (Kahn's Algorithm).
103
- * Tasks appear before their dependents.
104
- * @throws {Error} If circular dependency detected
105
- */
106
- getTopologicalOrder(): Task<T>[];
107
- /**
108
- * Detect circular dependencies using DFS.
109
- * @returns Array of cycles (each cycle is an array of task IDs), or null if acyclic
110
- */
111
- detectCycles(): string[][] | null;
112
- /**
113
- * Check if the graph contains any cycles.
114
- * @returns true if cycles exist, false if acyclic
115
- */
116
- hasCycle(): boolean;
117
- /**
118
- * Get the critical path (longest dependency chain).
119
- * This is the sequence of tasks that determines the minimum execution time.
120
- *
121
- * IMPORTANT: For optimal parallel scheduling, the invariant |waves| = |critical_path| must hold.
122
- *
123
- * Returns:
124
- * - Empty array for empty graph
125
- * - Single task for single-node graph (critical path length = 1)
126
- * - Single task if all tasks are independent (critical path length = 1, same as wave count)
127
- * - Longest dependency chain for graphs with dependencies
128
- */
129
- getCriticalPath(): Task<T>[];
130
- /**
131
- * Get the maximum number of tasks that can run in parallel.
132
- */
133
- getMaxParallelWidth(): number;
134
- /**
135
- * Group tasks into waves (batches) for parallel execution.
136
- * Tasks in the same wave have no dependencies on each other.
137
- * Each wave must complete before the next wave starts.
138
- */
139
- getWaves(): Task<T>[][];
140
- /**
141
- * Get direct dependencies of a task.
142
- */
143
- getDependencies(taskId: string): string[];
144
- /**
145
- * Get direct dependents of a task (tasks that depend on this one).
146
- */
147
- getDependents(taskId: string): string[];
148
- /**
149
- * Get all transitive dependencies of a task (including indirect).
150
- */
151
- getTransitiveDependencies(taskId: string): string[];
152
- /**
153
- * Get all transitive dependents of a task (including indirect).
154
- */
155
- getTransitiveDependents(taskId: string): string[];
156
- /**
157
- * Create a subgraph containing only the specified tasks and their edges.
158
- */
159
- subgraph(taskIds: string[]): TaskGraph<T>;
160
- private copyTasksToSubgraph;
161
- private copyEdgesToSubgraph;
162
- private copyNodeEdgesToSubgraph;
163
- /**
164
- * Clone the graph.
165
- */
166
- clone(): TaskGraph<T>;
167
- private copyAllTasks;
168
- private copyAllEdges;
169
- }
170
- export { CycleDetector };
171
- //# sourceMappingURL=task-graph.d.ts.map