{"slug": "building-an-ast-code-verifier-without-networkx-gitpython-or-any-dependencies", "title": "Building an AST Code Verifier Without NetworkX, GitPython, or Any Dependencies", "summary": "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.", "body_md": "You end up learning why those packages exist in the first place.\n\nFor the Zero Dependency Hackathon 2026, I built Proofline, a pure Python static analysis tool that works as a verification gate for code changes.\n\nThe main rule for the project was simple:\n\nNo third-party dependencies.\n\nSo there was no networkx, no GitPython, no pre-commit, no fastapi, and no watchdog.\n\nEverything had to be built using Python's standard library.\n\nWhat is Proofline?\n\nThe idea behind Proofline came from working with AI-generated code.\n\nMost 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:\n\nWhat actually changed, and what could that change affect?\n\nProofline parses Python code using the AST and builds information about functions, classes, callers, routes, and file changes.\n\nFor example, it tries to detect things like:\n\nchanged exception behavior\n\norphaned routes\n\nbroken caller relationships\n\nunexpected changes between scans\n\ndynamically registered routes that can't be completely verified statically\n\nIt's not supposed to replace Ruff, Flake8, or similar tools.\n\nThe question I'm trying to answer is more like:\n\n\"What is the blast radius of this change, and how confident are we about it?\"\n\nBuilding it without the usual libraries\n\nThis was probably the most interesting part of the hackathon.\n\nIf I were building this normally, I'd probably reach for GitPython for Git operations and NetworkX for the call graph.\n\nBut I couldn't.\n\nSo I had to build smaller versions of those pieces myself.\n\nDiff detection\n\nInstead of asking Git which files changed, Proofline walks the project using pathlib.rglob() and calculates SHA-256 hashes using hashlib.\n\nThe basic idea is:\n\nfind files\n\n↓\n\nread file\n\n↓\n\ncalculate hash\n\n↓\n\ncompare with previous hash\n\n↓\n\nanalyze changed files\n\nIt's obviously not as efficient as Git's own implementation, but it works without depending on GitPython.\n\nBuilding the symbol map\n\nFor the Python analysis, I used the built-in ast module.\n\nast.NodeVisitor lets me walk through the syntax tree and collect functions, classes, and other symbols.\n\nThat gives Proofline a basic understanding of what's inside each file before trying to connect everything together.\n\nBuilding the caller graph\n\nThis was another place where I initially thought, \"I'll just use NetworkX.\"\n\nObviously, I couldn't.\n\nSo the first version was basically an adjacency list:\n\ndict[str, set[str]]\n\nA function becomes a node and calls between functions become edges.\n\nIt's much simpler than a full graph library, but for what Proofline needs, it was enough to get started.\n\nGit hook\n\nI also wanted Proofline to work automatically before code gets committed.\n\nInstead of using the pre-commit package, the tool creates a small Bash/PowerShell wrapper inside:\n\n.git/hooks/pre-commit\n\nSo the verification can happen as part of the normal commit flow.\n\nDashboard\n\nFor the dashboard, I used Python's built-in http.server.\n\nFor live updates, I used Server-Sent Events (SSE).\n\nAgain, no web framework.\n\nThen I ran into dynamic dispatch\n\nThis was the part that took the most time.\n\nAt first, the AST-based caller graph looked pretty good.\n\nThe process was straightforward:\n\nparse AST\n\n↓\n\nfind function calls\n\n↓\n\nconnect callers and callees\n\nThen I started testing cases involving dynamic behavior.\n\nFor example:\n\ngetattr(obj, \"method_name\")()\n\nOr routes that are registered dynamically by a framework.\n\nThe problem is that the AST tells you what the source code looks like.\n\nIt doesn't necessarily tell you what the program will do at runtime.\n\nI initially tried to solve this by writing another AST pass that would evaluate variables and follow dynamically registered routes.\n\nThat quickly became messy.\n\nThere were too many possible cases.\n\nInstead of spending the entire project trying to build a mini Python interpreter, I changed the approach.\n\nI added a route_detector.py that looks for common route/decorator patterns.\n\nBut there's an important distinction:\n\nThe results from this detector are marked INFERRED, not PROVEN.\n\nThat was a design decision I was quite happy with.\n\nIf static analysis isn't sure about something, I would rather show that uncertainty than pretend the result is guaranteed.\n\nThe performance problem\n\nThe other thing I didn't expect was how noticeable file hashing would be.\n\nGit is extremely fast at figuring out what changed because it has its own index and optimized internals.\n\nMy implementation was doing something much simpler:\n\nwalk files\n\n↓\n\nread files\n\n↓\n\nhash files\n\n↓\n\ncompare hashes\n\nOn a medium-sized repository, the initial scan took a little over a second on my tests.\n\nThat's not terrible, but it was definitely slower than just asking Git.\n\nAnd honestly, this was a useful lesson.\n\nIt made me understand why Git has an index instead of simply scanning the entire repository every time.\n\nAdding a cache\n\nThe solution was to add a cache:\n\n.proofline/cache/\n\nEach file's SHA-256 hash is used as the key.\n\nThe first scan still has to do the expensive work:\n\nFile\n\n↓\n\nHash\n\n↓\n\nParse AST\n\n↓\n\nBuild symbols\n\n↓\n\nStore result\n\nBut after that:\n\nFile\n\n↓\n\nHash\n\n↓\n\nCache hit\n\n↓\n\nReuse analysis\n\nSo unchanged files don't need to be parsed again.\n\nOn subsequent scans, the difference is significant, with unchanged repositories getting down to millisecond-level analysis in my tests.\n\nWhere Proofline makes sense\n\nI don't think this is a tool that every Python project needs.\n\nIt makes more sense for projects where:\n\nAI-generated code is being reviewed regularly\n\nyou want an additional verification step before merging\n\nyou want to understand the impact of a code change\n\nyou don't want to add a large dependency tree for a small internal tool\n\nyou want a local dashboard without setting up another service\n\nThere are also obvious cases where it isn't a great fit.\n\nIf a project relies heavily on metaprogramming or runtime-generated behavior, static analysis can only go so far.\n\nThat's one of the limitations I'm deliberately keeping visible.\n\nWhat's next?\n\nThere are a few things I'd like to work on next:\n\n[1](https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fctrl5z0tinjlsruur0ox.png)\n\nGitHub PR comments without depending on requests\n\nbetter Django route detection\n\nmore verification rules for AI-generated changes\n\nimproving the caching system\n\nhandling more cases where static analysis currently reports UNKNOWN or INFERRED\n\nWhat I learned from building this\n\nThe biggest takeaway from this project wasn't actually AST parsing.\n\nIt was what happened when I couldn't install a package to solve a problem.\n\nNormally, if I need a graph, I use a graph library.\n\nIf I need Git integration, I use a Git library.\n\nIf I need a web server, I use a framework.\n\nThis project forced me to stop and ask:\n\nWhat is the library actually doing for me?\n\nThat turned out to be one of the most useful parts of the hackathon.\n\nI 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.\n\nAnd the dynamic dispatch problem taught me something else:\n\nA good static analysis tool should know when it doesn't know.\n\nThat's one of the ideas I want Proofline to keep as it grows.\n\nNot pretending to have perfect certainty.\n\nJust being clear about what is PROVEN, what is INFERRED, and what is UNKNOWN.\n\nTry it / Get involved\n\nGitHub: [https://github.com/ShreyaKaushikdev/zerodep-analyzer](https://github.com/ShreyaKaushikdev/zerodep-analyzer)\n\nIf you work with AI-generated code, I'd be interested to know:\n\nWhat would you want a tool like this to verify before an AI-generated PR gets merged?\n\nAnd if you think something like Proofline is unnecessary compared with tools like Ruff, Flake8, or Sonar, I'd also genuinely like to know why.\n\nWhen you remove the packages, you don't just end up writing more code.\n\nYou start understanding what those packages were doing for you in the first place.\n\ncc: @Hackathon Raptors", "url": "https://wpnews.pro/news/building-an-ast-code-verifier-without-networkx-gitpython-or-any-dependencies", "canonical_source": "https://dev.to/urjit_upadhyay/building-an-ast-code-verifier-without-networkx-gitpython-or-any-dependencies-20dd", "published_at": "2026-09-03 03:00:40+00:00", "updated_at": "2026-09-03 03:22:48.128285+00:00", "lang": "en", "topics": ["developer-tools", "ai-products"], "entities": ["Proofline", "Zero Dependency Hackathon 2026", "Python", "AST", "NetworkX", "GitPython"], "alternates": {"html": "https://wpnews.pro/news/building-an-ast-code-verifier-without-networkx-gitpython-or-any-dependencies", "markdown": "https://wpnews.pro/news/building-an-ast-code-verifier-without-networkx-gitpython-or-any-dependencies.md", "text": "https://wpnews.pro/news/building-an-ast-code-verifier-without-networkx-gitpython-or-any-dependencies.txt", "jsonld": "https://wpnews.pro/news/building-an-ast-code-verifier-without-networkx-gitpython-or-any-dependencies.jsonld"}}