perf(#307): use head-index queue for phase dependency BFS (O(V^2) -> O(V+E)) (#383)

The Pass-2 topological level assignment in cmdPhasePlanIndex dequeued its
Kahn's-algorithm queue with Array.shift(), which is O(n) per call in V8, so the
BFS was O(V^2) and slowed superlinearly on deep queues (wide fan-in plan
graphs). Extract the traversal into a pure, exported computeDependencyLevels
(rawPlans, planMap, canonicalToId) and dequeue via a head index (queue[head++])
-> O(V+E). Behavior is identical: same FIFO order, same longest-path levels,
same visited-count cycle detection. A complexity-contract comment above the loop
documents why shift() must not be reintroduced.

Adds tests/phase-dependency-levels.test.cjs with deterministic behavior and
edge-case coverage (linear chain, diamond longest-path, independent set, cycle,
canonical-prefix resolution, empty, self-loop, duplicate edge, external dep). A
timing-based complexity guard was intentionally omitted: the O(V+E) Map-build
constant dilutes the O(V^2) signal until impractical N (~1e6), so an empirical
guard is inherently flaky on contended CI — the contract is enforced by the
inline comment and correctness tests instead.

Fixes #307

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Tom Boucher
2026-05-27 20:21:12 -04:00
committed by GitHub
parent d17bb4ea1e
commit 51aac62e9a
3 changed files with 219 additions and 45 deletions

View File

@@ -0,0 +1,5 @@
---
type: Fixed
pr: 383
---
Phase dependency level assignment (gsd-tools phase-plan-index) now dequeues its Kahns-algorithm BFS via a head index instead of Array.shift(), fixing O(V^2) behavior that slowed superlinearly on wide fan-in plan graphs (Array.shift() is O(n) per call in V8). Now O(V+E); behavior is unchanged (same topological levels, same cycle detection). Fixes #307.