Files
codegraph/__tests__
Colby MchenryandClaude Opus 5.5 9bfdd09238 feat(cli): codegraph cycles lists the files that import each other in a circle (#3099)
New `codegraph cycles [path] [--json]` prints the groups of files that
all reach each other through imports. It exits 1 when there are any and
0 when there are none, so `codegraph cycles || exit 1` works as a
pre-commit or CI check.

The command stays a thin wrapper over CodeGraph.findCircularDependencies(),
but the method itself had to change. It had no callers, and on real
repos its walk wasn't fit to print:
- It reported one list per back edge, so the number depends on the walk:
  1,490 overlapping lists on excalidraw, 87 on gin, 44 on flask.
- It left out files. A walk that meets a visited file stops there, so a
  file whose loop closes through one is never listed (now pinned by a
  test: a>b, b>a, a>c, c>b).
- It counted every edge kind, and most of the graph's edges aren't
  imports. An interface's call fans out to a test's fake, so
  src/extraction/parse-pool.ts "depended" on its own test. Two Next.js
  pages that link to each other were a cycle. gin's files were a 55-file
  knot, though Go forbids import cycles. With exit 1 those are failures
  nobody can fix.

The method now takes the strongly connected components of the file
import graph, one group per knot. The Tarjan pass moves from the
viewer's map into src/graph/file-cycles.ts and both surfaces use it, so
the viewer's "Circular imports between files" panel, which was reading
every edge too, now reports the same groups. Dependent counts on the map
keep their own edge list.

Validation: new cli-cycles and file-cycles suites, plus cycles in the
read-commands suite (mid-rebuild, and old schema indexes); 4 of the 10
findCircularDependencies and finder tests fail with main's method and
all 15 pass with this one. Against the TypeScript compiler's own module
resolver over the same files (kit oracle): excalidraw 3 groups, 369
files, all identical; codegraph's src/ 3 of 4 identical, and the fourth
needs src/resolution/name-matcher.ts, which at 1.6 MB is over the
default size limit and so isn't parsed. flask reports 2 groups and gin
none. It runs in under a second on each. Type-only imports count, as
they aren't told apart in the graph.

The MCP tool from PR #889 is left out on purpose: a new tool is rarely
picked up, and this needs no agent steering.

Reimplements PR #1012 by @jiangyq9, with the exit code from the CLI in
PR #889 by @goutham80808.

Fixes #888

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 19:53:53 +00:00
..