Workspace/Lesson workspace
Loading progress
Reliable systems45 min

Repository navigation as evidence gathering

Lesson 1 of 3
How to study this lesson

Read one section, trace the worked example, and try the knowledge check. Bookmark saves a shortcut; notes appear in My notebook. Mark lesson complete is your own assessment and does not mark its lab passed.

By the end, you can

  • Locate behavior by combining text search, syntax, and tests.
  • Build a bounded static view of symbols and dependencies.
  • Identify what an AST can and cannot tell a coding agent.

Start from the behavior to be changed

A coding task usually begins with an observed behavior, a requested change, or a failing test. Translate that into a falsifiable statement before editing. For example, an empty batch currently raises an exception, but the public API should return an empty result. Search for the public entry point, the error text, and tests describing empty inputs. Reading files in alphabetical order rarely answers the relevant question efficiently.

Build a small evidence map: the caller that supplies the input, the function implementing the behavior, related invariants, and the tests that exercise the path. Read local repository instructions and environment setup before running project commands. If the workspace already contains user changes, identify them and keep your patch scoped. A good agent should know which lines it changed and why, rather than merely producing a repository that happens to pass one test.

Use text and syntax for different questions

Text search quickly finds error messages, configuration keys, and symbol names. Syntax-aware inspection can distinguish a function definition from a mention in a comment or string. Python's ast module parses code into a tree of language constructs without importing and executing the module. That makes it useful for finding definitions, call expressions, imports, decorators, and source locations.

The example extracts direct name calls inside top-level functions. For a pipeline function that calls clean and score, it returns those names. This is more structured than searching for parentheses, but it is not a full call graph. A name can be rebound, passed as an argument, imported under an alias, or resolved dynamically. Attribute calls such as service.score need additional resolution. Present this index as a navigation aid with known omissions, not as proof of runtime behavior.

Follow data and contracts across boundaries

A bug near a serializer may originate in the caller's units, the stored schema, or a downstream assumption. Trace a representative input through the relevant boundaries. If a value changes from seconds to milliseconds, identify exactly where conversion occurs and what the API promises. A local change that makes one assertion pass can break other callers if it changes the public contract.

Consider a synthetic package with api importing normalizer, normalizer importing units, and two test files importing api and units separately. A change in units may affect both tests even when neither test mentions the changed function name. Build a reverse dependency view to prioritize checks. Dynamic imports and plugin registration can create missing edges, so static impact analysis should guide test selection while preserving a broader gate for changes with larger consequences.

Reduce context while preserving the needed evidence

A repository can exceed the model's useful context even when it technically fits the context window. Include the definitions, callers, tests, and instructions relevant to the current hypothesis. Summarize the rest with paths and reasons for inclusion. An index of symbols and imports can help the agent retrieve additional code on demand instead of copying the entire project into every request.

Keep source versions with extracted snippets. If another edit changes a function, a cached excerpt can become stale and lead to a patch against the wrong lines. AST nodes carry line information, but comments and formatting are not faithfully preserved by every AST transformation. For small edits, applying a focused textual patch with verified context can be easier to review. Use concrete syntax tooling when preserving formatting is a core requirement.

Turn navigation into a testable hypothesis

Before editing, write the expected causal chain: this input reaches this condition, which produces this incorrect result; changing this condition should fix the target case while preserving these neighboring cases. That statement tells you which evidence would falsify the proposed fix. If the observed failure does not follow the predicted path, revise the hypothesis instead of layering speculative edits.

The accompanying source is a trusted string created for the lesson. Parsing it performs no application imports and no file writes. A real repository indexer should handle syntax errors, generated files, language versions, and ignored directories explicitly. Its output should make uncertainty visible, such as unresolved calls and skipped files. Navigation is successful when it reduces uncertainty about the required change and its impact, not when it produces the largest possible inventory of symbols.

Work through the code

The AST index records direct name calls in trusted lesson source. It omits attribute calls such as strip and does not resolve len as a built-in. Nested scopes are included by ast.walk, another limitation to address in a production indexer. No target module is imported or executed.

m25_lesson_1.py
python
import ast

source = (
    "def clean(value):\n"
    "    return value.strip()\n\n"
    "def score(value):\n"
    "    return len(value)\n\n"
    "def pipeline(value):\n"
    "    return score(clean(value))\n"
)

def direct_calls(text):
    tree = ast.parse(text)
    result = {}
    for node in tree.body:
        if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
            calls = set()
            for child in ast.walk(node):
                if isinstance(child, ast.Call) and isinstance(child.func, ast.Name):
                    calls.add(child.func.id)
            result[node.name] = sorted(calls)
    return result

for name, calls in sorted(direct_calls(source).items()):
    print(name, calls)
EXPECTED / ILLUSTRATIVE OUTPUT
clean []
pipeline ['clean', 'score']
score ['len']

Run Python snippets locally with the prerequisites named above. The module coding lab runs directly in your browser.

Pause and reason

A static index reports that changing parser cannot affect exporter, but exporter loads parser through importlib using a configuration string. What should the agent do before limiting its tests?

Check your understanding

Why parse source with ast before importing an unfamiliar project module?

Your notes

Explain the mechanism in your own words. Add a failure you want to test.

Saved notes appear in your notebook

Go deeper with primary sources

Practice this module