cd /news/developer-tools/building-an-ast-code-verifier-withou… · home topics developer-tools article
[ARTICLE · art-119725] src=dev.to ↗ pub= topic=developer-tools verified=true sentiment=· neutral

Building an AST Code Verifier Without NetworkX, GitPython, or Any Dependencies

A developer built Proofline, a pure Python static analysis tool for the Zero Dependency Hackathon 2026, using only the standard library. The tool parses code with the AST module, builds a caller graph via adjacency lists, and detects changed files by hashing, aiming to assess the blast radius of code changes, especially in AI-generated code.

read6 min views2 publishedSep 3, 2026

You end up learning why those packages exist in the first place.

For the Zero Dependency Hackathon 2026, I built Proofline, a pure Python static analysis tool that works as a verification gate for code changes. The main rule for the project was simple:

No third-party dependencies.

So there was no networkx, no GitPython, no pre-commit, no fastapi, and no watchdog.

Everything had to be built using Python's standard library.

What is Proofline?

The idea behind Proofline came from working with AI-generated code.

Most linters are great at finding things like syntax issues, formatting problems, unused variables, and other common mistakes. But I wanted to look at something slightly different:

What actually changed, and what could that change affect?

Proofline parses Python code using the AST and builds information about functions, classes, callers, routes, and file changes.

For example, it tries to detect things like: changed exception behavior

orphaned routes

broken caller relationships

unexpected changes between scans

dynamically registered routes that can't be completely verified statically

It's not supposed to replace Ruff, Flake8, or similar tools.

The question I'm trying to answer is more like:

"What is the blast radius of this change, and how confident are we about it?"

Building it without the usual libraries

This was probably the most interesting part of the hackathon.

If I were building this normally, I'd probably reach for GitPython for Git operations and NetworkX for the call graph. But I couldn't.

So I had to build smaller versions of those pieces myself.

Diff detection

Instead of asking Git which files changed, Proofline walks the project using pathlib.rglob() and calculates SHA-256 hashes using hashlib.

The basic idea is:

find files

read file

calculate hash

compare with previous hash

analyze changed files

It's obviously not as efficient as Git's own implementation, but it works without depending on GitPython.

Building the symbol map

For the Python analysis, I used the built-in ast module. ast.NodeVisitor lets me walk through the syntax tree and collect functions, classes, and other symbols.

That gives Proofline a basic understanding of what's inside each file before trying to connect everything together.

Building the caller graph

This was another place where I initially thought, "I'll just use NetworkX."

Obviously, I couldn't.

So the first version was basically an adjacency list:

dict[str, set[str]] A function becomes a node and calls between functions become edges.

It's much simpler than a full graph library, but for what Proofline needs, it was enough to get started.

Git hook

I also wanted Proofline to work automatically before code gets committed.

Instead of using the pre-commit package, the tool creates a small Bash/PowerShell wrapper inside:

.git/hooks/pre-commit

So the verification can happen as part of the normal commit flow.

Dashboard

For the dashboard, I used Python's built-in http.server.

For live updates, I used Server-Sent Events (SSE).

Again, no web framework.

Then I ran into dynamic dispatch

This was the part that took the most time.

At first, the AST-based caller graph looked pretty good.

The process was straightforward:

parse AST

find function calls

connect callers and callees

Then I started testing cases involving dynamic behavior.

For example:

getattr(obj, "method_name")()

Or routes that are registered dynamically by a framework.

The problem is that the AST tells you what the source code looks like.

It doesn't necessarily tell you what the program will do at runtime.

I initially tried to solve this by writing another AST pass that would evaluate variables and follow dynamically registered routes.

That quickly became messy.

There were too many possible cases.

Instead of spending the entire project trying to build a mini Python interpreter, I changed the approach.

I added a route_detector.py that looks for common route/decorator patterns.

But there's an important distinction:

The results from this detector are marked INFERRED, not PROVEN.

That was a design decision I was quite happy with.

If static analysis isn't sure about something, I would rather show that uncertainty than pretend the result is guaranteed. The performance problem

The other thing I didn't expect was how noticeable file hashing would be.

Git is extremely fast at figuring out what changed because it has its own index and optimized internals.

My implementation was doing something much simpler:

walk files

read files

hash files

compare hashes

On a medium-sized repository, the initial scan took a little over a second on my tests.

That's not terrible, but it was definitely slower than just asking Git.

And honestly, this was a useful lesson.

It made me understand why Git has an index instead of simply scanning the entire repository every time.

Adding a cache

The solution was to add a cache:

.proofline/cache/

Each file's SHA-256 hash is used as the key.

The first scan still has to do the expensive work:

File

Hash

Parse AST

Build symbols

Store result

But after that:

File

Hash

Cache hit

Reuse analysis

So unchanged files don't need to be parsed again.

On subsequent scans, the difference is significant, with unchanged repositories getting down to millisecond-level analysis in my tests.

Where Proofline makes sense

I don't think this is a tool that every Python project needs.

It makes more sense for projects where:

AI-generated code is being reviewed regularly

you want an additional verification step before merging

you want to understand the impact of a code change

you don't want to add a large dependency tree for a small internal tool

you want a local dashboard without setting up another service

There are also obvious cases where it isn't a great fit.

If a project relies heavily on metaprogramming or runtime-generated behavior, static analysis can only go so far. That's one of the limitations I'm deliberately keeping visible.

What's next?

There are a few things I'd like to work on next:

1

GitHub PR comments without depending on requests

better Django route detection

more verification rules for AI-generated changes

improving the caching system

handling more cases where static analysis currently reports UNKNOWN or INFERRED

What I learned from building this

The biggest takeaway from this project wasn't actually AST parsing.

It was what happened when I couldn't install a package to solve a problem.

Normally, if I need a graph, I use a graph library.

If I need Git integration, I use a Git library.

If I need a web server, I use a framework.

This project forced me to stop and ask:

What is the library actually doing for me?

That turned out to be one of the most useful parts of the hackathon.

I learned more about AST traversal, graph representation, file hashing, Git's index, hooks, caching, and the limits of static analysis than I probably would have by simply installing the packages.

And the dynamic dispatch problem taught me something else:

A good static analysis tool should know when it doesn't know.

That's one of the ideas I want Proofline to keep as it grows.

Not pretending to have perfect certainty.

Just being clear about what is PROVEN, what is INFERRED, and what is UNKNOWN.

Try it / Get involved

GitHub: [https://github.com/ShreyaKaushikdev/zerodep-analyzer](https://github.com/ShreyaKaushikdev/zerodep-analyzer)

If you work with AI-generated code, I'd be interested to know:

What would you want a tool like this to verify before an AI-generated PR gets merged?

And if you think something like Proofline is unnecessary compared with tools like Ruff, Flake8, or Sonar, I'd also genuinely like to know why.

When you remove the packages, you don't just end up writing more code.

You start understanding what those packages were doing for you in the first place.

cc: @Hackathon Raptors

── more in #developer-tools 4 stories · sorted by recency
── more on @proofline 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/building-an-ast-code…] indexed:0 read:6min 2026-09-03 ·