7.1 KiB
@alkdev/taskgraph
Directed acyclic graph analysis, risk scoring, and YAML frontmatter parsing for task management.
Built on graphology — pure TypeScript, no native addons, works in Node.js / Deno / Bun.
Install
npm install @alkdev/taskgraph
Quick Start
import { TaskGraph, parallelGroups, criticalPath } from '@alkdev/taskgraph';
const graph = TaskGraph.fromTasks([
{ id: 'design', name: 'Design API', dependsOn: [], risk: 'low', scope: 'narrow' },
{ id: 'impl', name: 'Implement', dependsOn: ['design'], risk: 'medium', scope: 'moderate' },
{ id: 'test', name: 'Write tests', dependsOn: ['impl'], risk: 'low', scope: 'narrow' },
{ id: 'docs', name: 'Write docs', dependsOn: ['design'], risk: 'trivial', scope: 'narrow' },
]);
const groups = parallelGroups(graph);
// [['design'], ['impl', 'docs'], ['test']]
const path = criticalPath(graph);
// ['design', 'impl', 'test']
TaskGraph
The primary data structure. Wraps a graphology DirectedGraph with validation and typed access.
Construction
// From task inputs (convenience — edges created from dependsOn)
const g1 = TaskGraph.fromTasks([...tasks]);
// From explicit tasks + edges (per-edge qualityRetention)
const g2 = TaskGraph.fromRecords([...tasks], [...edges]);
// From serialized data (round-trip with export())
const data = g1.export();
const g3 = TaskGraph.fromJSON(data);
// Incremental building
const g4 = new TaskGraph();
g4.addTask('a', { name: 'Task A' });
g4.addTask('b', { name: 'Task B' });
g4.addDependency('a', 'b'); // a → b (a is prerequisite)
Queries
graph.topologicalOrder(); // string[] — prerequisite → dependent order
graph.dependencies('impl'); // ['design'] — prerequisites of impl
graph.dependents('design'); // ['impl', 'docs'] — dependents of design
graph.hasCycles(); // boolean
graph.findCycles(); // string[][] — cycle paths
graph.taskCount(); // number
graph.getTask('design'); // TaskGraphNodeAttributes | undefined
Validation
graph.validate(); // AnyValidationError[] — combined schema + graph
graph.validateSchema(); // ValidationError[] — per-field TypeBox validation
graph.validateGraph(); // GraphValidationError[] — cycles, dangling refs
Mutation
graph.removeTask('id');
graph.removeDependency('prereq', 'dependent');
graph.updateTask('id', { risk: 'high' });
graph.updateEdgeAttributes('prereq', 'dependent', { qualityRetention: 0.8 });
Export
const data = graph.export(); // TaskGraphSerialized (graphology JSON)
const json = JSON.stringify(graph); // uses toJSON() alias
Analysis Functions
All analysis functions take a TaskGraph instance as their first argument.
Critical Path
import { criticalPath, weightedCriticalPath } from '@alkdev/taskgraph';
criticalPath(graph); // longest path by edge count
weightedCriticalPath(graph, (id, attrs) => {
// custom weight per node
return riskWeight(attrs.risk ?? 'medium') * impactWeight(attrs.impact ?? 'isolated');
});
Parallel Groups
import { parallelGroups } from '@alkdev/taskgraph';
parallelGroups(graph); // string[][] — tasks at each topological depth
Bottleneck Analysis
import { bottlenecks } from '@alkdev/taskgraph';
const scores = bottlenecks(graph);
// [{ taskId: 'design', score: 0.83 }, ...] — sorted descending
Risk Analysis
import { riskPath, riskDistribution } from '@alkdev/taskgraph';
riskPath(graph);
// { path: ['design', 'impl', 'test'], totalRisk: 4.2 }
riskDistribution(graph);
// { trivial: [...], low: [...], medium: [...], high: [...], critical: [...], unspecified: [...] }
Expected Value & Workflow Cost
import { calculateTaskEv, workflowCost } from '@alkdev/taskgraph';
calculateTaskEv(0.8, 3.0, 1.5);
// { ev: 4.2, pSuccess: 0.8, expectedRetries: 0.25 }
const result = workflowCost(graph, {
propagationMode: 'dag-propagate', // or 'independent'
defaultQualityRetention: 0.9,
includeCompleted: false,
});
// result.tasks — per-task EV entries
// result.totalEv — aggregate
// result.averageEv
Decomposition
import { shouldDecomposeTask } from '@alkdev/taskgraph';
shouldDecomposeTask({ name: 'Refactor', risk: 'high', scope: 'broad' });
// { shouldDecompose: true, reasons: ['risk: high — ...', 'scope: broad — ...'] }
Categorical Numeric Methods
import {
scopeCostEstimate, scopeTokenEstimate,
riskSuccessProbability, riskWeight,
impactWeight, resolveDefaults,
} from '@alkdev/taskgraph';
scopeCostEstimate('moderate'); // 3.0
scopeTokenEstimate('broad'); // 6000
riskSuccessProbability('high'); // 0.65
riskWeight('high'); // 0.35
impactWeight('project'); // 3.0
resolveDefaults({ name: 'Task', risk: 'high' });
// { scope: 'narrow', risk: 'high', ..., costEstimate: 2.0, ... }
Frontmatter
Parse and serialize YAML frontmatter in markdown files.
import {
parseFrontmatter, serializeFrontmatter,
parseTaskFile, parseTaskDirectory,
} from '@alkdev/taskgraph';
// Parse a markdown string with --- frontmatter
const task = parseFrontmatter(`---
id: my-task
name: My Task
dependsOn: []
risk: medium
---
Task body here`);
// Serialize back to markdown
const md = serializeFrontmatter(task, 'Task body here');
// File I/O (Node.js only)
const task2 = await parseTaskFile('/path/to/task.md');
const tasks = await parseTaskDirectory('/path/to/tasks/');
Schemas & Types
All schemas are TypeBox schemas and all types are inferred from them.
import type {
TaskInput, DependencyEdge,
TaskGraphNodeAttributes, TaskGraphEdgeAttributes, TaskGraphSerialized,
RiskPathResult, DecomposeResult, WorkflowCostOptions, WorkflowCostResult,
EvConfig, EvResult, RiskDistributionResult, ResolvedTaskAttributes,
} from '@alkdev/taskgraph';
import type {
TaskScope, TaskRisk, TaskImpact, TaskLevel, TaskPriority, TaskStatus,
} from '@alkdev/taskgraph';
Enums
// Type values (also usable as TypeScript types)
type Scope = 'single' | 'narrow' | 'moderate' | 'broad' | 'system';
type Risk = 'trivial' | 'low' | 'medium' | 'high' | 'critical';
type Impact = 'isolated' | 'component' | 'phase' | 'project';
type Level = 'planning' | 'decomposition' | 'implementation' | 'review' | 'research';
type Priority = 'low' | 'medium' | 'high' | 'critical';
type Status = 'pending' | 'in-progress' | 'completed' | 'failed' | 'blocked';
Error Classes
import {
TaskgraphError, // base class
TaskNotFoundError, // .taskId
CircularDependencyError, // .cycles: string[][]
InvalidInputError, // .field, .message
DuplicateNodeError, // .taskId
DuplicateEdgeError, // .prerequisite, .dependent
} from '@alkdev/taskgraph';
License
Licensed under either of Apache License, Version 2.0 or MIT License at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.