Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions .agents/skills/rbtr-languages/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,11 @@ The orchestrator's primary path is one of three:
as an injection target for an embedded block (see below).
2. **Query** — `reg.grammar_module` + `reg.query`: code, plus config/data
whose scope a query can express (python, rust, …, json, css, html, toml,
yaml, hcl). Goes through `extract_symbols`.
yaml, hcl, query). Goes through `extract_symbols`.
HTML captures its semantic elements (`head`, `body`, sectioning content,
landmarks) as doc sections, named by `id` else tag via a `name_extractor`.
The `query` plugin indexes `.scm` files themselves: each top-level pattern
is a `@doc_section`, named by its own outer capture else anonymous.
3. **Plaintext fallback** — no grammar/detection: fixed-size raw chunks.

`extract_symbols` is the query engine: *parse → run query → captures →
Expand Down Expand Up @@ -252,7 +254,10 @@ itself a tree-sitter query, so it is **inspiration for ours, not a drop-in**:

Distinct from **rbtr's own** `.scm` files (see *Where queries live*): we load
those at runtime (`reg.query` / `injection_query`) as the source of truth,
whereas `tags.scm` we only mine for ideas.
whereas `tags.scm` we only mine for ideas. And the `query` plugin now
*indexes* `.scm` files found in a repo — including third-party `tags.scm` /
`highlights.scm` / `injections.scm` — as content, orthogonal to whether we
run them.

Curated per-language verdicts (take / modify / ignore) and the
`@definition.* → ChunkKind` mapping live in
Expand Down
3 changes: 2 additions & 1 deletion packages/rbtr/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -1091,7 +1091,8 @@ capture conventions), see
language has one (`chunker(path, sha, content, grammar,
ranges)`; markdown, rst, svelte, vue), else tree-sitter
`extract_symbols` for a `grammar` + `query` (python,
rust, go, ...; json, css, html, toml, yaml, hcl).
rust, go, ...; json, css, html, toml, yaml, hcl; and
`query` itself, indexing `.scm` files).
2. **Injection (additive)** - if the registration has an
`injection_query`, the engine *also* runs
`extract_injections`: it matches embedded blocks
Expand Down
6 changes: 3 additions & 3 deletions packages/rbtr/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -358,9 +358,9 @@ extraction (symbol-level chunks, import metadata, scope
detection). Everything else gets line-based chunking.

Built-in: bash, c, cpp, css, go, hcl, html, java,
javascript, json, less, markdown, python, rst, ruby,
rust, scss, sql, svelte, toml, tsx, typescript, vue,
yaml.
javascript, json, less, markdown, python, query, rst,
ruby, rust, scss, sql, svelte, toml, tsx, typescript,
vue, yaml.

