◉ANTIGRAVITY LABJP
Articles/Agents & Manager
◈ Agents & Manager/2026-08-07Advanced

I Asked the Agent to Pick Tests From My Diff and It Said "None" Nine Times Out of Ten

Change-based test selection returned zero tests for most edits. Measuring how far an import graph actually reaches across three repositories, and rebuilding the selection contract around it.

Agent Operations8Testing StrategyStatic AnalysisAntigravity411

✦ Premium Article

I was trying to shorten the self-verification loop.

Running the whole suite after every agent edit is slow, and when it fails the signal is buried. Working as an indie developer, that wait comes straight out of my own day. Walking backwards from the changed files to the tests that actually depend on them should have been the obvious fix.

I wired it up, ran a few dozen edits through it, and got the same answer nearly every time: zero tests selected.

The whole measurement finished in 0.4 seconds, so my first suspicion was that the file reads were failing. I counted the reverse edges instead — 3,177 of them. The graph was fine. The zero was the honest answer.

Here is what I measured that afternoon.

What was measured

Three public repositories, chosen because their directory habits differ.

RepositoryFiles analyzedRelative import edgesGraph build time
vite1,5401,409303 ms
hono388914240 ms
nest1,7273,177339 ms

One question only. If you change a single source file, what share of the test suite is reachable by walking imports in reverse?

That calculation is exactly what happens inside an agent when you tell it to "run the tests related to this change." A smaller share means a faster loop — and a larger blind spot.

Building the graph

Only relative imports are resolved. Workspace references like @nestjs/common and path aliases are deliberately excluded, because I wanted something an agent could rebuild in a few hundred milliseconds.

import os, re, subprocess
from collections import defaultdict, deque
 
EXTS = [".ts", ".tsx", ".mts", ".js", ".mjs", ".jsx"]
 
# catches both `from "..."` forms and bare side-effect imports
IMP = re.compile(
    r'''(?:^|\n)\s*(?:import|export)\b[^;\n]*?from\s*['"]([^'"]+)['"]'''
    r'''|(?:^|\n)\s*import\s*['"]([^'"]+)['"]'''
)
# an index file made almost entirely of re-exports is treated as a barrel
REEXP = re.compile(r'''(?:^|\n)\s*export\s+(?:\*|\{)[^;]*?from\s*['"]''')
 
 
def resolve(base_dir, spec, fileset):
    """Resolve a relative specifier to a real file, or None."""
    if not spec.startswith("."):
        return None
    raw = os.path.normpath(os.path.join(base_dir, spec))
    cands = [raw] + [raw + e for e in EXTS]
    cands += [os.path.join(raw, "index" + e) for e in EXTS]
    # ESM specifiers often write .js while pointing at .ts
    if raw.endswith(".js"):
        cands += [raw[:-3] + e for e in (".ts", ".tsx", ".mts")]
    for c in cands:
        if c in fileset:
            return c
    return None
 
 
def build(repo):
    out = subprocess.run(["git", "-C", repo, "ls-files"],
                         capture_output=True, text=True).stdout
    paths = [p for p in out.split("\n")
             if p.strip() and os.path.splitext(p)[1] in EXTS]
    fileset = set(paths)
    fwd, barrel = defaultdict(set), set()
 
    for p in paths:
        try:
            src = open(os.path.join(repo, p), encoding="utf-8",
                       errors="ignore").read()
        except OSError:
            continue  # submodule placeholders and similar gaps
        if os.path.basename(p).startswith("index.") and len(REEXP.findall(src)) >= 3:
            barrel.add(p)
        d = os.path.dirname(p)
        for m in IMP.finditer(src):
            t = resolve(d, m.group(1) or m.group(2), fileset)
            if t and t != p:
                fwd[p].add(t)
 
    rev = defaultdict(set)
    for a, bs in fwd.items():
        for b in bs:
            rev[b].add(a)
    return paths, rev, barrel

The errors="ignore" matters more than it looks. git ls-files lists paths whose contents may not be materialized, and an exception there halts the walk and hands you a sparse graph that looks perfectly valid. That was the first thing I checked when the zeros appeared.

✦

Thank you for reading this far.

Continue Reading

What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.

WHAT YOU'LL LEARN
✦Across vite, hono and nest, the tests reachable from a single changed file sit at a median of 0.0-0.7% but a p90 of 0.3-50.4% — designing against the mean will mislead you
✦In vite, 1,029 of 1,148 source files reach no test at all through the import graph. The dangerous failure is not over-selection, it is a zero that says nothing
✦Dropping re-export barrels from the traversal moves nest from 4.0% to 1.4% mean reach and 14.5% to 2.5% at p90 — barrels were inflating perceived impact roughly threefold
Secure payment via Stripe · Cancel anytime
✦

Unlock This Article

Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.

or
Unlock all articles with Membership →
Share

Thank You for Reading

Antigravity Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • ✦Copy-paste ready implementation code
  • ✦New advanced guides published daily
  • ✦$5/mo or $10 for lifetime access
View Membership →

Related Articles

◈ Agents & Manager2026-10-25
An Unattended Job Stopped With 11MB Free. du Counted Only 59MB; the Other 9GB Was Somewhere I Couldn't See
Two bash scripts that measure free space on the filesystem you are about to write to and stop an unattended agent run before it starts. Includes why du and df disagreed and how to spot a shortage you cannot fix.
◈ Agents & Manager2026-10-24
I Added pipefail and a Passing Check Turned Red: The False Failure That Starts Once Output Passes 8 KB
Adding pipefail can turn a passing check red. I reproduced the false failure that appears once output passes 8 KB on 1,122 files, then built a file-based runner and a canary.
◈ Agents & Manager2026-10-22
17 Dead References in My Handover Docs Turned Out to Be 2 Files, and the Real Risk Was a Different Site's Playbook Mixed In
The full Python script I use to check the root .md files an agent reads. My naive version counted 17 dead references; fixed, it was 7 across 2 files. Then it missed the real problem. Here is how I found it.