Repository navigation
Conversation
Build() walks the tree, runs one parse per file to extract nested declarations, imports, and call sites, then joins them into a Node/Edge graph with contains/imports/calls relations. Go resolves via go.mod prefix and same-package directory grouping; Python via dotted and relative module paths. Unresolved targets get ext: nodes. New cmd/outline exposes graph/def/callers/callees/affected/path over the built or -g loaded graph.
modName now unescapes the module segment before it is passed back to ExtID. Affected sorts reached nodes by (distance, id) so path order is stable across runs, and the CLI drops its now-redundant sort. signature guards d.Start against a truncated buffer.
decl gains NameAt (the name node's byte offset) and symID(); sym: IDs
use NameAt so multi-name declarations like `var a, b int` produce
distinct nodes. assignParents treats identical-span decls as siblings.
resolver.moduleID qualifies Python relative imports by the importing
file's directory so `from .util import x` in two packages produces
two module nodes and resolves each to its own util.py. emitModules and
emitCalls iterate the sorted path slice instead of the files map.
extQualified derives from the ext: ID rather than the call site's
local receiver, so aliased imports of the same module share one
canonical Qualified and Def("subprocess.run") resolves.
There was a problem hiding this comment.
🟡 Changes recommended
There are confirmed correctness/UX issues in Go module import matching and text rendering output that should be fixed before approval.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
This PR adds a new cross-file code graph builder (Build) plus a cmd/outline CLI for querying definitions and call relationships, with cross-file call resolution implemented for Go and Python.
Changes:
- Introduces a
Graphschema (nodes/edges, deterministic JSON) and traversal/query APIs (Def,Callers,Callees,Affected,Path,Text). - Adds cross-file module + call resolution (Go module/package heuristics; Python module path + import forms) and supporting extraction for calls/imports.
- Adds a new
outlineCLI with subcommands and fixtures/tests covering Go and Python scenarios.
File summaries
| File | Description |
|---|---|
| testdata/cli-py/pkg/loader.py | Python fixture: module function used for CLI graph traversal. |
| testdata/cli-py/pkg/init.py | Python fixture: package init import for module layout coverage. |
| testdata/cli-py/app.py | Python fixture: imports + calls for resolver/CLI coverage. |
| testdata/cli-go/store/store.go | Go fixture: exported function for cross-package call resolution. |
| testdata/cli-go/main.go | Go fixture: imports + calls to internal and external targets. |
| testdata/cli-go/go.mod | Go fixture: module path used by Go import resolution heuristics. |
| resolve.go | Cross-file resolver joining per-file facts into module/call edges. |
| README.md | Documents Build API and the new outline CLI usage. |
| query.go | Graph query/traversal + text rendering for CLI output. |
| query_test.go | Tests for traversal and text rendering behavior. |
| pack.go | Adds Options.Resolution field (Pack ignores it). |
| import.go | Refactors language dispatch into internal importsFor. |
| graph.go | Defines graph schema, ID helpers, indexing, and deterministic JSON sorting. |
| graph_test.go | Tests ID escaping, adjacency, and deterministic JSON output. |
| cmd/outline/main.go | New CLI entrypoint and subcommand implementations. |
| cmd/outline/main_test.go | End-to-end CLI tests building/running the binary against fixtures. |
| call.go | Extracts call sites (Go/Python) for cross-file resolution. |
| build.go | Implements Build, analysis pipeline, node/edge emission, sanitization, digesting. |
| build_test.go | Tests Go/Python resolution behavior, determinism, and edge/node properties. |
| analyse.go | Single-parse extraction of decls/imports/calls feeding the resolver. |
| analyse_test.go | Tests decl nesting, call extraction, and enclosing-decl mapping. |
Review details
- Files reviewed: 21/21 changed files
- Comments generated: 3
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
|
Disclosure: I maintain trace-mcp, an open-source code-graph MCP server. We build cross-file call and import graphs across Go, Python, and other languages. Reading
|
|
Thanks @nikolai-vysotskyi, out of interest how did you find this pr? |
|
Fair question. Cross-file import and call resolution is what I build (I maintain trace-mcp, a code-graph MCP server), so your PR surfaced while I was searching GitHub for open work along those lines. Go plus Python resolvers with a real test surface are rare enough that I read resolve.go end to end. |
Text takes an edge filter and every CLI subcommand except def passes its TraverseOptions.Allow, so callers/callees honour -inferred and affected/path stop leaking contains/imports edges. resolveCall walks the enclosing decl chain (skipping class bodies) and checks captured parameter names before the file table, so nested functions resolve extracted and parameter-shadowed names go to ext. declsFromMatch handles the symbol.exported capture and extractDecls applies the shared explicitExports list. goModuleFiles requires a / after the module prefix; defaultAlias strips a trailing /vN segment; unresolved named relative imports use the directory-qualified module in their ext ID. nodeLine/edgeLine fall back to Name/ID, omit empty locations, and sanitise every field.
goModuleFiles iterates the sorted path slice so duplicate top-level names resolve to the same declaration on every build. analyseOne now detects language before reading, signature truncates at a rune boundary, and the unused resolver.warnings field is gone.
Adds
Build(root, opts) (*Graph, error)and acmd/outlinebinary withgraph,def,callers,callees,affected, andpathsubcommands.Buildreuses thePackwalk and parser pool. One parse per file yields nested declarations with byte spans, parent links, and parameter names; imports; and call sites. Nodes are files, modules, declarations, and unresolved external targets; edges arecontains,imports, andcallswithextracted(same-AST fact) orinferred(cross-file name match) confidence. Node IDs arefile:<path>,sym:<path>:<start>,mod:<lang>:<name>,ext:<lang>:<module>:<name>; JSON output is sorted so repeated builds of unchanged input are byte-identical.Cross-file call resolution covers Go (go.mod prefix with path-boundary check, same-package by directory,
/vNmajor-version suffix stripped from the default alias) and Python (dotted and relative module paths,from x import name, wildcard). Bare calls are resolved by walking outward through enclosing declarations, skipping class bodies, before the file-level table; a name matching an enclosing function's parameter is treated as shadowed and left external. Other supported languages get file, symbol, module, and import structure with call edges omitted.Options.Resolutionaccepts caller-supplied source roots and package hints;Packignores it so existing callers are unchanged.Traversal:
Deflooks up by ID, name, or qualified name;Callers/Calleesare one-hop;Affectedreturns reverse-reachable evidence paths sorted by distance;Pathis forward BFS shortest path.TraverseOptions.Allowexposes the relation and confidence filter.Textrenders a budgeted, sanitised NODE/EDGE line format withfile:linelocations, following only edges the caller's filter admits; every field including file paths and edge kinds is passed throughsanitiseso a repository filename or a hand-edited saved graph cannot break the line protocol.The changes to existing files are: extracting the per-language dispatch from
Importsinto an internalimportsFor; adding theResolutionfield toOptions; and splitting the export-list lookup out ofapplyExplicitExportsintoexplicitExportsso the graph extractor shares it.Known gaps: method calls on non-identifier receivers (
g.sortStable(),foo().Bar()) resolve toext:; local-variable assignments (not parameters) that shadow a top-level or imported name are not detected; Pythonfrom pkg import submoduleand__init__.pyre-exports resolve toext:; Go external-test-package files are grouped with the main package;defaultAliasuses the last import-path segment (after/vNstripping) rather than the target's package clause.