Build an Autonomous Coding Agent · 45 min · Python · tree-sitter · tree-sitter-python
Objective
AST parsing: signatures, not full source
import tree_sitter_python as tspython
from tree_sitter import Language, Parser
PY_LANGUAGE = Language(tspython.language())
parser = Parser(PY_LANGUAGE)
def extract_signatures(source_code: str) -> list[str]:
tree = parser.parse(bytes(source_code, "utf8"))
signatures = []
def walk(node):
if node.type in ("function_definition", "class_definition"):
name_node = node.child_by_field_name("name")
if name_node:
signatures.append(f"{node.type}: {name_node.text.decode()}")
for child in node.children:
walk(child)
walk(tree.root_node)
return signatures
Notice this extracts function and class NAMES from the AST, not their bodies -- a 200-line file might reduce to 8 signature lines. This is the actual trick behind every "codebase understanding" feature you've seen in Claude Code or Cursor: the agent's first pass over a repo doesn't need full source, it needs a structural map (what functions/classes exist, where) accurate enough to decide which files are worth reading in full.
The tree-sitter Python bindings API changed recently -- verify before trusting older tutorials
Language(tspython.language()) and Parser(PY_LANGUAGE) (language passed to the constructor) is the current pattern as of tree-sitter 0.26.x. Older tutorials show Parser() then parser.set_language(PY_LANGUAGE) as two separate steps -- that's a previous version's API. If a code snippet you find online doesn't match what you see in pip show tree-sitter's version, don't assume it's still correct; check the current docs. This is the exact "your training data is likely wrong" caution baked into how this course was written.
Relevance scoring: ranking files against a task, not just listing them
A structural map alone doesn't tell you what's relevant to THIS task
Knowing every file's functions and classes is necessary but not sufficient -- given the task "add input validation to all API endpoints," you still need to know WHICH files contain API endpoints. A simple, effective first pass: embed each file's signature summary and the task description, rank files by cosine similarity, and take the top N. It won't be perfect, but it's dramatically better than either "read everything" or "read whatever the model happens to ask for first."
def score_file_relevance(task: str, file_summaries: dict[str, str], top_n: int = 8) -> list[str]:
# Cheap heuristic version: keyword overlap between the task and each
# file's extracted signatures + docstring-like comments. A production
# version would embed both and rank by cosine similarity -- this keeps
# the course dependency-light while teaching the same principle.
task_words = set(task.lower().split())
scored = []
for path, summary in file_summaries.items():
summary_words = set(summary.lower().split())
overlap = len(task_words & summary_words)
scored.append((path, overlap))
scored.sort(key=lambda x: x[1], reverse=True)
return [path for path, score in scored[:top_n] if score > 0]
This keyword-overlap version is deliberately simple, not a compromise you should feel bad about -- it's honest about being a heuristic, testable without an embeddings API dependency, and good enough to prove the concept. The stretch challenge for this lesson: swap this for real embedding-based cosine similarity (reusing the embeddings pattern if you've covered it elsewhere) and compare which files each approach ranks highest on the same task.
Your relevance scorer ranks a file highly for the task "fix the login bug" because the file contains the word "login" in a comment, but the actual login logic lives in a differently-named file with no obvious keyword overlap. What does this tell you about this scoring approach?
This is a legitimate, well-known limitation of keyword-based relevance scoring, not a bug -- it's precisely the gap embeddings-based semantic similarity closes, because it can recognize that "login," "auth," and "session" are related concepts even without exact word overlap. Knowing this limitation explicitly (rather than being surprised by it later) is the actual lesson here.
What you're building
codebase_scanner.py: a full-repo scanner using extract_signatures on every .py file, producing a structural summary, plus score_file_relevance ranking files against a real task description — tested against your Lesson 1 test repo with a task you write yourself, verifying the top-ranked files are actually the ones you'd pick by hand.