Code embedded in another file is indexed in its own
language. A fenced code block in Markdown is extracted as
Expand Down
1 change: 1 addition & 0 deletions packages/rbtr/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ dependencies = [
"tree-sitter-less",
"tree-sitter-markdown",
"tree-sitter-python",
"tree-sitter-query",
"tree-sitter-rst",
"tree-sitter-ruby",
"tree-sitter-rust",
Expand Down
2 changes: 2 additions & 0 deletions packages/rbtr/src/rbtr/languages/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
from rbtr.languages.less.plugin import LessPlugin
from rbtr.languages.markdown.plugin import MarkdownPlugin
from rbtr.languages.python.plugin import PythonPlugin
from rbtr.languages.query.plugin import QueryPlugin
from rbtr.languages.rst.plugin import RstPlugin
from rbtr.languages.ruby.plugin import RubyPlugin
from rbtr.languages.rust.plugin import RustPlugin
Expand Down Expand Up @@ -120,6 +121,7 @@ def _register_builtins(self) -> None:
LessPlugin,
MarkdownPlugin,
PythonPlugin,
QueryPlugin,
RstPlugin,
RubyPlugin,
RustPlugin,
Expand Down
1 change: 1 addition & 0 deletions packages/rbtr/src/rbtr/languages/query/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""tree-sitter query language plugin package."""
43 changes: 43 additions & 0 deletions packages/rbtr/src/rbtr/languages/query/plugin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
"""tree-sitter query language plugin.

Indexes `.scm` files — the tree-sitter query language that rbtr's own
language plugins are written in (see the `.scm` files beside each
`plugin.py`). Third-party grammars ship `.scm` too (`tags.scm`,
`highlights.scm`, `injections.scm`), so cloned repos are covered as well.

Each top-level pattern becomes a `doc_section` chunk, named by its own
outer capture where the author gave one (`query.scm` pairs it as
`@_section_name`):

(function_definition name: (identifier) @_fn_name) @function
→ section "function"

["if" "else"] @keyword
→ section "keyword"

Patterns with no outer label of their own — a predicate-wrapped injection
rule, or one anchored at a structural wrapper — are anonymous sections;
their nested captures stay full-text-searchable.
"""

from __future__ import annotations

from rbtr.languages._queries import load_query
from rbtr.languages.hookspec import LanguageRegistration, hookimpl


class QueryPlugin:
"""tree-sitter query (`.scm`) language support."""

@hookimpl
def rbtr_register_languages(self) -> list[LanguageRegistration]:
return [
LanguageRegistration(
id="query",
extensions=frozenset({".scm"}),
grammar_module="tree_sitter_query",
query=load_query(__package__, "query"),
doc_comment_node_types=frozenset({"comment"}),
language_plugin_version=1,
),
]
11 changes: 11 additions & 0 deletions packages/rbtr/src/rbtr/languages/query/query.scm
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
; Every top-level pattern is a section. A pattern is named by its own outer
; label — a capture that is a direct child of the pattern node (e.g.
; `(function_definition …) @function` → "function"; `["if" "else"] @keyword`
; → "keyword"). The `?` makes that label optional: predicate-wrapped rules
; and structural wrappers carry no label of their own and stay anonymous.
; Captures, predicates, and matched node types inside remain full-text
; searchable within the section either way.
(program (named_node (capture (identifier) @_section_name)?) @doc_section)
(program (list (capture (identifier) @_section_name)?) @doc_section)
(program (grouping (capture (identifier) @_section_name)?) @doc_section)
(program (anonymous_node (capture (identifier) @_section_name)?) @doc_section)
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[]
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
[
{
"id": "95bcbb40fa68e03d",
"blob_sha": "sha1",
"file_path": "highlights.scm",
"kind": "doc_section",
"name": "definition.function",
"scope": "",
"language": "query",
"content": "; Named-node patterns with a field and an outer definition capture.\n(function_definition\n name: (identifier) @function\n body: (_) @_body) @definition.function",
"line_start": 6,
"line_end": 9,
"metadata": {
"module": "",
"names": "",
"dots": "",
"language_hint": ""
}
},
{
"id": "b9d016d76766b006",
"blob_sha": "sha1",
"file_path": "highlights.scm",
"kind": "doc_section",
"name": "definition.class",
"scope": "",
"language": "query",
"content": "(class_definition\n name: (identifier) @type) @definition.class",
"line_start": 11,
"line_end": 12,
"metadata": {
"module": "",
"names": "",
"dots": "",
"language_hint": ""
}
},
{
"id": "12955e88d71fa702",
"blob_sha": "sha1",
"file_path": "highlights.scm",
"kind": "doc_section",
"name": "keyword",
"scope": "",
"language": "query",
"content": "; Alternation list and a bare anonymous-node matcher.\n[\"if\" \"else\" \"while\" \"return\"] @keyword",
"line_start": 14,
"line_end": 15,
"metadata": {
"module": "",
"names": "",
"dots": "",
"language_hint": ""
}
},
{
"id": "0a7aae7f30c848e6",
"blob_sha": "sha1",
"file_path": "highlights.scm",
"kind": "doc_section",
"name": "keyword.coroutine",
"scope": "",
"language": "query",
"content": "\"async\" @keyword.coroutine",
"line_start": 17,
"line_end": 17,
"metadata": {
"module": "",
"names": "",
"dots": "",
"language_hint": ""
}
},
{
"id": "9fd153dd462c97fe",
"blob_sha": "sha1",
"file_path": "highlights.scm",
"kind": "doc_section",
"name": "<anonymous>",
"scope": "",
"language": "query",
"content": "; Wildcard child, a negated field, anchors, and quantifiers.\n(call_expression\n function: (_) @call\n !type_arguments)",
"line_start": 19,
"line_end": 22,
"metadata": {
"module": "",
"names": "",
"dots": "",
"language_hint": ""
}
},
{
"id": "08bf92e385e51759",
"blob_sha": "sha1",
"file_path": "highlights.scm",
"kind": "doc_section",
"name": "<anonymous>",
"scope": "",
"language": "query",
"content": "(array . (identifier) @first (_)* @rest)",
"line_start": 24,
"line_end": 24,
"metadata": {
"module": "",
"names": "",
"dots": "",
"language_hint": ""
}
},
{
"id": "d4b473df03d82d82",
"blob_sha": "sha1",
"file_path": "highlights.scm",
"kind": "doc_section",
"name": "<anonymous>",
"scope": "",
"language": "query",
"content": "; Groupings with predicates: regex match, alternation, and an injection.\n((identifier) @constant\n (#match? @constant \"^[A-Z][A-Z_]+$\"))",
"line_start": 26,
"line_end": 28,
"metadata": {
"module": "",
"names": "",
"dots": "",
"language_hint": ""
}
},
{
"id": "fa51260b21d08e68",
"blob_sha": "sha1",
"file_path": "highlights.scm",
"kind": "doc_section",
"name": "<anonymous>",
"scope": "",
"language": "query",
"content": "((identifier) @type.builtin\n (#any-of? @type.builtin \"int\" \"str\" \"bool\"))",
"line_start": 30,
"line_end": 31,
"metadata": {
"module": "",
"names": "",
"dots": "",
"language_hint": ""
}
},
{
"id": "005bca176ae4b980",
"blob_sha": "sha1",
"file_path": "highlights.scm",
"kind": "doc_section",
"name": "<anonymous>",
"scope": "",
"language": "query",
"content": "((comment) @injection.content\n (#set! injection.language \"markdown\"))",
"line_start": 33,
"line_end": 34,
"metadata": {
"module": "",
"names": "",
"dots": "",
"language_hint": ""
}
},
{
"id": "cf19ec3f8c8c0c41",
"blob_sha": "sha1",
"file_path": "highlights.scm",
"kind": "doc_section",
"name": "definition.function",
"scope": "",
"language": "query",
"content": "; Supertype/subtype match and a MISSING-node pattern.\n(declaration/function_definition) @definition.function",
"line_start": 36,
"line_end": 37,
"metadata": {
"module": "",
"names": "",
"dots": "",
"language_hint": ""
}
},
{
"id": "c001bdd71bc27782",
"blob_sha": "sha1",
"file_path": "highlights.scm",
"kind": "doc_section",
"name": "<anonymous>",
"scope": "",
"language": "query",
"content": "(binary_expression (MISSING identifier))",
"line_start": 39,
"line_end": 39,
"metadata": {
"module": "",
"names": "",
"dots": "",
"language_hint": ""
}
}
]
13 changes: 13 additions & 0 deletions packages/rbtr/src/rbtr/tests/languages/cases_samples.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,19 @@ def case_hcl() -> SampleCase:
)


@case(id="query", tags=["sample"])
def case_query() -> SampleCase:
"""tree-sitter query (`.scm`): every top-level pattern is a doc section,
named by its definition capture (else its matched node type). Exercises
named nodes, alternation lists, groupings, predicates, wildcards, negated
fields, anchors, quantifiers, supertypes, and a MISSING node.
"""
return (
"query",
{ChunkKind.DOC_SECTION},
)


@case(id="rst", tags=["sample"])
def case_rst() -> SampleCase:
"""reStructuredText: heading hierarchy from adornment order (as doc
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
; A tree-sitter query file exercising every construct rbtr should index:
; named nodes with fields, anonymous nodes, alternation lists, groupings,
; predicates, wildcards, negated fields, anchors, quantifiers, supertypes,
; and a MISSING-node pattern. Every top-level pattern is one doc section.

; Named-node patterns with a field and an outer definition capture.
(function_definition
name: (identifier) @function
body: (_) @_body) @definition.function

(class_definition
name: (identifier) @type) @definition.class

; Alternation list and a bare anonymous-node matcher.
["if" "else" "while" "return"] @keyword

"async" @keyword.coroutine

; Wildcard child, a negated field, anchors, and quantifiers.
(call_expression
function: (_) @call
!type_arguments)

(array . (identifier) @first (_)* @rest)

; Groupings with predicates: regex match, alternation, and an injection.
((identifier) @constant
(#match? @constant "^[A-Z][A-Z_]+$"))

((identifier) @type.builtin
(#any-of? @type.builtin "int" "str" "bool"))

((comment) @injection.content
(#set! injection.language "markdown"))

; Supertype/subtype match and a MISSING-node pattern.
(declaration/function_definition) @definition.function

(binary_expression (MISSING identifier))
Loading