Skip to content

Code Querying

Bifrost’s composable code-query engine is query_code. Version 2 searches normalized syntactic structure and can transform matches through enclosing declarations, exact source references and semantic users, resolved call edges and call-site inputs, direct import-file edges, indexed type hierarchies, and declaration ownership. It answers questions such as “find calls to this callee,” “which declarations call this function,” “what enters this sensitive formal parameter,” “which types derive from this type,” and “which declarations are direct members of those types” across supported languages.

The broader name is intentional. Future versions may add more steps backed by future control-flow and data-flow analyses. Version 2 traverses resolved calls but does not resolve arbitrary types or aliases or prove control/data flow. call_input projects only the expression written directly at the call site; it does not follow assignments into that expression.

Use the narrowest tool that directly answers the question:

QuestionToolWhy
“Where is Parser.parse declared?”search_symbolsSearches indexed declarations by name.
“Who references this exact symbol?”scan_usages_by_reference or scan_usages_by_locationResolves a known declaration to reference sites from a symbol or source location.
“What is the workspace caller/callee graph?”usage_graphReturns the existing whole-workspace resolved usage graph.
“Which code has this shape, enclosing declaration, import relationship, or indexed type/member relationship?”query_codeMatches normalized kinds and applies typed declaration/file steps.
“Which code is conceptually about retry policy?”semantic_searchRetrieves code by meaning rather than exact structure.
“Where does this literal text occur?”search_file_contentsSearches source text without structural interpretation.

Start with search_symbols or the mode-appropriate scan-usages tool when you already know the symbol. Use query_code when the shape matters more than symbol identity. A useful workflow is to capture structural candidates with query_code, then pass their locations or enclosing symbols to the more semantic tools.

Language adapters map grammar-specific tree-sitter nodes and fields into Rune IR, Bifrost’s normalized source-side representation. The matcher evaluates typed CodeQuery queries against those facts rather than against raw grammar node names.

See Rune IR for the representation, .rune files and VS Code previews, query-by-example workflow, limits, and the complete per-language adapter mapping.

query_code validates the structural seed query, chooses candidate files and facts, and then applies an ordered typed pipeline. Queries without steps return tagged structural matches. enclosing_decl returns exact indexed declarations; references_of, used_by, and uses traverse exact structured references; callers, callees, and the call-site steps traverse only AST-confirmed calls; file_of, imports_of, and importers_of navigate project files; supertypes and subtypes traverse indexed hierarchy edges; and members / owner navigate exact declaration ownership. Derived results retain seed-and-edge provenance.

Semantic declaration steps intentionally stop at the analyzer’s indexed declaration boundary. Seeing a reference or usage into a dependency is not evidence that the dependency declaration is indexed. Until Bifrost can target library code for indexing, unindexed library declarations are omitted rather than reconstructed from names, and their absence is not reported as a capability error.

RQL wrapperJSON stepInput → outputUse it to
enclosing-declenclosing_declstructural match → indexed declarationFind the smallest real declaration that contains a matching expression.
references-ofreferences_ofdeclaration → reference siteReturn exact structured sites targeting a declaration.
used-byused_bydeclaration → declarationReturn each smallest exact semantic user, with its proving site under via.
usesusesdeclaration → declarationReturn exact indexed targets used by one semantic declaration, with via.
callerscallersdeclaration → declarationFollow incoming calls, direct by default or through a positive depth.
calleescalleesdeclaration → declarationFollow outgoing calls, direct by default or through a positive depth.
call-sites-tocall_sites_todeclaration → call siteReturn incoming call sites with caller, callee, proof, receiver, and bound arguments.
call-sites-fromcall_sites_fromdeclaration → call siteReturn call sites lexically owned by the declaration.
call-inputcall_inputcall site → expression siteSelect receiver: true, a zero-based parameter_index, or parameter_name.
file-offile_ofstructural match or semantic source value → fileMove from code, a declaration, reference, call, or input expression to its project file.
imports-ofimports_offile → fileFollow one resolved direct project-local import.
importers-ofimporters_offile → fileFind every project file with a resolved direct import of that file.

