Skip to content

JSON CodeQuery

JSON CodeQuery is the canonical machine-facing representation accepted by Bifrost’s query_code tool. MCP hosts and the Python client send this shape directly. The RQL REPL prints the same representation with :json.

Version 2 starts with normalized syntactic structure and can apply typed semantic steps for enclosing declarations, resolved call edges and direct call-site inputs, direct project import edges, indexed type hierarchies, and declaration ownership. It does not infer override families, resolve arbitrary aliases, or perform control-flow or data-flow analysis.

{
"schema_version": 2,
"match": {
"kind": "call",
"callee": {
"name": "eval"
}
}
}

The match object is the root pattern. It must constrain at least one of kind, name, or text; a capture-only or wildcard root would match nearly every normalized fact in the workspace and is rejected.

FieldShapeMeaning
schema_versionintegerOptional. Omit it for version 2 or pass 2 explicitly. Other versions are rejected.
matchpatternRequired root pattern.
wherestring arrayOptional project-relative globs. Absolute paths or globs inside the active workspace are normalized by MCP and CLI entrypoints.
languagesstring arrayOptional language labels such as python, typescript, cpp, or csharp. Empty means every structural adapter.
insidepatternRequire the root match to be lexically inside a matching ancestor.
not_insidepatternReject the root match when a matching ancestor exists.
stepsstep arrayOrdered typed transformations applied after structural matching. At most 16.
limitintegerMaximum terminal results after pipeline deduplication. Defaults to 100; valid range is 1 through 1000.
result_detailstringcompact by default or full for stable IDs and precise ranges.

Unknown fields are rejected rather than ignored.

When calling query_code, MCP clients may instead send a tool-call envelope such as { "query_file": "queries/audit.json" }. That selector is not a CodeQuery field and must not be written inside the JSON file itself: the file contains the complete canonical query shown in this reference. The same tool-call input accepts .rql files, which lower through RQL before validation.

A pattern combines all supplied constraints with logical AND.

FieldShapeMeaning
kindstring or string arrayMatch one normalized kind or a union. Every entry is subtype-aware.
not_kindstring or string arrayExclude matching kinds and their subtypes. It never helps candidate pruning.
namestring or { "regex": string }Match a normalized name exactly or by Rust regular expression.
text{ "regex": string }Match parser-backed source text by Rust regular expression. There is no exact string shorthand.
capturestringReturn this node or role target under the supplied capture label.
haspatternRequire some structural descendant to match.
not_haspatternReject the node if any structural descendant matches. It never helps candidate pruning.

Nested role targets may be capture-only or otherwise unconstrained. The root match may not.

An exact name uses string shorthand:

{ "name": "handler" }

A name regex nests the regex key under name:

{ "name": { "regex": "^(eval|exec)$" } }

Source text always uses the regex object:

{ "text": { "regex": "^safe_eval\\(" } }

Fields such as name_regex and text_regex do not exist. To express exact source text, use an anchored, escaped regular expression.

Kinds are language-neutral. Adapters map grammar-specific nodes such as Java method_invocation, Python call, and TypeScript call_expression to the same call kind.

Kind matching is subtype-aware:

declaration
├── callable
│ ├── function
│ ├── method
│ ├── constructor
│ └── lambda
├── class
└── import
literal
├── string_literal
├── numeric_literal
├── boolean_literal
└── null_literal

The remaining kinds are independent leaves: call, assignment, field_access, identifier, return, throw, catch, if, loop, and decorator.

Therefore {"kind":"callable"} matches functions, methods, constructors, and lambdas, and {"kind":"literal"} matches every normalized literal subtype. There is deliberately no exact-kind operator. Use a leaf kind or subtract unwanted subtypes with not_kind.

Roles are normalized edges from one structural fact to a related node or source span. The parent pattern must declare a kind for which the role is valid.

RoleCardinalityValid parent kindsMeaning
calleeonecallTerminal call target, such as run in service.run().
receiveronecallReceiver or qualifying scope, such as service.
argsordered listcallPositional argument patterns.
kwargsname-to-pattern mapcallNamed or keyword argument values.
left, rightone eachassignmentAssignment target and assigned value.
moduleoneimport, declarationImported module or binding target.
decoratorslistcallable or class-like declarationsDecorators, annotations, or attributes.
object, fieldone eachfield_accessObject and terminal field sides of member access.

Each args pattern must match a distinct positional argument in source order, but the matches need not be contiguous and do not assert exact arity. For exact positions or arity, narrow the surrounding source shape in a follow-up query; version 2 has no positional-index operator.

kwargs support is adapter-specific. Python, PHP, Scala, C#, and Ruby expose normalized named arguments; languages without that role return a capability diagnostic.

capture adds a named entry to the result. Captures include their text and start line in compact mode; full mode also includes their normalized kind and byte/line/column range when available.

