/**
 * `DepGraph` — the workboard's dependency relation, with edges added exactly once.
 *
 * A ticket may declare it `deps` on other tickets: ticket *B* depends on ticket
 * *A* means "A must reach `done` before B is workable". That relation is a
 * directed acyclic graph — an edge `A → B` ("A unblocks B") for every `A` in
 * `B.deps`. This module is the pure, in-memory view of that graph, rebuilt from
 * the ticket store whenever the board needs to reason about readiness or admit a
 * new ticket.
 *
 * Two properties the workboard leans on, both enforced here rather than scattered
 * across the board logic:
 *
 *   1. **Edges are added once.** Declaring the same dependency twice (a duplicate
 *      in `deps`, or the same pair re-derived on a later rebuild) adds the edge a
 *      single time. The adjacency sets are de-duplicating by construction, so the
 *      graph carries no parallel edges and downstream traversals visit each
 *      neighbour at most once.
 *   2. **Cycles are detectable.** {@link DepGraph.firstCycle} returns the first
 *      dependency cycle it finds (as the node path that closes the loop) or
 *      `null` when the graph is acyclic. The board calls this when admitting a
 *      ticket so a dependency loop is rejected at `add()` time instead of
 *      deadlocking `ready()` forever.
 *
 * The graph is intentionally decoupled from persistence and from the `Ticket`
 * shape: it speaks only in node ids and `id → deps` adjacency, so it is trivial
 * to test and reuse.
 */
/**
 * A directed dependency graph over ticket ids.
 *
 * Build one with {@link DepGraph.fromDeps}; the constructor is private so every
 * graph goes through the de-duplicating edge-insertion path. Nodes are ticket
 * ids; an edge `dep → ticket` records that `dep` must complete before `ticket`.
 */
export declare class DepGraph {
    /** `dep → set of tickets it unblocks`. De-duplicating: each edge appears once. */
    private readonly forward;
    /** `ticket → set of tickets it depends on`. The reverse of {@link forward}. */
    private readonly backward;
    /** Every node known to the graph (a dependency may be named before it exists). */
    private readonly nodes;
    private constructor();
    /**
     * Build a graph from an `id → deps` mapping.
     *
     * Each `(dep, id)` pair becomes the edge `dep → id`, inserted via
     * {@link addEdge} so duplicates collapse. Self-edges (`id` listing itself in
     * its own `deps`) are admitted as ordinary edges so that {@link firstCycle}
     * reports them as the trivial one-node cycle rather than silently dropping
     * them.
     */
    static fromDeps(entries: Iterable<readonly [id: string, deps: readonly string[]]>): DepGraph;
    /**
     * Record the edge `from → to` (i.e. `from` must finish before `to`).
     *
     * Idempotent: the backing adjacency is a `Set`, so re-adding an existing edge
     * is a no-op and the graph never accumulates parallel edges.
     */
    private addEdge;
    /** Lazily fetch (or create) the adjacency set for `key` in `side`. */
    private edgeSet;
    /** The ids this node directly depends on (its in-neighbours). */
    dependenciesOf(id: string): readonly string[];
    /** The ids directly unblocked by this node (its out-neighbours). */
    dependentsOf(id: string): readonly string[];
    /**
     * Find the first dependency cycle, or `null` if the graph is acyclic.
     *
     * Runs an iterative depth-first search colouring nodes white/grey/black; the
     * first back-edge to a grey (on-stack) node closes a cycle, which is returned
     * as the node path from the re-encountered node around to itself
     * (`[a, b, …, a]`). Detection is what lets the board reject a cyclic `add()`
     * up front rather than letting `ready()` starve.
     */
    firstCycle(): readonly string[] | null;
}
