Inspiration

I used version control for years without really understanding it. Not the commands. The model. When a change broke something three files away, I had no way to know in advance that it would, and the only reliable answers came from CI or from production. That gap between "I changed this" and "this is what it touches" bothered me for a long time.

The project started as an AI tool that would inspect a code change, trace its dependencies, and explain what might be affected. As I scoped it, I made a decision that shaped everything after: no LLM in v1. Dependency tracing is deterministic. A model would be slower, non-reproducible, and capable of hallucinating the graph. If the tool's whole promise is "every claim here is verifiable," then a guessed edge undermines the one thing that makes it worth having. The explanations in v1 are structured and templated ("imported by 3 files, 2 of which have tests"), which is correct and dull, and that trade is the point.

What it does

Impact Map shows the blast radius of a code change. You point it at a folder of JavaScript or TypeScript, it reads the real import statements, and every file your change can reach lights up on a graph.

You say what changed two ways: paste a public GitHub pull request URL and the tool marks its changed files for you, or click files directly in the graph. Then press Trace impact. Files fly outward from the change, ring by ring, and everything the change can reach turns purple while everything else stays dim.

Click any node and a panel opens beside the graph with the actual chain of imports leading back to your change, and which tests cover that file. The chain is shown verbatim, so you can check every link.

Two colour channels carry the meaning. Node fill says how far a file is from your change: orange for changed, purple for affected, grey for untouched. A badge on each node says whether tests cover it: a hollow blue ring for yes, a solid red disc for no. An orange node with a red disc means you changed this and nothing tests it.

The tool refuses to guess. If a pull request names files your folder doesn't have, it lists them by name and marks nothing. If GitHub returns a 404, it says the pull request can't be read, because a private repo, a deleted repo, and a typo all look the same from the outside.

How we built it

I wrote a scope, then a PRD, then a technical spec, and only then touched code. That sequence was the point of the exercise. The PRD forced me to answer questions I'd been avoiding, and a few of those answers turned out to contradict the scope I'd written earlier. Building against a document meant those contradictions surfaced as design decisions instead of as bugs.

The architecture is one rule: src/lib/ contains no React. Every piece of logic is pure and testable in Node: the parser, the resolver, the graph builder, the tracer, the coverage detector, both layouts, the animation timing. That gave me 165 tests that run in about two seconds and need no browser. The UI is thin wrappers over them.

The parser uses Tree-sitter compiled to WebAssembly rather than a regex, specifically because a regex misses multi-line imports and would silently produce a wrong graph. That risk turned out to be real in an interesting way: the obvious package of prebuilt WASM grammars was built at an incompatible ABI and silently rejected at load. The fix was a different package. I found that one by testing, not by reading docs.

Challenges we ran into

The hardest bug was invisible. Edges didn't connect to their nodes, and after a long hunt I found the cause: React Flow's stylesheet was never loading. I'd imported it by reaching into node_modules, which bypasses the bundler's module pipeline, so the request silently fell through to the HTML fallback and returned a 200 with the wrong content. Because it returned 200, every check I ran said "loaded." I only caught it because I looked at the byte count instead of the status code.

That taught me something I applied the rest of the build: a passing test doesn't mean the claim is correct. Several of the best bugs here were invisible to green tests. A shared array mutated across two tests leaked a value and still passed. Two source files had literal NUL bytes as string separators, which compiled fine but made grep treat them as binary, so they were unsearchable to anyone reviewing the repo. And the sample repository itself had a wrong relative import (./cart instead of ../utils/cart) that the tool could never catch, because an unresolvable relative path is reported as an external npm package, which looks normal. The graph was wrong and nothing flagged it, because the tool analyzes structure and doesn't typecheck the code it reads. I documented that as a known limitation rather than hiding it.

The recurring theme: my failures were almost never the algorithms. They were in the seams: a stylesheet that didn't load, a type that erased a distinction, a verb that didn't agree with its noun, a path format that silently never matched. Every one of them was found by looking at the actual output, not by trusting that a green check meant the thing worked.

Accomplishments that we're proud of

The mismatch check. A tool that claims every line it draws is verifiable has to refuse when the evidence doesn't line up. When a pull request disagrees with the folder you picked, Impact Map names the missing files and marks nothing. Marking the files that happen to be present would draw the blast radius of a change you didn't make, and it would look entirely plausible.

The colour encoding survives colour blindness. "How far from my change" and "is this tested" are different kinds of fact, so they can't share one fill. They also differ in shape, a hollow ring versus a filled disc, because blue and purple sit close on the spectrum and because about 8 percent of men see red and green poorly. The untested warning is the most useful thing the tool says, so it has to survive that.

The evidence is checkable. Clicking a node shows the real chain of imports back to your change. The tracer records the shortest chain, and the panel shows it end to end.