The same capture label may appear more than once in a query. Every occurrence must bind exactly the same source text, allowing equality constraints such as “both arguments use the same expression.”

The response contains a results array. Every item has a result_type: structural_match, declaration, reference_site, call_site, expression_site, or file. A query without steps returns structural matches with path, language, kind, line range, a bounded text snippet, captures, and a best-effort enclosing_symbol.

With result_detail: "full", results additionally include:

  • a deterministic match id
  • node_range byte and 1-based line/column bounds
  • capture ranges and kinds
  • decorator_ranges for matched declarations
  • decorated_range, the union of the declaration and its decorators

Derived declaration, reference-site, and file results include provenance. Each provenance path records the original structural seed and every ordered step result. Declaration-returning reference steps additionally record the exact proving reference site under via. Compact mode keeps minimal identities; full mode adds stable IDs and precise ranges. At most sixteen paths are retained per terminal result, with provenance_truncated: true when more paths converge.

For completeness claims, result metadata is mandatory: inspect diagnostics, require truncated: false, distinguish proven from unproven graph edges, and check every derived result’s provenance_truncated field. Agent Result Safety turns those fields into an explicit decision rule.

Steps execute in array order and are validated before the workspace is searched:

OperationInputOutputMeaning
enclosing_declstructural matchdeclarationSmallest non-synthetic indexed declaration containing the exact match range, inclusive of a matched declaration itself.
references_ofdeclarationreference siteExact structured source sites targeting the declaration.
used_bydeclarationdeclarationSmallest exact declaration enclosing each matching site.
usesdeclarationdeclarationExact indexed declarations referenced by this semantic owner.
callersdeclarationdeclarationResolved incoming call edges; accepts positive depth and optional proof.
calleesdeclarationdeclarationResolved outgoing call edges; accepts positive depth and optional proof.
call_sites_todeclarationcall siteStructured incoming sites; accepts optional proof.
call_sites_fromdeclarationcall siteStructured outgoing sites; accepts optional proof.
call_inputcall siteexpression siteDirect receiver or formal-parameter input selected by exactly one selector.
file_ofstructural match, declaration, reference site, call site, or expression sitefileExact project file containing the value.
imports_offilefileDirect project-local files imported by the input file.
importers_offilefileDirect project-local files importing the input file.
supertypesdeclarationdeclarationDirect ancestors by default, or a bounded/full indexed ancestor closure.
subtypesdeclarationdeclarationDirect descendants by default, or a bounded/full indexed descendant closure.
membersdeclarationdeclarationReal direct declaration children of a type.
ownerdeclarationdeclarationExact declaring type of a direct member.

Repeat an import step for multiple hops. Traversal is cycle-safe and deterministic; it does not silently compute a transitive closure.

{
"match": {"kind": "function", "name": "handle"},
"steps": [
{"op": "file_of"},
{"op": "imports_of"}
]
}

Hierarchy steps are direct by default. A positive depth returns declarations reachable in one through that many edges; transitive: true returns the full reachable closure under the global execution budget:

{"op":"supertypes"}
{"op":"supertypes","depth":2}
{"op":"subtypes","transitive":true}

Zero depth, transitive: false, unknown fields, depth together with transitive, and traversal options on members or owner are rejected. Invalid input declarations are omitted with aggregated per-language diagnostics, while supported hierarchy leaves simply return no rows. owner after members round-trips each returned member to its exact type.

Hierarchy and ownership results are restricted to declarations returned by the active analyzer’s index and having renderable ranges. Bifrost may observe usages that refer to library code without having indexed that library’s declaration; such a declaration is intentionally absent from these results. This is the current precision boundary until library code can be targeted and indexed explicitly.

Reference steps accept optional reference_kinds, proof, and surface fields. reference_kinds is a non-empty array drawn from method_call, constructor_call, field_read, field_write, type_reference, static_reference, super_call, and inheritance. proof is proven or unproven. surface is external_usages (the default) or lsp_references. Omitted kind and proof fields include both tiers; a kind filter excludes unclassified structured hits. See the executable Reference Traversal recipes.

Call traversal is direct by default. callers and callees accept a positive finite depth; there is deliberately no unbounded transitive form. Traversal is iterative and cycle-safe. A real recursive or cyclic edge is returned, but Bifrost stops expanding when the next declaration is already present on that provenance path. The same declaration may still be expanded through a different path, preserving alternate provenance within the execution budget. Every declaration reached by a call step records the proving call_site under provenance via.

call_sites_to and call_sites_from expose the full call range, callee range, caller and callee declarations, call kind, proof tier, optional explicit receiver, and arguments. call_input requires exactly one of {"receiver":true}, {"parameter_index":0}, or {"parameter_name":"payload"}. Parameter indexes are zero-based formal slots and exclude receiver-bound parameters; keyword/named arguments bind by the callee’s declared parameter name. A variadic slot may yield several expression rows. Spreads/splats are retained on the call-site result but are not guessed into a formal slot. An implicit receiver has no synthetic expression row.

