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:
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