Decoder
| GitHub: | https://github.com/maryamtb/decoder |
|---|---|
| Description: | Graph-based Python code navigation for engineers and agents. |
| Year: | 2026 |
| Interest: | AST analysis, relational graphs, MCP |
| Built with: | Python, SQLite, TypeScript, MCP |
When I first tried coding with AI, I’d see it running grep commands to find the parts of the code it needed to update. And I kept thinking, that’s not really how I’d approach changing a feature. I’d follow the logic instead.
So I thought about debuggers and how you generally step through code. What if I could give AI something like its own GPS for the codebase? It could trace how a feature connects across different files and use that to figure out where to make changes, instead of relying on grep, which felt too focused on matching text. And you could follow along as it stepped through the code.
So basically, to look at how Decoder works, we need to look at how Python reads code in the first place.
So when you write Python code in a .py file, Python first breaks it into tokens, like names, keywords, and operators. That’s tokenization, a step you’ll find in many other programming languages too. The parser then works out how those tokens fit together according to Python’s grammar and builds an abstract syntax tree, or AST.
At this point, your code is represented as a tree. A function definition has a node, a function call has a node, and an if statement has a node. Those nodes are nested, so you can see that a particular call sits inside an if statement, inside a particular function.
From there, Python works out things like whether a variable is defined inside a function or at the module level, then compiles the code into bytecode. These are instructions for Python’s interpreter, which carries them out when the code runs. You might also see that bytecode cached in a .pyc file.
But how does that get to the hardware? In CPython, the interpreter is itself a program that’s already been compiled into machine code. The processor runs that machine code as the interpreter carries out your program’s bytecode instructions.
For Decoder, I use Python’s ast module to get that tree. Decoder reads each Python file and passes its source code to ast.parse(), so it can inspect the structure without running the application.
Once it has those trees, Decoder needs to connect what it’s found across the different files. If the code says service.create_user(), which service is that? Where is it defined? Which method does that call actually lead to?
Decoder does this in two passes. First, it indexes the definitions across the Python files. Then it resolves the connections between them, using things like imports and type annotations to work out what each name refers to. It also tracks whether a call sits inside a condition, a loop, or a try block, so that context stays with the connection.
The definitions and connections are stored in a local SQLite file inside the repo, at .decoder/index.db. That gives us a graph we can navigate alongside the agent.
For agents, there’s an MCP server that exposes the graph. An agent can look up what calls a function, see what that function calls, or trace a chain across files, instead of repeatedly searching and reading files to piece those relationships together.
For engineers, there’s a CLI and a VS Code extension. You can explore the same graph yourself and step through the traces with the relevant code highlighted.