For example, (importers-of (file-of (function :name "target"))) answers “which project files directly import the file declaring target?” It is deliberately a file relationship: it does not prove that an importer uses that particular declaration, resolve an out-of-scope library’s members, or manufacture external declarations. The schema-v2 references-of, used-by, and uses steps provide that exact declaration relationship separately, and references-of can compose through file-of when both symbol and import-file provenance matter. See Reference Traversal.

The engine has one semantic query model: CodeQuery. Different input formats must lower into that same model before execution.

Bifrost currently has two representations for CodeQuery:

  • Rune Query Language is the experimental S-expression syntax used by the human REPL.
  • JSON CodeQuery is the canonical JSON representation used by query_code over MCP and by :json output in the REPL.

JSON is not a separate query language. It is the stable serialization of the CodeQuery model. RQL is a convenience language that compiles to that JSON-shaped model.

See JSON CodeQuery for the complete schema, validation rules, result model, and copy-paste examples. See Rune Query Language for interactive authoring and canonical JSON inspection.

For source-first walkthroughs, see the per-language query_code tutorials. Their fixtures, RQL and JSON forms, and exact results are exercised against the real structural adapters.

The examples below use one-shot CLI mode. They were validated against a toy workspace containing the small per-language shapes on the Rune IR adapter-mapping page, with one file for each supported language. The JSON reference contains the complete, test-parsed input examples.

For a reusable query, save the complete RQL or canonical JSON query under the workspace and run it directly:

Terminal window
bifrost --query-file queries/audit.rql
bifrost --root ./code-query-toy --query-file queries/audit.json

The current directory is the default workspace root. Query files must stay within that workspace after symlinks resolve. --query-file selects the complete query and does not merge command-line filters or inline JSON.

Find calls to audit across every structural adapter:

Terminal window
bifrost --root ./code-query-toy --tool query_code --args '{"match":{"kind":"call","callee":{"name":"audit"}},"limit":20}'

The result contains one call match for each current analyzable language and no diagnostics. Representative rows look like:

{"result_type":"structural_match","language":"python","path":"python/app.py","kind":"call","text":"audit(code)"}
{"result_type":"structural_match","language":"typescript","path":"typescript/app.ts","kind":"call","text":"audit(code)"}
{"result_type":"structural_match","language":"ruby","path":"ruby/app.rb","kind":"call","text":"audit(code)"}

Find assignments to password whose right-hand side is a string literal, and capture the value:

Terminal window
bifrost --root ./code-query-toy --tool query_code --args '{"match":{"kind":"assignment","left":{"name":"password"},"right":{"kind":"string_literal","capture":"value"}},"limit":20}'

The result contains one assignment match per language. The captured value is "hunter2" in each match, even though the source syntax varies:

{"result_type":"structural_match","language":"java","text":"password = \"hunter2\"","captures":[{"name":"value","text":"\"hunter2\""}]}
{"result_type":"structural_match","language":"php","text":"$password = \"hunter2\"","captures":[{"name":"value","text":"\"hunter2\""}]}
{"result_type":"structural_match","language":"rust","text":"let password = \"hunter2\";","captures":[{"name":"value","text":"\"hunter2\""}]}

Limit a query to one adapter while debugging a mapping:

Terminal window
bifrost --root ./code-query-toy --tool query_code --args '{"languages":["typescript"],"match":{"kind":"call","callee":{"name":"audit"},"args":[{"capture":"argument"}]},"result_detail":"full"}'

This searches only TypeScript files and returns the matched call plus deterministic byte and line ranges because result_detail is full.

Use RQL when you are exploring a repository interactively:

Terminal window
bifrost --root /path/to/project --repl

Use JSON CodeQuery when a host, script, or MCP client needs a stable machine-facing payload for the query_code tool.