These steps use tree-sitter call shapes and the existing definition/usage resolvers for Java, Go, C/C++, JavaScript, TypeScript, Python, Rust, PHP, Scala, C#, and Ruby. Resolution precision still varies with each language analyzer: unresolved calls are omitted, ambiguous edges are unproven, and formal input projection appears only when Bifrost can pair the resolved callee with structured parameter syntax. This is direct call-site projection, not local or interprocedural data flow.

{
"match": {"kind": "callable", "name": "dangerous"},
"steps": [
{"op": "enclosing_decl"},
{"op": "call_sites_to", "proof": "proven"},
{"op": "call_input", "parameter_name": "payload"}
]
}

inside and not_inside inspect lexical ancestors of the root match. has and not_has inspect descendants of the pattern on which they appear.

{
"match": {
"kind": "call",
"callee": { "name": "execute" },
"capture": "call"
},
"inside": {
"kind": ["function", "method"],
"name": { "regex": "Controller$" },
"capture": "handler"
},
"not_inside": {
"kind": "callable",
"name": { "regex": "^(test_|mock_)" }
}
}
{
"match": {
"kind": "function",
"has": {
"kind": "call",
"callee": { "name": "open" }
},
"not_has": {
"kind": "call",
"callee": { "name": "close" }
}
}
}

Receiver, Positional Arguments, Keyword Arguments, And Captures

Section titled “Receiver, Positional Arguments, Keyword Arguments, And Captures”
{
"languages": ["python"],
"match": {
"kind": "call",
"receiver": { "name": "subprocess" },
"callee": { "name": "run" },
"args": [
{ "capture": "command" }
],
"kwargs": {
"shell": {
"kind": "boolean_literal",
"capture": "shell_value"
}
}
},
"result_detail": "full"
}
{
"match": {
"kind": "import",
"module": { "name": "pickle", "capture": "module" }
}
}

Module names are normalized from syntax, not resolved through aliases or re-exports.

{
"match": {
"kind": "assignment",
"left": { "name": "password" },
"right": {
"kind": "string_literal",
"capture": "value"
}
}
}
{
"match": {
"kind": "callable",
"decorators": [
{ "name": { "regex": "^(route|GetMapping)$" }, "capture": "decorator" }
]
},
"result_detail": "full"
}

Adapters normalize Python decorators, Java annotations, PHP/C# attributes, and equivalent supported forms into the decorators role.

{
"match": {
"kind": "callable",
"not_kind": ["constructor", "lambda"],
"name": { "regex": "^(load|save)" }
}
}

The subtractive form above selects named functions and methods. A direct union such as "kind": ["function", "method"] expresses the same kind set when no broader callable subtype is wanted.

{
"where": ["src/**/*.ts", "src/**/*.tsx"],
"languages": ["typescript"],
"match": {
"kind": "call",
"callee": { "name": { "regex": "^(eval|exec)$" } },
"args": [
{ "capture": "argument" }
]
},
"limit": 25
}

The planner may skip a file only when a positive literal anchor proves that the file cannot match. Exact name predicates and kwargs keys in positive match, inside, has, and role positions can become source anchors. Regex predicates, not_kind, not_has, and not_inside never prune; they are checked only by the structural verifier.

Kind-only, text-regex, and name-regex queries may scan many files because they provide no safe literal anchor. Large broad queries return guidance diagnostics suggesting where, languages, or exact names.

A query is validated against the global normalized schema first. Each language adapter then reports unsupported kinds or roles separately. A query can therefore be valid but still produce a diagnostic such as:

structural adapter for javascript does not support role(s): kwargs

That diagnostic means the affected language was not searched for that feature; it does not silently claim that no matches exist.

Version 2 enforces these budgets:

BudgetMaximum
Results1000
where globs128 entries, 1024 bytes each
Language filters32
Pattern nodes256
Pattern nesting64 levels
Kinds in one union/exclusion32
Entries in one role list64
Named arguments64; names at most 128 bytes
Name predicate source (exact or regex) and text regex source4096 bytes
Capture label128 bytes
Pipeline steps16
Seed and edge rows per execution50000
Provenance paths per terminal result16

Validation failures carry a JSON path so agents can correct the precise field. For example, this misspelling:

{
"match": {
"kind": "call",
"calee": { "name": "eval" }
}
}

reports an error at match.calee and lists the accepted pattern fields. Invalid regexes report paths such as match.callee.name.regex; malformed kind arrays include the failing index, such as match.kind[1].

The same semantic query can be written in Rune Query Language while exploring interactively, then inspected with :json. JSON and RQL are peer frontends over CodeQuery; neither has separate matching semantics.