Map your pull request
Glance shows which files and functions a branch changed, how those functions call each other, and which lines match your own flag rules.
$ pnpm add -g github:nishilfaldu/glance
Repo map
Shows every folder and file in the repo. Each changed file lists the functions the branch touched. Switch to Changed only to hide everything the branch didn't touch. Click a function to mark it reviewed.
Call graph
Shows only the changed files and the folders that lead to them, starting from the deepest folder they all share. Gray lines join folders to their files and functions. Arrows show which changed function calls which. Hover a function to highlight its calls.
Run it
Check out a branch and run glance. The page opens on localhost and nothing is uploaded. Add --working to include changes you haven't committed.
Under the hood
How it works
Glance doesn't use AI. It reads git and parses your code with the TypeScript compiler, so the same branch always gives the same result. These are the steps, using the example above.
Where the branch starts
Glance compares your branch with the last commit it shares with main. Git calls that commit the merge base. Comparing with the newest commit on main would also show changes other people merged after you branched.
By default, main is the remote's default branch, or else main or master. Use --base to compare with a different branch.
What changed
git diff --name-status -M lists the files that were added, changed or renamed. Deleted files are left out, because they no longer exist on the branch.
Glance skips the files you list under skip: in .glance.yml, such as generated code. Skipped files still appear on the map with a dashed border, but Glance doesn't read them. For every other file, git diff -U0 gives the exact line numbers that changed.
Finding the changed functions
Glance parses each changed file with the TypeScript compiler. It doesn't build your project or check types, so any TS or JS file works. These count as functions: function declarations, named arrow functions, class methods, route handlers like "POST /session", and test cases like it("…", () => …).
Each changed line belongs to the smallest function that contains it. A removed line belongs to the function now at that spot. Lines outside any function belong to the file. A function is new if main's version of the file has no function with that name, and changed if it does. In package.json, each added or updated dependency gets its own entry.
Connecting calls
For each function, Glance records what it calls: foo(), new Foo(), <Foo />, ns.foo() when ns comes from import * as ns, class methods (this.save(), new Cart().total(), or cart.total() after const cart = new Cart()), and functions passed by name, like list.map(build) or onClick={save}. It looks for each name in the same file first, then in the file's imports.
An import can be a relative path, a paths alias from the nearest tsconfig.json (like @/lib), or a package that lives in the same repo. Glance follows export * from and export { … } from to reach the function itself. Any other package points to its entry in the nearest package.json.
If both functions changed, the call graph draws an arrow between them. Glance stops at your own code. It doesn't follow calls into a package from outside the repo, or calls on objects like db.insert(), which usually go into a library or ORM.
../auth/sessionFlags
Glance only flags what you ask for. Add rules under flags: in .glance.yml at the root of your repo. Each rule has a name and at least one of these:
path: a file pattern. On its own, it flags every changed file that matches.added: a regular expression, checked against each added line.removed: a regular expression, checked against each removed line that wasn't added back.
A match on a line flags the function around it. Each rule gets one number on the page; click a numbered item to see the rule's name in the bar at the bottom. With no rules, nothing is flagged.
The brief
Glance saves the result as one JSON file called the brief. It has the folder tree, the state of each file, the changed functions and their calls, and the flags. It has names, not code. The local page reads the brief from memory, and glance publish uploads the same file. Run glance json to print it.
Drawing the page
The page is a single HTML file without a framework or graph library. The repo map is nested boxes that wrap like text. The call graph puts folders, files and functions in columns. Each column starts right after the widest label in the column before it. Each function gets its own row, and each folder or file is centered next to what it contains.
Lines are SVG curves. Call arrows curve out to the right of the functions, so they don't cross the folder lines.
Reference
glance | Open the map on localhost |
glance publish | Upload it to your Vercel Blob store and print a link |
glance json | Print the brief as JSON |
--base <ref> | Compare against this branch |
--working | Include uncommitted changes |
--pr [n] | Link it on the pull request |
Limits
Glance only finds functions in TypeScript and JavaScript. It doesn't follow calls on objects it can't trace back to a class in the repo (db.insert()), or into packages from outside the repo. Deleted files don't appear on the map.