Files
skills/docs/devtools/tooling/overview/src/graph/graph.md
2026-05-29 08:20:56 +01:00

3.2 KiB

graph/graph.ts

(no submodules)

Purpose

Implements the graph utilities that operate on a WorkspaceGraph built from a list of PackageRecords. Covers the analyses every project tool eventually needs: build a forward / reverse adjacency map, compute a topological dependency-first order, detect cycles, gather transitive deps or dependents, and identify graph roots (no deps) and leaves (no dependents).

Edges where the target is not also a node are silently dropped at build time. This is the same posture as scripts/folder-reorg/generate-manifest.ts — necessary because Suaveplan migrates in waves and partial-manifest snapshots are routine.

Features

  • buildGraph — populates forward and reverse adjacency in one pass; emits overview.graph_builds_total
  • topologicalOrder — Kahn's algorithm with stable lexicographic tie-breaking; throws OVERVIEW.CYCLE_DETECTED on a cycle (error carries context.cycle)
  • detectCycles — Tarjan's SCC, returns every cycle as a sorted string[]
  • transitiveDependencies / transitiveDependents — BFS over the appropriate direction; throws OVERVIEW.UNKNOWN_PACKAGE for missing inputs
  • leafPackages — no dependents (no incoming edges)
  • rootPackages — no deps (no outgoing edges)

Usage: Basic

import { buildGraph, topologicalOrder } from "@suaveplan/overview";

const graph = buildGraph(records);
const order = topologicalOrder(graph);
// `order[0]` has no workspace deps; the last entry depends on everything.

Usage: Advanced

import {
  buildGraph,
  detectCycles,
  transitiveDependencies,
  transitiveDependents,
  leafPackages,
  rootPackages,
} from "@suaveplan/overview";

const graph = buildGraph(records);
const cycles = detectCycles(graph);
if (cycles.length > 0) {
  console.error("Cycles detected:", cycles);
}

const blastRadius = transitiveDependents(graph, "@suaveplan/error");
const supplyChain = transitiveDependencies(graph, "@suaveplan/api");
const leaves = leafPackages(graph);
const roots = rootPackages(graph);

topologicalOrder is deterministic — two callers with the same graph see the same order — so it can drive reproducible build plans and CI fan-outs.

API Reference

  • buildGraph(records): WorkspaceGraph
  • topologicalOrder(graph): string[] — throws OVERVIEW.CYCLE_DETECTED
  • detectCycles(graph): string[][]
  • transitiveDependencies(graph, pkg): string[] — throws OVERVIEW.UNKNOWN_PACKAGE
  • transitiveDependents(graph, pkg): string[] — throws OVERVIEW.UNKNOWN_PACKAGE
  • leafPackages(graph): string[]
  • rootPackages(graph): string[]

Implementation Notes

topologicalOrder initialises a remaining-out-degree counter per node and drains nodes with zero out-degree first; the queue is kept sorted on insertion so the output is stable across runs. detectCycles uses a recursive Tarjan implementation — the workspace size is bounded (hundreds of packages), so the stack-depth budget is comfortable. BFS in transitiveDependencies / transitiveDependents uses an array-based queue with Array.prototype.shift — acceptable at workspace scale; if the package ever has to handle tens of thousands of nodes the queue can be swapped for a head/tail index pair.