Files
taskgraph_ts/README.md

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.