It's deterministic. The same repository always produces the same graph. The force layout seeds positions randomly by default, so we replaced the seed with fixed golden-angle steps and wrote tests that fail if one graph ever lays out two ways.

165 tests in about two seconds. The logic lives in modules with no React runtime imports, so it tests in Node without a browser. Cheap tests are what let us be wrong often and still ship.

Verified against real pull requests. We made a small public repository, opened real PRs against it, merged one and left one open, and pasted the URLs in: clean match, missing file, partial match, unreadable repository. Then we cloned the project into an empty directory and ran npm ci, the build, and the tests there.

What we learned

The planning was the actual skill, and it was the part I was worst at. I'd worked on projects where a PRD existed from design through development, but I'd never written one. I knew what one was from the reader's side. Driving one is a different job. The scope doc came first, and writing it exposed how much I'd been hand-waving: I'd written "traces dependencies and explains what might break" as if that were a specification, when it was a wish. Turning that into something a build could be tested against meant answering questions I'd been avoiding.

The PRD was where the real work happened, because every requirement has to survive contact with a real user. Writing "two ways to mark what changed: paste a PR URL, or click files" seemed complete on paper, until I asked what happens when the PR names files your folder doesn't have. There was no answer. That gap became the mismatch check, and it turned out to be one of the most useful things in the tool. I didn't design that up front; I discovered it by being forced to be specific.

It also taught me that my instinct is usually better than my first framing, and worth defending. I wanted the user to paste a PR URL or click files, treating them as alternatives. They're not alternatives. One supplies what changed, the other supplies the code to trace from, and you need both. Same with selecting individual files instead of a whole folder: I'd framed it that way, and choosing the folder fixed a defect I'd only half-seen, because an import pointing at a file the user didn't share is indistinguishable from an import of an external package. Twice I wrote something into the spec that was technically consistent and subtly wrong, and caught it later. That's the part of writing a PRD nobody warns you about: you're the only reviewer, so you have to review yourself.

I kept expecting the interesting parts to be the parser or the animation. The interesting part is that impact flows against the direction of imports. If cart.ts imports price.ts, changing price.ts affects cart.ts. Get that backwards and the graph looks plausible while pointing at dependencies that don't exist. I wrote a test fixture backwards during the build and the tests caught me.

Circular imports are common in actual code, not an error case, and they broke my first layout algorithm, which relaxed depths until they settled and pushed every file in a cycle one level deeper on each pass, smearing the graph across dozens of columns. The fix was Tarjan's strongly-connected-components algorithm: collapse each cycle into one node first, then compute depth on the acyclic result.

The spec required that the same repository always produce the same graph, because determinism is a credibility requirement. d3-force seeds positions randomly by default, so the same repo would lay out differently on every load. I overrode its initial positions with a fixed phyllotaxis spiral. Two layout algorithms (columns and force-directed) both needed this, and the tests that pin it are the ones I would least want deleted.

A tool that makes claims has to refuse gracefully, and the most consequential design decision was the mismatch check. If a pull request names files your folder doesn't have, the tool lists them by name and marks nothing. Silently marking the files that happen to be present would draw the blast radius of a change you didn't make, and it would look entirely plausible. A tool claiming verifiability can't produce a confident wrong answer without warning. The same honesty applies to HTTP404 from GitHub, which is identical for private, deleted, and typo'd repos. The tool says "can't be read" and never guesses which.

Two visual channels beat one. "Distance from my change" and "is this tested?" are different kinds of fact, so they can't share one fill color. They differ by shape too: a hollow ring versus a filled disc, which means the distinction survives red-green color blindness and survives zooming out. That was a real accessibility constraint, not decoration: roughly 8% of men have red-green color vision deficiency, and the "untested" warning is the single most actionable thing the tool says.

What's next for Impact Map

LLM-generated explanations. v1 explains things in templates: "imported by three files, two of which have tests." That's correct and dull. The seam is left for a model to say why an area matters, once the deterministic evidence underneath it is trusted.

Reviewer-facing annotations. The same graph serves a reviewer reading someone else's pull request, but the text per node leans toward the author. A reviewer wants to know where to start reading, not what they forgot.

Repository history mining. Churn, hot spots, which files keep changing together. That's a different product with a time dimension, and it needs a full scan of the whole repository.

More languages. Parsing is Tree-sitter, so adding a language is mostly a grammar. Go, Rust, and Python are the next ones.

Multiple change sets. Comparing two changes side by side, so you can see which one reaches further.

Authenticated GitHub. Public repositories only, unauthenticated, 60 requests an hour. A token would unlock private repos and a higher limit, at the cost of a secrets story in a browser.

Deployment. The demo runs locally. A public URL would let people try it without installing Node.

Built With

Share this project:

Updates

Submission history