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 Operations7Testing StrategyStatic AnalysisAntigravity349

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 quiet zero
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-06-28
Prune an Antigravity plan before you approve it
Instead of approving a Planning-mode plan wholesale, cut the one risky step and keep the rest. A field-tested look at partial plan editing from a solo developer's desk.
Agents & Manager2026-07-29
A Typo in agent.md Quietly Widened My Permissions — Writing a Strict Frontmatter Lint
Misspelled keys in agent.md frontmatter do not raise errors. They fall back to defaults, and for permission fields that fallback points the wrong way. Here is the failure I hit, the lint I wrote to catch it, and what the measurements showed.
Agents & Manager2026-07-25
The night my agent shipped nothing: giving generation agents an abstain outcome
When you score a background generation agent by how much it produces, the quality gate quietly loosens over time. Here is a three-valued ACCEPT / ABSTAIN / REJECT design that counts a zero-artifact run as a success, with the code and the measurements from running it.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →