Skip to main content

omgbase_surface/
reference.rs

1//! The `query_syntax` text (`spec/surface/README.md` §4: reference prose,
2//! unpinned). A condensed distillation of `docs/query-language.md`. Byte for
3//! byte the reference's `packages/core/src/mcp/reference.ts` — a test below
4//! compares the two when the monorepo is present, and another parses every
5//! example the text shows.
6
7/// The agent-facing OQX syntax reference the `query_syntax` tool returns.
8pub const QUERY_SYNTAX: &str = r##"# query — OQX syntax reference
9
10The `query` tool takes ONE plain OQX string (+ optional `limit`/`cursor`, and
11`bare: true` to drop the automatic `id`/`path` envelope — needs a `select`).
12There is no {from, filter, order} envelope: every concern is a clause of the
13string. Two rules tell the two syntaxes apart: DOT navigation belongs to the
14host object model (`doc.layer`, `section.blocks`, `dst.title`); WHITESPACE
15query directives belong to OQX — a nested query is `<receiver> collect|exists|
16count|first|single { <block> }` (a postfix directive + a `{ … }` block), never a
17method. Its distinctive power is the receiver-constrained nested query that
18correlates to the current row: `from docs where nodes exists { where kind ==
19"md:task" }` returns only the docs that themselves contain a matching node.
20
21  [select <items>] from <target> [where <pred>] [frontier <pred>] [follow <dest> [{ … }], …]
22                   [order by <expr> [asc|desc], …] [limit N] [offset N]
23  <target> count|exists|none|first|single { <block> }     (scalar/one-row form: a bare target at the root scope)
24
25CLAUSE ORDER IS FIXED: select, from, where, frontier, follow, order by, limit, offset —
26each at most once; an out-of-order clause is a parse error naming the order.
27Only `select` may drop its keyword, and only as the first clause
28(`path, era from docs where …`). A top-level predicate needs `where`, because
29`where` follows `from` (`era > 1600 from docs` is the clause-order error); a
30block may LEAD with a predicate (see Sugar), but a predicate after a projection
31still needs the keyword (`{ name where kind == "md:task" }`, not
32`{ name, kind == "md:task" }`); `from docs count` is an error (write
33`docs count { … }`). `where` and later `select` items may use the same body's
34`select` aliases (`select path, old: era < 1000 from docs where old`; an alias
35shadows a same-named field there). `and` / `or` / `not` are the connectives;
36the symbols `&&` / `||` / `!` are accepted synonyms (the words are canonical and
37what `print` writes).
38
39## Sugar (OQX 0.17)
40
41Every form here is shorthand for an explicit directive (the AST is the explicit form):
42  nodes { kind == "md:task" }        ≡ nodes collect { where kind == "md:task" } — a receiver block with
43                                       no consumer is collect; a block whose LEADING expression is a predicate
44                                       (comparison, call, !/is/not, in, literal, (…), a consumer test — anything
45                                       but a bare name, a dotted path or a ^lift) is where-first. Bare names still
46                                       project: nodes { name } ≡ nodes collect { name }; filter a bare field with
47                                       nodes { is checked }. Wherever a value stands (0.20), call arguments included:
48                                       size(children { is checked }); x distinct { … } ≡ x collect distinct { … }. After a
49                                       follow destination too: follow children { depth 2 } is that destination's block.
50  (a, b)                              a tuple (0.20): two or more expressions, concatenated one level flat — a value
51                                       (size((tags, aliases)), "x" in (tags, aliases)) and a receiver ((a, b) { … },
52                                       follow (before, after) { depth 2 }); (a) alone is grouping
53  refs(x)[0]                          ≡ refs(x) first { offset 0 }   positional (integer literal or ${binding});
54                                       out of range ⇒ absent; refs(x)[0].path navigates the row
55  ^docs[path == ^company]             ≡ ^docs first { where path == ^company }   first match or absent
56  ^docs[path == ^company]!            ≡ … single { … } required: exactly one, else filter_invalid
57  title!                              required: title, or an error naming the expression (and the row's $key — its id)
58                                       — never a filter, never a coercion (0!, ""! are values); tightest: a!.b vs a.b!
59  is x / not x                        truthiness of x, and its negation (prefix; `not` ≡ `!`)
60  x is y / x is not y                 identity ($key: a store row's id, else structural) — compares rows, which == does not;
61                                       x is null = absent; for scalars is ≡ ==; comparison precedence, no chaining
62  a and b / a or b                    the connectives (≡ && / ||: same precedence, short-circuit, value: data.title or path coalesces)
63  boss: ^docs[path == ^manager], bossName: boss.title   a select item may use the items to its LEFT
64  Reserved words (never a bare field name): from where select is not and or, true false null.
65  Contextual words (limit order offset follow collect count first distinct desc by in frontier depth) parse as
66  fields in expression position (select o: order from docs).
67
68Results are LEAN hits — { id, path } + whatever `select` projects (or `values`,
69`count`, `exists`, `none`). The envelope keys ARE the attributes: copy a hit's
70`id` straight into `where id == "…"` (no translation); `select id, path` is simply
71the envelope (no duplicate columns); any OTHER projection whose output key would
72be `id` or `path` must be aliased — `select data.path` is refused ("projection
73`path` collides with the hit envelope — alias it (`p: data.path`)"); a row
74surfacing as a value (an empty `collect { }`) is the same `{ id, path }`. Every
75other tool spells a row the same way (`docs_read` → `{ id, path, rev, properties,
76raw }`, `blocks_read` → `{ id, type, text, raw, hash, … }`, `docs_list` → `{ id,
77path, updated_at, blocks }`, a revision → `{ id, path, hash }`): hydrate by id via
78blocks_read/docs_read.
79
80## Paths
81
82Every path the surface returns is `/`-rooted — the form a reference is
83written in (`[x](/projects/oqx.md)`, `before: [/timeline/kickoff.md]`): a
84hit's `path`, an edge's `dst_path`, every tool's paths. Every path it
85accepts tolerates both forms (`/a.md` or `a.md`): tool arguments, `refs(x)`,
86`within(...)`, and a string LITERAL compared with `path`/`dst_path` (bare, or
87through a row handle: `doc.path`) by `==`/`!=` or passed to their
88`.startsWith(...)` (it is rooted first, so `path == "a.md"` and `path == "/a.md"`
89both match). A property holding a reference compares directly — and is never
90rooted: `where ^path in list(after)`, `where customer == ^path`,
91`where data.path == "/x.md"` (the author's `path:` key, as written) — no
92`"/" + ...` glue.
93
94## Targets & field namespaces
95
96THE NAME RULE: `$` belongs to OQX — $it, $key, $depth, $stop, $leaf, $frontier,
97$ordinal are values the LANGUAGE injects into a scope; omgbase adds NO $ names
98(a `$id` in a query is an unknown name, absent). Every engine attribute is a BARE
99snake_case name that WINS on collision — the convention the relations (children,
100blocks, out, in, doc, frontmatter, …) always followed: a bare name that is an
101engine attribute reads the engine's value, never yours. Your data is bare too;
102where a key of yours collides with an engine name, reach it through a source
103handle: `data.<k>` (the merged authored view — frontmatter + inline, exactly the
104read a bare non-colliding key performs), `frontmatter.<k>`, `inline.<k>`. So
105`path` is the engine's /-rooted location and `data.path` your `path:` key;
106`tags` is your tags ∪ the body #hashtags and `data.tags` the authored list alone;
107`title` the computed title (the first H1) and `data.title` your `title:`;
108`entries(data)` lists every authored top-level key. Same value, same name
109everywhere: a hit's `id` is docs_read's `id` and the `id` argument of blocks_read.
110
111  Reserved bare names per kind (engine attributes; handles; self; relations):
112    docs    id path raw hash updated_at title tags format | data frontmatter inline | doc | blocks nodes out in out_links in_links
113    blocks  id path doc_id raw hash updated_at type text attrs depth ordinal | block doc | children nodes out_links section
114    nodes   id path doc_id block_id kind name value attrs | section doc block | blocks subsections children
115    links   id path doc_id block_id via_id dst_id dst_path uri field dst_kind predicate provenance anchor | doc
116  A key of yours with any of these names is `data.<k>` (or frontmatter.<k> /
117  inline.<k> by source; attrs.<k> on a block or node).
118
119docs:
120  - bare identifier  = a document PROPERTY (frontmatter + inline `key:: value`,
121                       unioned; nested via dots: meta.owner). YAML/JSON docs
122                       expose the parsed object's keys the same way.
123  - source handles   = data.<k> (merged authored view) / frontmatter.<k> /
124                       inline.<k>; the whole bag as a collection via entries(data)
125                       / entries(frontmatter) / entries(inline) (see entries()).
126  - attributes       = id, path (/-rooted, see Paths), updated_at (ISO-8601,
127                       sorts chronologically), raw (the whole file, frontmatter
128                       included), hash (its sha256, 64 hex), title (the computed
129                       title: the first H1; data.title is the authored key), tags
130                       (authored tags ∪ body #hashtags, deduped, authored first;
131                       data.tags is the authored list), format (the doc's format —
132                       "markdown"/"yaml"/"json"; present on EVERY doc, so
133                       has(format) is always true and `format == "yaml"` is the
134                       filter).
135  - relations        = nodes, blocks, doc.out_links / doc.in_links (a doc's
136                       links as rows; the documents at their other end are
137                       out_links collect distinct { from dst } and
138                       in_links collect distinct { from doc } — there is no
139                       out / in relation).
140
141blocks:
142  - bare fields      = doc_id (the owning doc), type, text (normalized), ordinal
143                       (0-based among siblings), depth (0 at the top level), and
144                       every attrs key FLATTENED onto the row (bare `checked`
145                       reads attrs.checked; `attrs.<k>` still works; the
146                       structural fields win on collision; absent ⇒ false).
147  - attributes       = id, path (the owning doc's path), raw (the block's raw
148                       source — a container's raw holds its children), hash
149                       (its sha256 — the value update/split expect as
150                       expect.content_hash), updated_at.
151  - reach-through    = doc.<key> / doc.path / doc.id / doc.format = the
152                       CONTAINING doc (doc.id and doc.path cost nothing — they
153                       read the row's own doc_id / joined path, no lookup).
154  - relations        = block.children, block.nodes, block.out_links, section.
155  NOTE: inside a `follow`, $depth/$ordinal are ALWAYS the walk's metadata; a
156  block's own nesting is `depth` / `ordinal` (no $) everywhere.
157
158nodes:
159  - bare fields      = doc_id, block_id (null for a block-less node), kind
160                       (md:task / md:link / md:section / yaml:…), name, value,
161                       + attrs keys flattened (checked, level).
162  - attributes       = id, path.
163  - reach-through    = doc.<key> / doc.id / doc.path, block.type / block.text
164                       / block.id / block.path.
165  - relations        = section.blocks, section.children, section.subsections.
166
167links:  (the authored link graph as rows — one per open link: the relation a link
168        establishes; the md:link NODE is its occurrence in the text and via_id
169        points at it — a frontmatter link has a row and no node)
170  - bare fields      = doc_id (the SOURCE doc), block_id (the source block; null
171                       for a frontmatter edge), via_id, dst_id (the destination
172                       node id: a doc id, an external node id or phantom:<path>),
173                       dst_path (target doc path, /-rooted; NULL when
174                       dangling/external), uri (external URL; NULL otherwise),
175                       predicate ("references"/"embeds"/freeform), provenance
176                       ("link"/"frontmatter"/"inline_field"/…), dst_kind
177                       ("document"/"external"/"collection"), anchor, field (the
178                       frontmatter key / inline field it was authored in).
179  - attributes       = id, path (the SOURCE doc's path); doc.<key> reaches the
180                       source document. There is no from_commit: a query sees
181                       open edges only (history is blocks_history/changes_since).
182  - text()/semantic() are N/A on links.
183
184## Scoping (ADR-015; the root row since surface 2.0): bare names are LOCAL
185
186  name          the CURRENT row only — never falls through to an enclosing row
187                or the repository; an absent field is simply absent.
188  ^name         one scope OUT (the enclosing row's fields/intrinsics/lifts);
189                ^^name two scopes out. `^path` = the enclosing row's path.
190  ^docs ^nodes ^blocks ^links
191                THE ROOT ROW (surface 2.0, oqx 0.18): the repository is the root
192                scope's row, one scope out from a top-level row, so ^docs there is
193                the whole documents scan (uncorrelated until you add a ^ predicate);
194                one caret per enclosing block (^^docs from depth two), or the
195                absolute 0^docs from ANY depth. ^id / 0^id = the repository id.
196                ^$it = the root object: ^$it.docs ≡ ^docs, entries(^$it) names the
197                four collections. At the root scope itself a bare target is the scan
198                (docs count { … }, from docs, entries(docs)).
199                ERRORS: a bare `docs` INSIDE a block reads a property of the current
200                row, which has none — refused ("did you mean ^docs"); `^docs` AT THE
201                TOP LEVEL reaches past the root — refused (write the bare `docs`);
202                so is `$repo` (surface < 2.0; the message names the replacement).
203                nodes/blocks on a doc row are that doc's RELATIONS, not the root.
204  $it           the current item itself (a row, or each ELEMENT when the
205                receiver is a list property); ^$it = the enclosing row.
206                $value is NOT an intrinsic (since oqx 0.15): it reads a
207                property literally named $value, absent when there is none.
208  $key          the row's identity (0.20): a store row's id (doc/block/node/edge), an entries(x)
209                entry's key, a follow occurrence's walk identity; project $key: <expr> to rekey the
210                rows a block yields (a destination block's $key: is not a column).
211
212## Scalar semantics (@omgbase/oqx)
213
214  comparisons  ==  !=  <  <=  >  >=    strict & typed (5 == "5" is false); either
215                                       side may be a field (field-vs-field OK).
216  absence      null and a missing field are the same "absent" value; two absents
217               are EQUAL, so `absent != "x"` is TRUE and `absent == null` is TRUE.
218               Relational (< <= > >=) on an absent operand → FALSE. Bare `f` in
219               boolean position uses JS truthiness (absent/false/0/"" ⇒ false;
220               NOTE: "false", [] and {} are truthy). has(f) = explicit presence.
221  boolean      and  or  not    grouping ( … )   — && || ! are accepted synonyms
222  arithmetic   + - * / %    (+ concatenates if either side is a string)
223  membership   x in y       array → typed membership; string → substring;
224                            object → key exists; range → coverage. Any list
225                            property works directly: "pricing" in tags. list(f)
226                            coerces scalar-or-list (absent → []) — use it when a
227                            key is sometimes scalar, sometimes a list.
228  ranges       lo..hi (inclusive)  lo...hi (exclusive hi)  ..hi  lo..
229               era in 800..1680; works over ISO dates. range(prop) reads a
230               range-shaped STRING property as an interval: "2026-01-15" in range(window).
231  strings      x.contains("s") x.startsWith("p") x.endsWith("s") x.matches("^re$")
232               (real regex) x.lower() x.upper() size(x). ALL CASE-SENSITIVE —
233               fold with .lower(): title.lower().contains("aurora").
234  functions    has(f)  size(x)  list(x)  entries(x)  range(s)
235  literals     "str" 'str' 123 1.5 true false null
236  determinism  no now()/clock/random. Lists/structs are not literals; ternary is
237               not supported. Errors surface as filter_invalid.
238  scalars vs lists  a list-authored key never == a scalar (tags == "x" is false
239               when tags is a list) — use `"x" in tags` / `in list(tags)`.
240
241## Domain predicates (omgbase)
242
243  text("terms")        FTS5 PRUNING predicate (docs match via their blocks, nodes
244                       via the node index); composes anywhere, incl. nested scopes.
245  semantic("phrase")   embedding cosine SCORE (docs/blocks): threshold it
246                       (semantic("x") > 0.6), project it, or `order by semantic("x")
247                       desc` for top-K. Needs a provider that has embedded the repository; else provider_unavailable.
248  blocks target only:
249    under(id_or_heading)      block in the containment subtree / section range
250    under_heading("Launch")   an ancestor section heading contains the text (ci)
251    under_kind(type[, name])  an ancestor block has that type (name: its text
252                              contains / its key equals name)
253    within("path" | "d_..")   containing doc = id, exact path, or path glob (*);
254                              either path form
255    yaml_path("a.b")          YAML docs: the block at that key path
256    json_pointer("#/a/b")     JSON docs: the block at that pointer
257    has_edge("pred"[, target])  an open authored edge with that predicate leaves here
258    has_anchor()              block carries an authored ^ref
259    parent_type() == "x"      parent block's type (must be compared)
260    child_count() > 0         number of direct children (must be compared)
261  refs(x)              the live documents a property's document references name:
262                       x is a string, a list or absent; each "/a/b.md", "a/b.md"
263                       or "d_…" element resolves to that doc's row; dangling and
264                       non-string elements are dropped. Yields docs rows, so it is
265                       a source, a receiver or a follow destination:
266                       follow refs(before), refs(after) walks a timeline both ways.
267                       The reverse direction needs no function: a document's
268                       path is already the reference form, so
269                       ^docs { where ^path in list(after) } is
270                       "the documents whose `after` names me".
271  A HIT IS A STORE ROW: a top-level row that is not a doc/block/node/edge fails
272                       (filter_invalid). `follow before` over a list of paths
273                       reaches the STRINGS — write `follow refs(before)`.
274
275## Nested queries & consumers
276
277  <receiver> collect|exists|none|count|first|single { <block> }
278    receiver = a relation (nodes, blocks, doc.out_links, section.blocks, …), a
279               reach-through row (dst, doc: one row — dst { title }), a
280    list property (tags), entries(x), or a root collection (^docs; 0^docs at depth ≥ 2).
281    block    = [select …] [from E] [where …] [frontier …] [follow …] [order by …] [limit N] [offset N]
282               — the same fixed order as the top level (`from` optional; the
283               receiver supplies the rows). A leading name/alias list is the
284               projection (`select` dropped); a predicate always needs `where`.
285               A `from E` inside a block RE-PROJECTS the rows through a relation
286               (a typed flatMap): `docs collect { from nodes where kind == "md:task" }`
287               starts at docs, projects each through `nodes`, and queries the
288               resulting nodes; a scalar relation (`from doc`) contributes one
289               row; a missing or non-navigable relation is a loud error, never a
290               global scan.
291  exists    ≥1 row      nodes exists { where kind == "md:task" and not checked }
292  none      0 rows      nodes none { where kind == "md:task" and not checked }  ("every task done")
293  count     a number    nodes count { where kind == "md:task" } >= 2
294  collect   rows (default) — in select for nested results; in where with a lift
295  first / single   zero-or-one (single errors on >1) — lookups in select
296  distinct  on any consumer: dedup by projected value — nodes count distinct { select kind }
297  limit/offset  bound the block's rows AFTER where/order/distinct, BEFORE the
298            consumer reduces: nodes exists { offset 1 } = at least two;
299            first { … offset 1 } = the second.
300  lift      `^name:` in a where-collect binds values outward:
301            select path, open from docs where nodes collect { ^open: value where kind == "md:task" and not checked }
302  joins     ^<t> exists { where slug == ^ref }  semi-join; not … exists  anti-join;
303            ^nodes single { where kind == "person" and attrs.id == ^owner_id }  lookup;
304            x in ^keys  membership over a lifted set.
305
306## select — projection
307
308  select a, alias: expr, nested: nodes collect { … }
309  bare key → the attribute when it is one (id, path, title, …), else the property
310  (scalar-authored → scalar; list → array); data.<k> forces the property;
311  a call/arithmetic needs an alias (n: size(tags)) unless in values mode;
312  an output key of id/path other than the attribute itself needs an alias.
313  raw         whole file bytes (docs) — docs_read is cheaper for one doc.
314  distinct    select distinct type   (dedup hits by projected value)
315  values      select <ONE item> values → bare values instead of records:
316                select distinct type values from docs          → values: ["hub","lab-note",…]
317                select path values from docs                   → values: ["/index.md", …]
318                tags: tags collect { $it values }             → a plain array
319                h: nodes first { name values where kind == "md:section" order by first_ordinal }
320  entries(x)  a record as a collection ($key / $it per entry), key order:
321                select fm: entries(data) collect { k: $key, v: $it }
322                where entries(frontmatter) exists { where $key == "era" and $it > 1600 }
323                entries(inline), entries(attrs), a nested map, or a list (index keys)
324  $semantic_score is not a field — project semantic("…") under an alias instead.
325
326## order by, limit/offset, pagination
327
328  order by era desc, path asc      absent values sort LAST (asc); ties → (path, id)
329  limit N / offset N               define the result SET (integer literals);
330                                   the tool's limit/cursor then page WITHIN it
331  cursor                           opaque keyset on (path,id); a custom order by
332                                   disables it (you get the top page + truncated)
333
334## Graph traversal (follow) and links
335
336  The citation graph is the LINK ROWS walked through a destination block:
337  dst is a link's destination document (absent for an external or dangling
338  link — it then contributes no successor), doc its source.
339  from docs where <seed> follow out_links collect { from dst }     docs the seed links TO
340  from docs where <seed> follow in_links collect { from doc }      backlinks
341    follow out_links collect { from dst depth 3 }                  bound (1..8, default 8) — a clause of the block
342    follow out_links collect { from dst where layer == "canon" }   keep only matching successors
343    follow in_links collect { from doc where ^path in list(before) }   ^ = the row being expanded
344                                                   (^^ = the walk's enclosing scope)
345    follow out_links collect { from dst where predicate == "depends_on" }   one predicate only
346    follow out_links collect { from dst }, in_links collect { from doc }
347                                                   several destinations: their union
348                                                   (one step dedups by identity)
349    follow ^docs { where out_links exists { where dst_path == ^^path } }
350                                                   a destination block over the root's
351                                                   documents, re-read per frontier row
352                                                   (^ = that row) = the backlinks walk
353    follow out_links collect { from dst frontier type == "practitioner" }   cut, keeping the frontier row
354    from docs where type == "process" frontier !verified follow out_links collect { from dst }
355                                                   the BODY's frontier applies to the seeds:
356                                                   an unverified seed is kept, not expanded
357    follow distinct out_links collect { from dst } / … { $key: group from dst }   identity control ($key)
358    follow refs(before), ^docs { where ^path in list(after) }    forward over `before`, backward
359                                                   over every doc whose `after` names me
360  Per-occurrence metadata: $depth (seed = 1), $stop (interior|leaf|frontier|
361  depth|cycle), $leaf/$frontier, $ordinal (a deterministic 1..N rank over the
362  walk, ordered by depth then path — `order by $ordinal` for walk order,
363  `where $ordinal <= N` for a deterministic budget cut), $key — usable in
364  select/order by and the top-level post-walk where (a where conjunct that
365  reads one filters the walk's RESULT; the other conjuncts remain the seed
366  predicate), NOT metadata of the candidates a destination block's where reads,
367  nor valid inside a consumer directive. Cyclic graphs are safe: a revisited
368  node is admitted once as a `$stop == "cycle"` occurrence and never
369  re-expanded. The default keeps one occurrence per distinct walk path;
370  `follow distinct` dedups reached rows by identity.
371  Destination blocks (OQX 0.20): a brace after ANY destination opens that
372  destination's block — children { depth 2 }, refs(x) { depth 2 }, out_links
373  collect { from dst depth 2 }, ^docs { where ^path in list(after) } — with the
374  0.17 body rules (where-first, projections) and two clauses of its own after
375  `where`: frontier <pred> (occurrences reached through this destination that
376  match are frontiers; ^ there is the walk's enclosing scope) and depth <n>
377  (this destination expands a frontier row only while $depth < n). The body
378  that carries the follow may write frontier <pred> too, after its where and
379  before follow: it applies to the SEEDS (read like the body's where) — a
380  matching seed stays in the result with $stop == "frontier" and is not
381  expanded; each destination's frontier is its own. There is no options block;
382  the old `by` is $key. A bare follow ^docs (no brace, no consumer) is an
383  error naming both block spellings.
384  Type-preserving relations: block.children, section.children, section.subsections;
385  the citation graph is out_links collect { from dst } / in_links collect { from doc }.
386  from links where dst_path == "/notes/x.md"       inbound links to a doc, as rows
387  select d: dst.title, k: dst_kind from links       the destination document's title
388  from docs where out_links exists { where dst.layer == "draft" }   links into drafts
389  select b: in_links collect distinct { from doc } from docs     backlinks as { id, path }
390  select doc_id, dst_path from links where predicate == "depends_on"
391  from docs where not doc.in_links exists { }      orphans (nothing links in)
392  (The `graph` tool is a convenience wrapper that compiles to a follow query.)
393
394## semantic grain
395
396from docs ranks whole DOCUMENTS (one hit per file); from blocks ranks PASSAGES.
397text()/where prune the candidate set; only order by reweights.
398
399## Examples
400
401  from docs where layer == "working"
402  from docs where path.startsWith("/guides/") and updated_at >= "2026-08-01"
403  select layer, tags from docs where "pricing" in list(tags)
404  from docs where inline.owner == "alice"
405  from docs where title.lower().contains("q3 plan")
406  from docs where format == "yaml"
407  from docs where type == "practitioner" order by era desc limit 2
408  from docs where nodes none { where kind == "md:task" and not checked }
409  from docs where tags exists { where $it == "tria-prima" }
410  select fm: entries(data) collect { k: $key, v: $it } from docs where path == "/x.md"
411  select owner_id, owner: ^nodes single { where kind == "person" and attrs.id == ^owner_id } from docs
412  from blocks where type == "task" and not checked and under_heading("Launch") and doc.layer == "working"
413  from blocks where type == "paragraph" and has_edge("references", "d_92aaaaa")
414  select path, $depth from docs where path == "/timeline/review.md" follow refs(before), refs(after)
415  select path, next: ^docs collect { path where ^path in list(before) } from docs where type == "milestone"
416  select path, n: size(^docs), repo: ^id from docs limit 1
417  select p: data.path, t: data.title from docs where has(data.path)
418  docs { type == "substance" and not verified }
419  select subject, process: ^docs[slug == ^subject]!.path from docs where type == "lab-note"
420  select path, back: nodes first { select p: 0^docs collect { path where ^^path in list(after) } values } from docs where type == "milestone"
421  select path, prior: refs(before) collect { path, when } from docs where type == "milestone"
422  from blocks order by semantic("identity preservation across edits") desc limit 10
423  select uri from links where dst_kind == "external"
424  docs count { where layer == "canon" }
425
426Common mistake: `$path`/`$id` (the 2.x spelling) — omgbase has no $ names; write
427`path`/`id`. Another: `where tags == "x"` on a list-valued key is false — write
428`"x" in tags`. Another: `select data.path` — alias it (`p: data.path`).
429"##;
430
431#[cfg(test)]
432mod tests {
433    //! Every OQX example the text shows must parse — extracted MECHANICALLY by
434    //! the rule the reference's `packages/core/src/mcp/reference.test.ts` states
435    //! (and mirrors here): (1) only indented lines are candidates — an error
436    //! example belongs in prose, a code line is a valid query; (2) in
437    //! `## Examples` every indented line is a whole query; (3) elsewhere the
438    //! query is the text before the first run of three or more spaces, skipped
439    //! when it holds a grammar placeholder (`<pred>`), an ellipsis (`…`) or
440    //! sentence punctuation (wrapped commentary); (4) a candidate is a query
441    //! when it begins `select `, `from `, or a root target followed by a
442    //! consumer or `{` (a `^`-led line is an expression fragment); (5) a
443    //! `select`-led candidate without a depth-0 ` from ` is a fragment.
444    use super::QUERY_SYNTAX;
445
446    const TARGETS: [&str; 4] = ["docs", "blocks", "nodes", "links"];
447    const CONSUMERS: [&str; 7] = [
448        "collect", "exists", "none", "count", "first", "single", "distinct",
449    ];
450
451    fn has_top_level_from(q: &str) -> bool {
452        let chars: Vec<char> = q.chars().collect();
453        let (mut depth, mut quote): (i32, Option<char>) = (0, None);
454        for (i, &c) in chars.iter().enumerate() {
455            if let Some(open) = quote {
456                if c == open {
457                    quote = None;
458                }
459                continue;
460            }
461            match c {
462                '"' | '\'' => quote = Some(c),
463                '{' => depth += 1,
464                '}' => depth -= 1,
465                _ if depth == 0 && chars[i..].starts_with(&[' ', 'f', 'r', 'o', 'm', ' ']) => {
466                    return true;
467                }
468                _ => {}
469            }
470        }
471        false
472    }
473
474    /// `<seed>`, `<pred>`, `<ONE item>`: a `<` + letter … `>` run of letters, `_`, `|`, space or `…`.
475    fn has_placeholder(q: &str) -> bool {
476        let chars: Vec<char> = q.chars().collect();
477        for (i, &c) in chars.iter().enumerate() {
478            if c != '<' || !chars.get(i + 1).is_some_and(|n| n.is_ascii_alphabetic()) {
479                continue;
480            }
481            let mut j = i + 1;
482            while j < chars.len()
483                && (chars[j].is_ascii_alphabetic() || matches!(chars[j], '_' | '|' | ' ' | '…'))
484            {
485                j += 1;
486            }
487            if chars.get(j) == Some(&'>') {
488                return true;
489            }
490        }
491        false
492    }
493
494    fn is_prose(q: &str) -> bool {
495        q.contains(". ")
496            || q.contains("; ")
497            || q.ends_with('.')
498            || q.ends_with(':')
499            || q.ends_with(';')
500    }
501
502    fn root_consumer(q: &str) -> bool {
503        let mut it = q.splitn(3, ' ');
504        let (Some(target), Some(next)) = (it.next(), it.next()) else {
505            return false;
506        };
507        TARGETS.contains(&target) && (CONSUMERS.contains(&next) || next.starts_with('{'))
508    }
509
510    /// `(line, section, query)` for every admitted example, plus the skipped `select` fragments.
511    fn extract(text: &str) -> (Vec<(usize, String, String)>, Vec<String>) {
512        let (mut queries, mut fragments) = (Vec::new(), Vec::new());
513        let mut section = String::new();
514        for (i, raw) in text.lines().enumerate() {
515            if let Some(h) = raw.strip_prefix("## ") {
516                section = h.trim().to_owned();
517                continue;
518            }
519            if !raw.starts_with("  ") || raw.trim().is_empty() {
520                continue;
521            }
522            let trimmed = raw.trim();
523            if section == "Examples" {
524                queries.push((i + 1, section.clone(), trimmed.to_owned()));
525                continue;
526            }
527            let query = trimmed.split("   ").next().unwrap_or("").trim();
528            if has_placeholder(query) || query.contains('…') || is_prose(query) {
529                continue;
530            }
531            if !(query.starts_with("select ") || query.starts_with("from ") || root_consumer(query))
532            {
533                continue;
534            }
535            if query.starts_with("select ") && !has_top_level_from(query) {
536                fragments.push(query.to_owned());
537                continue;
538            }
539            queries.push((i + 1, section.clone(), query.to_owned()));
540        }
541        (queries, fragments)
542    }
543
544    #[test]
545    fn extraction_is_substantial() {
546        let (queries, fragments) = extract(QUERY_SYNTAX);
547        let in_examples = queries.iter().filter(|q| q.1 == "Examples").count();
548        assert!(in_examples >= 20, "{in_examples} examples");
549        assert!(
550            queries.len() - in_examples >= 10,
551            "{} outside Examples",
552            queries.len() - in_examples
553        );
554        for f in &fragments {
555            assert!(
556                !TARGETS.iter().any(|t| f.contains(&format!(" from {t}"))),
557                "a fragment must not carry a top-level from: {f}"
558            );
559        }
560    }
561
562    #[test]
563    fn every_example_parses() {
564        let (queries, _) = extract(QUERY_SYNTAX);
565        let failures: Vec<String> = queries
566            .iter()
567            .filter_map(|(line, section, q)| {
568                oqx::parse_string(q)
569                    .err()
570                    .map(|e| format!("line {line} ({section}): {q}\n    {e}"))
571            })
572            .collect();
573        assert!(
574            failures.is_empty(),
575            "query_syntax examples that do not parse:\n{}",
576            failures.join("\n")
577        );
578    }
579
580    #[test]
581    fn error_examples_live_in_prose() {
582        let (queries, _) = extract(QUERY_SYNTAX);
583        for bad in [
584            "era > 1600 from docs",
585            "from docs count",
586            "from docs where \"pricing\" in list(tags) select layer, tags",
587        ] {
588            assert!(oqx::parse_string(bad).is_err());
589            assert!(
590                !queries.iter().any(|q| q.2 == bad),
591                "{bad} is an error example and must stay in prose"
592            );
593        }
594    }
595
596    #[test]
597    fn clause_order_sentence_matches_the_parser() {
598        let e = oqx::parse_string("from docs where x select y")
599            .unwrap_err()
600            .to_string();
601        let (_, rest) = e
602            .split_once("OQX clause order is ")
603            .expect("the parser names the order");
604        let order = rest
605            .split(" (")
606            .next()
607            .unwrap()
608            .trim_end_matches(['.', ' ']);
609        assert!(
610            QUERY_SYNTAX.contains(&format!("CLAUSE ORDER IS FIXED: {order}")),
611            "the text's order sentence drifted from the parser's ({order})"
612        );
613    }
614
615    /// The twin is byte for byte the reference's `QUERY_SYNTAX` (the TS
616    /// template literal, unescaped). Skipped outside the monorepo.
617    #[test]
618    fn twin_is_byte_identical_to_the_reference() {
619        let path = concat!(
620            env!("CARGO_MANIFEST_DIR"),
621            "/../../packages/core/src/mcp/reference.ts"
622        );
623        let Ok(ts) = std::fs::read_to_string(path) else {
624            return;
625        };
626        let open = "export const QUERY_SYNTAX = `";
627        let start = ts.find(open).expect("the TS export") + open.len();
628        let end = ts.rfind("`;").expect("the TS literal's end");
629        let mut text = String::new();
630        let mut chars = ts[start..end].chars();
631        while let Some(c) = chars.next() {
632            if c == '\\' {
633                if let Some(n) = chars.next() {
634                    text.push(n);
635                }
636            } else {
637                text.push(c);
638            }
639        }
640        assert!(
641            text == QUERY_SYNTAX,
642            "crates/omgbase-surface/src/reference.rs drifted from packages/core/src/mcp/reference.ts"
643        );
644    }
645}