@omgbase/core 0.10.1 → 0.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/README.md +10 -10
  2. package/dist/src/core/read/document.d.ts +18 -5
  3. package/dist/src/core/read/document.js +21 -7
  4. package/dist/src/core/read/nodes.d.ts +7 -7
  5. package/dist/src/core/read/nodes.js +5 -3
  6. package/dist/src/core/read/reader.d.ts +11 -7
  7. package/dist/src/core/read/reader.js +8 -10
  8. package/dist/src/format/adapter.d.ts +1 -1
  9. package/dist/src/graph/history.d.ts +20 -14
  10. package/dist/src/graph/history.js +12 -12
  11. package/dist/src/graph/inbound-links.d.ts +4 -4
  12. package/dist/src/graph/inbound-links.js +4 -4
  13. package/dist/src/graph/link-health.d.ts +8 -8
  14. package/dist/src/graph/link-health.js +6 -6
  15. package/dist/src/graph/links.d.ts +3 -3
  16. package/dist/src/graph/links.js +4 -4
  17. package/dist/src/index.d.ts +2 -1
  18. package/dist/src/index.js +2 -1
  19. package/dist/src/mcp/arguments.d.ts +27 -0
  20. package/dist/src/mcp/arguments.js +147 -0
  21. package/dist/src/mcp/errors.d.ts +1 -1
  22. package/dist/src/mcp/graph.d.ts +16 -9
  23. package/dist/src/mcp/graph.js +58 -35
  24. package/dist/src/mcp/reference.d.ts +1 -1
  25. package/dist/src/mcp/reference.js +200 -111
  26. package/dist/src/mcp/server.d.ts +9 -1
  27. package/dist/src/mcp/server.js +359 -268
  28. package/dist/src/mutate/apply.d.ts +11 -4
  29. package/dist/src/mutate/apply.js +3 -2
  30. package/dist/src/mutate/docs.js +7 -7
  31. package/dist/src/mutate/macros.d.ts +4 -1
  32. package/dist/src/mutate/macros.js +1 -1
  33. package/dist/src/mutate/ops.js +2 -2
  34. package/dist/src/mutate/opset.d.ts +8 -5
  35. package/dist/src/mutate/plan-update.js +9 -8
  36. package/dist/src/oqx-js/context.d.ts +17 -4
  37. package/dist/src/oqx-js/context.js +268 -131
  38. package/dist/src/oqx-js/planner.d.ts +3 -3
  39. package/dist/src/oqx-js/planner.js +14 -15
  40. package/dist/src/oqx-js/root-row.d.ts +10 -3
  41. package/dist/src/oqx-js/root-row.js +18 -8
  42. package/dist/src/oqx-js/run.d.ts +6 -0
  43. package/dist/src/oqx-js/run.js +72 -25
  44. package/dist/src/oqx-js/sql/scan.js +7 -7
  45. package/dist/src/oqx-js/sql/translate.d.ts +12 -9
  46. package/dist/src/oqx-js/sql/translate.js +183 -75
  47. package/dist/src/oqx-js/store-index.d.ts +1 -1
  48. package/dist/src/oqx-js/store-index.js +28 -26
  49. package/dist/src/search/cel/ast.d.ts +1 -1
  50. package/dist/src/search/cel/compile.d.ts +1 -1
  51. package/dist/src/search/cel/compile.js +43 -70
  52. package/dist/src/search/cel/parser.js +1 -1
  53. package/dist/src/search/resolve.d.ts +2 -0
  54. package/dist/src/search/rrf.js +2 -2
  55. package/dist/src/search/text.d.ts +2 -2
  56. package/dist/src/search/text.js +1 -1
  57. package/dist/src/spec-versions.d.ts +6 -6
  58. package/dist/src/spec-versions.js +5 -5
  59. package/dist/src/surface-paths.d.ts +41 -8
  60. package/dist/src/surface-paths.js +34 -18
  61. package/dist/src/sync/admin.d.ts +2 -1
  62. package/dist/src/sync/admin.js +1 -1
  63. package/package.json +2 -2
@@ -1 +1 @@
1
- export declare const QUERY_SYNTAX = "# query \u2014 OQX syntax reference\n\nThe `query` tool takes ONE plain OQX string (+ optional `limit`/`cursor`). There\nis no {from, filter, order} envelope: every concern is a clause of the string.\n\n [select <items>] from <target> [where <pred>] [follow <dest>, \u2026 [{ \u2026 }]]\n [order by <expr> [asc|desc], \u2026] [limit N] [offset N]\n <target> count|exists|none|first|single { <block> } (scalar/one-row form: a bare target at the root scope)\n\nCLAUSE ORDER IS FIXED: select, from, where, follow, order by, limit, offset \u2014\neach at most once; an out-of-order clause is a parse error naming the order.\nOnly `select` may drop its keyword, and only as the first clause\n(`$path, era from docs where \u2026`). A top-level predicate needs `where`, because\n`where` follows `from` (`era > 1600 from docs` is the clause-order error); a\nblock may LEAD with a predicate (see Sugar), but a predicate after a projection\nstill needs the keyword (`{ name where kind == \"md:task\" }`, not\n`{ name, kind == \"md:task\" }`); `from docs count` is an error (write\n`docs count { \u2026 }`). `where` and later `select` items may use the same body's\n`select` aliases (`select $path, old: era < 1000 from docs where old`; an alias\nshadows a same-named field there). `and` / `or` / `not` are the connectives;\nthe symbols `&&` / `||` / `!` are accepted synonyms (the words are canonical and\nwhat `print` writes).\n\n## Sugar (OQX 0.17)\n\nEvery form here is shorthand for an explicit directive (the AST is the explicit form):\n nodes { kind == \"md:task\" } \u2261 nodes collect { where kind == \"md:task\" } \u2014 a receiver block with\n no consumer is collect; a block whose LEADING expression is a predicate\n (comparison, call, !/is/not, in, literal, (\u2026), a consumer test \u2014 anything\n but a bare name, a dotted path or a ^lift) is where-first. Bare names still\n project: nodes { name } \u2261 nodes collect { name }; filter a bare field with\n nodes { is checked }. Not after follow: follow children { depth 2 } is options.\n refs(x)[0] \u2261 refs(x) first { offset 0 } positional (integer literal or ${binding});\n out of range \u21D2 absent; refs(x)[0].$path navigates the row\n ^docs[$path == ^company] \u2261 ^docs first { where $path == ^company } first match or absent\n ^docs[$path == ^company]! \u2261 \u2026 single { \u2026 } required: exactly one, else filter_invalid\n title! required: title, or an error naming the expression (and the row's $id)\n \u2014 never a filter, never a coercion (0!, \"\"! are values); tightest: a!.b vs a.b!\n is x / not x truthiness of x, and its negation (prefix; `not` \u2261 `!`)\n x is y / x is not y identity (a row's id, else structural) \u2014 compares rows, which == does not;\n x is null = absent; for scalars is \u2261 ==; comparison precedence, no chaining\n a and b / a or b the connectives (\u2261 && / ||: same precedence, short-circuit, value: title or $path coalesces)\n boss: ^docs[$path == ^manager], bossName: boss.$title a select item may use the items to its LEFT\n Reserved words (never a bare field name): from where select is not and or, true false null.\n\nResults are LEAN hits \u2014 {id, path} + whatever `select` projects (or `values`,\n`count`, `exists`, `none`). Hydrate full content by id via nodes_get/docs_read.\n\n## Paths\n\nEvery path the surface returns is `/`-rooted \u2014 the form a reference is\nwritten in (`[x](/projects/oqx.md)`, `before: [/timeline/kickoff.md]`): a\nhit's `path`, `$path`, `$dst_path`, every tool's paths. Every path it\naccepts tolerates both forms (`/a.md` or `a.md`): tool arguments, `refs(x)`,\n`within(...)`, and a string LITERAL compared with `$path`/`$dst_path` by\n`==`/`!=` or passed to their `.startsWith(...)` (it is rooted first, so\n`$path == \"a.md\"` and `$path == \"/a.md\"` both match). A property holding a\nreference compares directly: `where ^$path in list(after)`,\n`where customer == ^$path` \u2014 no `\"/\" + ...` glue.\n\n## Targets & field namespaces\n\nThe sigil rule: `$`-prefixed names are engine intrinsics; BARE identifiers are\nyour content. On docs a bare first segment that collides with an intrinsic base\nname (id/path/updated_at/content_hash/body) is REJECTED with a \"did you mean\n$X?\" hint (bare `path` would silently read an absent frontmatter key). Force the\nproperty with `frontmatter.<k>`. `title`/`tags` are NOT reserved: `$title`/`$tags`\nare computed and never shadow an authored `title:`/`tags:` key.\n\ndocs:\n - bare identifier = a document PROPERTY (frontmatter + inline `key:: value`,\n unioned; nested via dots: meta.owner). YAML/JSON docs\n expose the parsed object's keys the same way.\n - source-scoped = frontmatter.<k> / inline.<k>; the whole bag as a collection\n via entries(frontmatter) / entries(inline) (see entries()).\n - computed = $title (first H1), $tags (body #hashtags), format\n (the doc's format \u2014 \"markdown\"/\"yaml\"/\"json\"; a bare\n computed field, present on EVERY doc, so has(format) is\n always true and `format == \"yaml\"` is the filter).\n - intrinsics = $id, $path (/-rooted, see Paths), $updated_at (ISO-8601,\n sorts chronologically), $body (whole file), $content_hash.\n - relations = nodes, blocks, doc.out / doc.in (citation graph),\n doc.out_edges / doc.in_edges (a doc's edges as rows).\n\nblocks:\n - bare fields = type, text, and every attrs key FLATTENED onto the row\n (bare `checked` reads attrs.checked; `attrs.<k>` still\n works; type/text win on collision; absent \u21D2 false).\n - intrinsics = $id, $doc, $path, $ordinal, $depth, $updated_at, $body\n (the block text), $content_hash (the block's OWN raw hash \u2014\n the value update/split expect).\n - reach-through = doc.<key> / doc.$path / doc.format = the CONTAINING doc.\n - relations = block.children, block.nodes, block.out_edges, section.\n\nnodes:\n - bare fields = kind (md:task / md:link / md:section / yaml:\u2026), name,\n value, + attrs keys flattened (checked, level).\n - intrinsics = $id (=$node_id), $doc_id, $block_id, $path.\n - reach-through = doc.<key>, block.type / block.text.\n - relations = section.blocks, section.children, section.subsections.\n\nedges: (the authored link graph as rows \u2014 one per open edge)\n - bare fields = predicate (\"references\"/\"embeds\"/freeform), provenance\n (\"link\"/\"frontmatter\"/\"inline_field\"/\u2026), dst_kind\n (\"document\"/\"external\"/\"collection\"), anchor, src_field\n - intrinsics = $id, $src, $dst, $dst_path (target doc path, /-rooted;\n NULL when dangling/external), $dst_uri (external URL;\n NULL otherwise), $src_block, $via, $from_commit; $path\n and doc.<key> reach the SOURCE document.\n - text()/semantic() are N/A on edges.\n\n## Scoping (ADR-015; the root row since surface 2.0): bare names are LOCAL\n\n name the CURRENT row only \u2014 never falls through to an enclosing row\n or the repository; an absent field is simply absent.\n ^name one scope OUT (the enclosing row's fields/intrinsics/lifts);\n ^^name two scopes out. `^$path` = the enclosing row's path.\n ^docs ^nodes ^blocks ^edges\n THE ROOT ROW (surface 2.0, oqx 0.18): the repository is the root\n scope's row, one scope out from a top-level row, so ^docs there is\n the whole documents scan (uncorrelated until you add a ^ predicate);\n one caret per enclosing block (^^docs from depth two), or the\n absolute 0^docs from ANY depth. ^$id / 0^$id = the repository id.\n ^$it = the root object: ^$it.docs \u2261 ^docs, entries(^$it) names the\n four collections. At the root scope itself a bare target is the scan\n (docs count { \u2026 }, from docs, entries(docs)).\n ERRORS: a bare `docs` INSIDE a block reads a property of the current\n row, which has none \u2014 refused (\"did you mean ^docs\"); `^docs` AT THE\n TOP LEVEL reaches past the root \u2014 refused (write the bare `docs`);\n so is `$repo` (surface < 2.0; the message names the replacement).\n nodes/blocks on a doc row are that doc's RELATIONS, not the root.\n $it the current item itself (a row, or each ELEMENT when the\n receiver is a list property); ^$it = the enclosing row.\n $value is NOT an intrinsic (since oqx 0.15): it reads a\n property literally named $value, absent when there is none.\n $key inside an entries(x) block: the entry's key.\n\n## Scalar semantics (@omgbase/oqx)\n\n comparisons == != < <= > >= strict & typed (5 == \"5\" is false); either\n side may be a field (field-vs-field OK).\n absence null and a missing field are the same \"absent\" value; two absents\n are EQUAL, so `absent != \"x\"` is TRUE and `absent == null` is TRUE.\n Relational (< <= > >=) on an absent operand \u2192 FALSE. Bare `f` in\n boolean position uses JS truthiness (absent/false/0/\"\" \u21D2 false;\n NOTE: \"false\", [] and {} are truthy). has(f) = explicit presence.\n boolean and or not grouping ( \u2026 ) \u2014 && || ! are accepted synonyms\n arithmetic + - * / % (+ concatenates if either side is a string)\n membership x in y array \u2192 typed membership; string \u2192 substring;\n object \u2192 key exists; range \u2192 coverage. Any list\n property works directly: \"pricing\" in tags. list(f)\n coerces scalar-or-list (absent \u2192 []) \u2014 use it when a\n key is sometimes scalar, sometimes a list.\n ranges lo..hi (inclusive) lo...hi (exclusive hi) ..hi lo..\n era in 800..1680; works over ISO dates. range(prop) reads a\n range-shaped STRING property as an interval: \"2026-01-15\" in range(window).\n strings x.contains(\"s\") x.startsWith(\"p\") x.endsWith(\"s\") x.matches(\"^re$\")\n (real regex) x.lower() x.upper() size(x). ALL CASE-SENSITIVE \u2014\n fold with .lower(): $title.lower().contains(\"aurora\").\n functions has(f) size(x) list(x) entries(x) range(s)\n literals \"str\" 'str' 123 1.5 true false null\n determinism no now()/clock/random. Lists/structs are not literals; ternary is\n not supported. Errors surface as filter_invalid.\n scalars vs lists a list-authored key never == a scalar (tags == \"x\" is false\n when tags is a list) \u2014 use `\"x\" in tags` / `in list(tags)`.\n\n## Domain predicates (omgbase)\n\n text(\"terms\") FTS5 PRUNING predicate (docs match via their blocks, nodes\n via the node index); composes anywhere, incl. nested scopes.\n semantic(\"phrase\") embedding cosine SCORE (docs/blocks): threshold it\n (semantic(\"x\") > 0.6), project it, or `order by semantic(\"x\")\n desc` for top-K. Needs a provider; else semantic_unavailable.\n blocks target only:\n under(id_or_heading) block in the containment subtree / section range\n under_heading(\"Launch\") an ancestor section heading contains the text (ci)\n under_kind(type[, name]) an ancestor block has that type (name: its text\n contains / its key equals name)\n within(\"path\" | \"d_..\") containing doc = id, exact path, or path glob (*);\n either path form\n yaml_path(\"a.b\") YAML docs: the block at that key path\n json_pointer(\"#/a/b\") JSON docs: the block at that pointer\n has_edge(\"pred\"[, target]) an open authored edge with that predicate leaves here\n has_anchor() block carries an authored ^ref\n parent_type() == \"x\" parent block's type (must be compared)\n child_count() > 0 number of direct children (must be compared)\n refs(x) the live documents a property's document references name:\n x is a string, a list or absent; each \"/a/b.md\", \"a/b.md\"\n or \"d_\u2026\" element resolves to that doc's row; dangling and\n non-string elements are dropped. Yields docs rows, so it is\n a source, a receiver or a follow destination:\n follow refs(before), refs(after) walks a timeline both ways.\n The reverse direction needs no function: a document's\n $path is already the reference form, so\n ^docs collect { where ^$path in list(after) } is\n \"the documents whose `after` names me\".\n A HIT IS A STORE ROW: a top-level row that is not a doc/block/node/edge fails\n (filter_invalid). `follow before` over a list of paths\n reaches the STRINGS \u2014 write `follow refs(before)`.\n\n## Nested queries & consumers\n\n <receiver> collect|exists|none|count|first|single { <block> }\n receiver = a relation (nodes, blocks, doc.out_edges, section.blocks, \u2026), a\n list property (tags), entries(x), or a root collection (^docs; 0^docs at depth \u2265 2).\n block = [select \u2026] [where \u2026] [follow \u2026] [order by \u2026] [limit N] [offset N]\n \u2014 the same fixed order as the top level (`from` optional; the\n receiver supplies the rows). A leading name/alias list is the\n projection (`select` dropped); a predicate always needs `where`.\n exists \u22651 row nodes exists { where kind == \"md:task\" and not checked }\n none 0 rows nodes none { where kind == \"md:task\" and not checked } (\"every task done\")\n count a number nodes count { where kind == \"md:task\" } >= 2\n collect rows (default) \u2014 in select for nested results; in where with a lift\n first / single zero-or-one (single errors on >1) \u2014 lookups in select\n distinct on any consumer: dedup by projected value \u2014 nodes count distinct { select kind }\n limit/offset bound the block's rows AFTER where/order/distinct, BEFORE the\n consumer reduces: nodes exists { offset 1 } = at least two;\n first { \u2026 offset 1 } = the second.\n lift `^name:` in a where-collect binds values outward:\n select $path, open from docs where nodes collect { ^open: value where kind == \"md:task\" and not checked }\n joins ^<t> exists { where slug == ^ref } semi-join; not \u2026 exists anti-join;\n ^nodes single { where kind == \"person\" and attrs.id == ^owner_id } lookup;\n x in ^keys membership over a lifted set.\n\n## select \u2014 projection\n\n select a, alias: expr, nested: nodes collect { \u2026 }\n bare key \u2192 the property (scalar-authored \u2192 scalar; list \u2192 array); $-intrinsics;\n a call/arithmetic needs an alias (n: size(tags)) unless in values mode.\n \"$body\" whole file bytes (docs) \u2014 docs_read is cheaper for one doc.\n distinct select distinct type (dedup hits by projected value)\n values select <ONE item> values \u2192 bare values instead of records:\n from docs select distinct type values \u2192 values: [\"hub\",\"lab-note\",\u2026]\n select $path values from docs \u2192 values: [\"/index.md\", \u2026]\n tags: tags collect { $it values } \u2192 a plain array\n h: nodes first { name values where kind == \"md:section\" order by first_ordinal }\n entries(x) a record as a collection ($key / $it per entry), key order:\n select fm: entries(frontmatter) collect { k: $key, v: $it }\n where entries(frontmatter) exists { where $key == \"era\" and $it > 1600 }\n entries(inline), entries(attrs), a nested map, or a list (index keys)\n $semantic_score is not a field \u2014 project semantic(\"\u2026\") under an alias instead.\n\n## order by, limit/offset, pagination\n\n order by era desc, $path asc absent values sort LAST (asc); ties \u2192 (path, id)\n limit N / offset N define the result SET (integer literals);\n the tool's limit/cursor then page WITHIN it\n cursor opaque keyset on (path,id); a custom order by\n disables it (you get the top page + truncated)\n\n## Graph traversal (follow) and edges\n\n from docs where <seed> follow doc.out docs the seed links TO\n from docs where <seed> follow doc.in backlinks\n follow doc.out { depth 3 } bound (1..8, default 8)\n follow doc.out { where layer == \"canon\" } keep only matching successors\n follow doc.in { where ^$path in list(before) } ^ = the row being expanded\n (^^ = the walk's enclosing scope)\n follow doc.out, doc.in several destinations: their union\n (one step dedups by identity)\n follow ^docs collect { where doc.out exists { where $path == ^^$path } }\n a destination block, re-read per\n frontier row (^ = that row; ^docs\n is the root's documents) = doc.in\n follow doc.out { frontier type == \"practitioner\" } cut, keeping the frontier row\n follow distinct doc.out / follow doc.out { by group } identity control\n Per-occurrence metadata: $depth (seed = 1), $stop (interior|leaf|frontier|\n depth|cycle), $leaf/$frontier, $ordinal \u2014 usable in select/order by and the\n top-level post-walk where, NOT metadata of the candidates the follow-local\n where/frontier read.\n Type-preserving relations: doc.out/doc.in, block.children, section.children,\n section.subsections.\n from edges where $dst_path == \"/notes/x.md\" inbound edges to a doc, as rows\n from edges where predicate == \"depends_on\" select $src, $dst_path\n from docs where not doc.in_edges exists { } orphans (nothing links in)\n (The `graph` tool is a convenience wrapper that compiles to a follow query.)\n\n## semantic grain\n\nfrom docs ranks whole DOCUMENTS (one hit per file); from blocks ranks PASSAGES.\ntext()/where prune the candidate set; only order by reweights.\n\n## Examples\n\n from docs where layer == \"working\"\n from docs where $path.startsWith(\"/guides/\") and $updated_at >= \"2026-08-01\"\n from docs where \"pricing\" in list(tags) select layer, tags\n from docs where inline.owner == \"alice\"\n from docs where $title.lower().contains(\"q3 plan\")\n from docs where format == \"yaml\"\n from docs where type == \"practitioner\" order by era desc limit 2\n from docs where nodes none { where kind == \"md:task\" and not checked }\n from docs where tags exists { where $it == \"tria-prima\" }\n from docs where $path == \"/x.md\" select fm: entries(frontmatter) collect { k: $key, v: $it }\n from docs select owner_id, owner: ^nodes single { where kind == \"person\" and attrs.id == ^owner_id }\n from blocks where type == \"task\" and not checked and under_heading(\"Launch\") and doc.layer == \"working\"\n from blocks where type == \"paragraph\" and has_edge(\"references\", \"d_92aaaaa\")\n select $path, $depth from docs where $path == \"/timeline/review.md\" follow refs(before), refs(after)\n select $path, next: ^docs collect { $path where ^$path in list(before) } from docs where type == \"milestone\"\n select $path, n: size(^docs), repo: ^$id from docs limit 1\n docs { type == \"substance\" and not verified }\n select subject, process: ^docs[slug == ^subject]!.$path from docs where type == \"lab-note\"\n select $path, back: nodes first { select p: 0^docs collect { $path where ^^$path in list(after) } values } from docs where type == \"milestone\"\n select $path, prior: refs(before) collect { $path, when } from docs where type == \"milestone\"\n from blocks order by semantic(\"identity preservation across edits\") desc limit 10\n from edges where dst_kind == \"external\" select $dst_uri\n docs count { where layer == \"canon\" }\n\nCommon mistake: bare `path.startsWith(...)` meant the intrinsic \u2014 it fails loud\nwith a hint; write `$path.startsWith(...)`. Another: `where tags == \"x\"` on a\nlist-valued key is false \u2014 write `\"x\" in tags`.\n";
1
+ export declare const QUERY_SYNTAX = "# query \u2014 OQX syntax reference\n\nThe `query` tool takes ONE plain OQX string (+ optional `limit`/`cursor`). There\nis no {from, filter, order} envelope: every concern is a clause of the string.\n\n [select <items>] from <target> [where <pred>] [frontier <pred>] [follow <dest> [{ \u2026 }], \u2026]\n [order by <expr> [asc|desc], \u2026] [limit N] [offset N]\n <target> count|exists|none|first|single { <block> } (scalar/one-row form: a bare target at the root scope)\n\nCLAUSE ORDER IS FIXED: select, from, where, frontier, follow, order by, limit, offset \u2014\neach at most once; an out-of-order clause is a parse error naming the order.\nOnly `select` may drop its keyword, and only as the first clause\n(`path, era from docs where \u2026`). A top-level predicate needs `where`, because\n`where` follows `from` (`era > 1600 from docs` is the clause-order error); a\nblock may LEAD with a predicate (see Sugar), but a predicate after a projection\nstill needs the keyword (`{ name where kind == \"md:task\" }`, not\n`{ name, kind == \"md:task\" }`); `from docs count` is an error (write\n`docs count { \u2026 }`). `where` and later `select` items may use the same body's\n`select` aliases (`select path, old: era < 1000 from docs where old`; an alias\nshadows a same-named field there). `and` / `or` / `not` are the connectives;\nthe symbols `&&` / `||` / `!` are accepted synonyms (the words are canonical and\nwhat `print` writes).\n\n## Sugar (OQX 0.17)\n\nEvery form here is shorthand for an explicit directive (the AST is the explicit form):\n nodes { kind == \"md:task\" } \u2261 nodes collect { where kind == \"md:task\" } \u2014 a receiver block with\n no consumer is collect; a block whose LEADING expression is a predicate\n (comparison, call, !/is/not, in, literal, (\u2026), a consumer test \u2014 anything\n but a bare name, a dotted path or a ^lift) is where-first. Bare names still\n project: nodes { name } \u2261 nodes collect { name }; filter a bare field with\n nodes { is checked }. Wherever a value stands (0.20), call arguments included:\n size(children { is checked }); x distinct { \u2026 } \u2261 x collect distinct { \u2026 }. After a\n follow destination too: follow children { depth 2 } is that destination's block.\n (a, b) a tuple (0.20): two or more expressions, concatenated one level flat \u2014 a value\n (size((tags, aliases)), \"x\" in (tags, aliases)) and a receiver ((a, b) { \u2026 },\n follow (before, after) { depth 2 }); (a) alone is grouping\n refs(x)[0] \u2261 refs(x) first { offset 0 } positional (integer literal or ${binding});\n out of range \u21D2 absent; refs(x)[0].path navigates the row\n ^docs[path == ^company] \u2261 ^docs first { where path == ^company } first match or absent\n ^docs[path == ^company]! \u2261 \u2026 single { \u2026 } required: exactly one, else filter_invalid\n title! required: title, or an error naming the expression (and the row's $key \u2014 its id)\n \u2014 never a filter, never a coercion (0!, \"\"! are values); tightest: a!.b vs a.b!\n is x / not x truthiness of x, and its negation (prefix; `not` \u2261 `!`)\n x is y / x is not y identity ($key: a store row's id, else structural) \u2014 compares rows, which == does not;\n x is null = absent; for scalars is \u2261 ==; comparison precedence, no chaining\n a and b / a or b the connectives (\u2261 && / ||: same precedence, short-circuit, value: data.title or path coalesces)\n boss: ^docs[path == ^manager], bossName: boss.title a select item may use the items to its LEFT\n Reserved words (never a bare field name): from where select is not and or, true false null.\n Contextual words (limit order offset follow collect count first distinct desc by in frontier depth) parse as\n fields in expression position (select o: order from docs).\n\nResults are LEAN hits \u2014 { id, path } + whatever `select` projects (or `values`,\n`count`, `exists`, `none`). The envelope keys ARE the attributes: copy a hit's\n`id` straight into `where id == \"\u2026\"` (no translation); `select id, path` is simply\nthe envelope (no duplicate columns); any OTHER projection whose output key would\nbe `id` or `path` must be aliased \u2014 `select data.path` is refused (\"projection\n`path` collides with the hit envelope \u2014 alias it (`p: data.path`)\"); a row\nsurfacing as a value (an empty `collect { }`) is the same `{ id, path }`. Every\nother tool spells a row the same way (`docs_read` \u2192 `{ id, path, rev, properties,\nraw }`, `blocks_read` \u2192 `{ id, type, text, raw, hash, \u2026 }`, `docs_list` \u2192 `{ id,\npath, updated_at, blocks }`, a revision \u2192 `{ id, path, hash }`): hydrate by id via\nblocks_read/docs_read.\n\n## Paths\n\nEvery path the surface returns is `/`-rooted \u2014 the form a reference is\nwritten in (`[x](/projects/oqx.md)`, `before: [/timeline/kickoff.md]`): a\nhit's `path`, an edge's `dst_path`, every tool's paths. Every path it\naccepts tolerates both forms (`/a.md` or `a.md`): tool arguments, `refs(x)`,\n`within(...)`, and a string LITERAL compared with `path`/`dst_path` (bare, or\nthrough a row handle: `doc.path`) by `==`/`!=` or passed to their\n`.startsWith(...)` (it is rooted first, so `path == \"a.md\"` and `path == \"/a.md\"`\nboth match). A property holding a reference compares directly \u2014 and is never\nrooted: `where ^path in list(after)`, `where customer == ^path`,\n`where data.path == \"/x.md\"` (the author's `path:` key, as written) \u2014 no\n`\"/\" + ...` glue.\n\n## Targets & field namespaces\n\nTHE NAME RULE: `$` belongs to OQX \u2014 $it, $key, $depth, $stop, $leaf, $frontier,\n$ordinal are values the LANGUAGE injects into a scope; omgbase adds NO $ names\n(a `$id` in a query is an unknown name, absent). Every engine attribute is a BARE\nsnake_case name that WINS on collision \u2014 the convention the relations (children,\nblocks, out, in, doc, frontmatter, \u2026) always followed: a bare name that is an\nengine attribute reads the engine's value, never yours. Your data is bare too;\nwhere a key of yours collides with an engine name, reach it through a source\nhandle: `data.<k>` (the merged authored view \u2014 frontmatter + inline, exactly the\nread a bare non-colliding key performs), `frontmatter.<k>`, `inline.<k>`. So\n`path` is the engine's /-rooted location and `data.path` your `path:` key;\n`tags` is your tags \u222A the body #hashtags and `data.tags` the authored list alone;\n`title` the computed title (the first H1) and `data.title` your `title:`;\n`entries(data)` lists every authored top-level key. Same value, same name\neverywhere: a hit's `id` is docs_read's `id` and the `id` argument of blocks_read.\n\n Reserved bare names per kind (engine attributes; handles; self; relations):\n docs id path raw hash updated_at title tags format | data frontmatter inline | doc | blocks nodes out in out_links in_links\n blocks id path doc_id raw hash updated_at type text attrs depth ordinal | block doc | children nodes out_links section\n nodes id path doc_id block_id kind name value attrs | section doc block | blocks subsections children\n links id path doc_id block_id via_id dst_id dst_path uri field dst_kind predicate provenance anchor | doc\n A key of yours with any of these names is `data.<k>` (or frontmatter.<k> /\n inline.<k> by source; attrs.<k> on a block or node).\n\ndocs:\n - bare identifier = a document PROPERTY (frontmatter + inline `key:: value`,\n unioned; nested via dots: meta.owner). YAML/JSON docs\n expose the parsed object's keys the same way.\n - source handles = data.<k> (merged authored view) / frontmatter.<k> /\n inline.<k>; the whole bag as a collection via entries(data)\n / entries(frontmatter) / entries(inline) (see entries()).\n - attributes = id, path (/-rooted, see Paths), updated_at (ISO-8601,\n sorts chronologically), raw (the whole file, frontmatter\n included), hash (its sha256, 64 hex), title (the computed\n title: the first H1; data.title is the authored key), tags\n (authored tags \u222A body #hashtags, deduped, authored first;\n data.tags is the authored list), format (the doc's format \u2014\n \"markdown\"/\"yaml\"/\"json\"; present on EVERY doc, so\n has(format) is always true and `format == \"yaml\"` is the\n filter).\n - relations = nodes, blocks, doc.out_links / doc.in_links (a doc's\n links as rows; the documents at their other end are\n out_links collect distinct { from dst } and\n in_links collect distinct { from doc } \u2014 there is no\n out / in relation).\n\nblocks:\n - bare fields = doc_id (the owning doc), type, text (normalized), ordinal\n (0-based among siblings), depth (0 at the top level), and\n every attrs key FLATTENED onto the row (bare `checked`\n reads attrs.checked; `attrs.<k>` still works; the\n structural fields win on collision; absent \u21D2 false).\n - attributes = id, path (the owning doc's path), raw (the block's raw\n source \u2014 a container's raw holds its children), hash\n (its sha256 \u2014 the value update/split expect as\n expect.content_hash), updated_at.\n - reach-through = doc.<key> / doc.path / doc.id / doc.format = the\n CONTAINING doc (doc.id and doc.path cost nothing \u2014 they\n read the row's own doc_id / joined path, no lookup).\n - relations = block.children, block.nodes, block.out_links, section.\n NOTE: inside a `follow`, $depth/$ordinal are ALWAYS the walk's metadata; a\n block's own nesting is `depth` / `ordinal` (no $) everywhere.\n\nnodes:\n - bare fields = doc_id, block_id (null for a block-less node), kind\n (md:task / md:link / md:section / yaml:\u2026), name, value,\n + attrs keys flattened (checked, level).\n - attributes = id, path.\n - reach-through = doc.<key> / doc.id / doc.path, block.type / block.text\n / block.id / block.path.\n - relations = section.blocks, section.children, section.subsections.\n\nlinks: (the authored link graph as rows \u2014 one per open link: the relation a link\n establishes; the md:link NODE is its occurrence in the text and via_id\n points at it \u2014 a frontmatter link has a row and no node)\n - bare fields = doc_id (the SOURCE doc), block_id (the source block; null\n for a frontmatter edge), via_id, dst_id (the destination\n node id: a doc id, an external node id or phantom:<path>),\n dst_path (target doc path, /-rooted; NULL when\n dangling/external), uri (external URL; NULL otherwise),\n predicate (\"references\"/\"embeds\"/freeform), provenance\n (\"link\"/\"frontmatter\"/\"inline_field\"/\u2026), dst_kind\n (\"document\"/\"external\"/\"collection\"), anchor, field (the\n frontmatter key / inline field it was authored in).\n - attributes = id, path (the SOURCE doc's path); doc.<key> reaches the\n source document. There is no from_commit: a query sees\n open edges only (history is blocks_history/changes_since).\n - text()/semantic() are N/A on links.\n\n## Scoping (ADR-015; the root row since surface 2.0): bare names are LOCAL\n\n name the CURRENT row only \u2014 never falls through to an enclosing row\n or the repository; an absent field is simply absent.\n ^name one scope OUT (the enclosing row's fields/intrinsics/lifts);\n ^^name two scopes out. `^path` = the enclosing row's path.\n ^docs ^nodes ^blocks ^links\n THE ROOT ROW (surface 2.0, oqx 0.18): the repository is the root\n scope's row, one scope out from a top-level row, so ^docs there is\n the whole documents scan (uncorrelated until you add a ^ predicate);\n one caret per enclosing block (^^docs from depth two), or the\n absolute 0^docs from ANY depth. ^id / 0^id = the repository id.\n ^$it = the root object: ^$it.docs \u2261 ^docs, entries(^$it) names the\n four collections. At the root scope itself a bare target is the scan\n (docs count { \u2026 }, from docs, entries(docs)).\n ERRORS: a bare `docs` INSIDE a block reads a property of the current\n row, which has none \u2014 refused (\"did you mean ^docs\"); `^docs` AT THE\n TOP LEVEL reaches past the root \u2014 refused (write the bare `docs`);\n so is `$repo` (surface < 2.0; the message names the replacement).\n nodes/blocks on a doc row are that doc's RELATIONS, not the root.\n $it the current item itself (a row, or each ELEMENT when the\n receiver is a list property); ^$it = the enclosing row.\n $value is NOT an intrinsic (since oqx 0.15): it reads a\n property literally named $value, absent when there is none.\n $key the row's identity (0.20): a store row's id (doc/block/node/edge), an entries(x)\n entry's key, a follow occurrence's walk identity; project $key: <expr> to rekey the\n rows a block yields (a destination block's $key: is not a column).\n\n## Scalar semantics (@omgbase/oqx)\n\n comparisons == != < <= > >= strict & typed (5 == \"5\" is false); either\n side may be a field (field-vs-field OK).\n absence null and a missing field are the same \"absent\" value; two absents\n are EQUAL, so `absent != \"x\"` is TRUE and `absent == null` is TRUE.\n Relational (< <= > >=) on an absent operand \u2192 FALSE. Bare `f` in\n boolean position uses JS truthiness (absent/false/0/\"\" \u21D2 false;\n NOTE: \"false\", [] and {} are truthy). has(f) = explicit presence.\n boolean and or not grouping ( \u2026 ) \u2014 && || ! are accepted synonyms\n arithmetic + - * / % (+ concatenates if either side is a string)\n membership x in y array \u2192 typed membership; string \u2192 substring;\n object \u2192 key exists; range \u2192 coverage. Any list\n property works directly: \"pricing\" in tags. list(f)\n coerces scalar-or-list (absent \u2192 []) \u2014 use it when a\n key is sometimes scalar, sometimes a list.\n ranges lo..hi (inclusive) lo...hi (exclusive hi) ..hi lo..\n era in 800..1680; works over ISO dates. range(prop) reads a\n range-shaped STRING property as an interval: \"2026-01-15\" in range(window).\n strings x.contains(\"s\") x.startsWith(\"p\") x.endsWith(\"s\") x.matches(\"^re$\")\n (real regex) x.lower() x.upper() size(x). ALL CASE-SENSITIVE \u2014\n fold with .lower(): title.lower().contains(\"aurora\").\n functions has(f) size(x) list(x) entries(x) range(s)\n literals \"str\" 'str' 123 1.5 true false null\n determinism no now()/clock/random. Lists/structs are not literals; ternary is\n not supported. Errors surface as filter_invalid.\n scalars vs lists a list-authored key never == a scalar (tags == \"x\" is false\n when tags is a list) \u2014 use `\"x\" in tags` / `in list(tags)`.\n\n## Domain predicates (omgbase)\n\n text(\"terms\") FTS5 PRUNING predicate (docs match via their blocks, nodes\n via the node index); composes anywhere, incl. nested scopes.\n semantic(\"phrase\") embedding cosine SCORE (docs/blocks): threshold it\n (semantic(\"x\") > 0.6), project it, or `order by semantic(\"x\")\n desc` for top-K. Needs a provider that has embedded the repository; else provider_unavailable.\n blocks target only:\n under(id_or_heading) block in the containment subtree / section range\n under_heading(\"Launch\") an ancestor section heading contains the text (ci)\n under_kind(type[, name]) an ancestor block has that type (name: its text\n contains / its key equals name)\n within(\"path\" | \"d_..\") containing doc = id, exact path, or path glob (*);\n either path form\n yaml_path(\"a.b\") YAML docs: the block at that key path\n json_pointer(\"#/a/b\") JSON docs: the block at that pointer\n has_edge(\"pred\"[, target]) an open authored edge with that predicate leaves here\n has_anchor() block carries an authored ^ref\n parent_type() == \"x\" parent block's type (must be compared)\n child_count() > 0 number of direct children (must be compared)\n refs(x) the live documents a property's document references name:\n x is a string, a list or absent; each \"/a/b.md\", \"a/b.md\"\n or \"d_\u2026\" element resolves to that doc's row; dangling and\n non-string elements are dropped. Yields docs rows, so it is\n a source, a receiver or a follow destination:\n follow refs(before), refs(after) walks a timeline both ways.\n The reverse direction needs no function: a document's\n path is already the reference form, so\n ^docs { where ^path in list(after) } is\n \"the documents whose `after` names me\".\n A HIT IS A STORE ROW: a top-level row that is not a doc/block/node/edge fails\n (filter_invalid). `follow before` over a list of paths\n reaches the STRINGS \u2014 write `follow refs(before)`.\n\n## Nested queries & consumers\n\n <receiver> collect|exists|none|count|first|single { <block> }\n receiver = a relation (nodes, blocks, doc.out_links, section.blocks, \u2026), a\n reach-through row (dst, doc: one row \u2014 dst { title }), a\n list property (tags), entries(x), or a root collection (^docs; 0^docs at depth \u2265 2).\n block = [select \u2026] [where \u2026] [frontier \u2026] [follow \u2026] [order by \u2026] [limit N] [offset N]\n \u2014 the same fixed order as the top level (`from` optional; the\n receiver supplies the rows). A leading name/alias list is the\n projection (`select` dropped); a predicate always needs `where`.\n exists \u22651 row nodes exists { where kind == \"md:task\" and not checked }\n none 0 rows nodes none { where kind == \"md:task\" and not checked } (\"every task done\")\n count a number nodes count { where kind == \"md:task\" } >= 2\n collect rows (default) \u2014 in select for nested results; in where with a lift\n first / single zero-or-one (single errors on >1) \u2014 lookups in select\n distinct on any consumer: dedup by projected value \u2014 nodes count distinct { select kind }\n limit/offset bound the block's rows AFTER where/order/distinct, BEFORE the\n consumer reduces: nodes exists { offset 1 } = at least two;\n first { \u2026 offset 1 } = the second.\n lift `^name:` in a where-collect binds values outward:\n select path, open from docs where nodes collect { ^open: value where kind == \"md:task\" and not checked }\n joins ^<t> exists { where slug == ^ref } semi-join; not \u2026 exists anti-join;\n ^nodes single { where kind == \"person\" and attrs.id == ^owner_id } lookup;\n x in ^keys membership over a lifted set.\n\n## select \u2014 projection\n\n select a, alias: expr, nested: nodes collect { \u2026 }\n bare key \u2192 the attribute when it is one (id, path, title, \u2026), else the property\n (scalar-authored \u2192 scalar; list \u2192 array); data.<k> forces the property;\n a call/arithmetic needs an alias (n: size(tags)) unless in values mode;\n an output key of id/path other than the attribute itself needs an alias.\n raw whole file bytes (docs) \u2014 docs_read is cheaper for one doc.\n distinct select distinct type (dedup hits by projected value)\n values select <ONE item> values \u2192 bare values instead of records:\n select distinct type values from docs \u2192 values: [\"hub\",\"lab-note\",\u2026]\n select path values from docs \u2192 values: [\"/index.md\", \u2026]\n tags: tags collect { $it values } \u2192 a plain array\n h: nodes first { name values where kind == \"md:section\" order by first_ordinal }\n entries(x) a record as a collection ($key / $it per entry), key order:\n select fm: entries(data) collect { k: $key, v: $it }\n where entries(frontmatter) exists { where $key == \"era\" and $it > 1600 }\n entries(inline), entries(attrs), a nested map, or a list (index keys)\n $semantic_score is not a field \u2014 project semantic(\"\u2026\") under an alias instead.\n\n## order by, limit/offset, pagination\n\n order by era desc, path asc absent values sort LAST (asc); ties \u2192 (path, id)\n limit N / offset N define the result SET (integer literals);\n the tool's limit/cursor then page WITHIN it\n cursor opaque keyset on (path,id); a custom order by\n disables it (you get the top page + truncated)\n\n## Graph traversal (follow) and links\n\n The citation graph is the LINK ROWS walked through a destination block:\n dst is a link's destination document (absent for an external or dangling\n link \u2014 it then contributes no successor), doc its source.\n from docs where <seed> follow out_links collect { from dst } docs the seed links TO\n from docs where <seed> follow in_links collect { from doc } backlinks\n follow out_links collect { from dst depth 3 } bound (1..8, default 8) \u2014 a clause of the block\n follow out_links collect { from dst where layer == \"canon\" } keep only matching successors\n follow in_links collect { from doc where ^path in list(before) } ^ = the row being expanded\n (^^ = the walk's enclosing scope)\n follow out_links collect { from dst where predicate == \"depends_on\" } one predicate only\n follow out_links collect { from dst }, in_links collect { from doc }\n several destinations: their union\n (one step dedups by identity)\n follow ^docs { where out_links exists { where dst_path == ^^path } }\n a destination block over the root's\n documents, re-read per frontier row\n (^ = that row) = the backlinks walk\n follow out_links collect { from dst frontier type == \"practitioner\" } cut, keeping the frontier row\n from docs where type == \"process\" frontier !verified follow out_links collect { from dst }\n the BODY's frontier applies to the seeds:\n an unverified seed is kept, not expanded\n follow distinct out_links collect { from dst } / \u2026 { $key: group from dst } identity control ($key)\n follow refs(before), ^docs { where ^path in list(after) } forward over `before`, backward\n over every doc whose `after` names me\n Per-occurrence metadata: $depth (seed = 1), $stop (interior|leaf|frontier|\n depth|cycle), $leaf/$frontier, $ordinal, $key \u2014 usable in select/order by and\n the top-level post-walk where, NOT metadata of the candidates a destination\n block's where reads.\n Destination blocks (OQX 0.20): a brace after ANY destination opens that\n destination's block \u2014 children { depth 2 }, refs(x) { depth 2 }, out_links\n collect { from dst depth 2 }, ^docs { where ^path in list(after) } \u2014 with the\n 0.17 body rules (where-first, projections) and two clauses of its own after\n `where`: frontier <pred> (occurrences reached through this destination that\n match are frontiers; ^ there is the walk's enclosing scope) and depth <n>\n (this destination expands a frontier row only while $depth < n). The body\n that carries the follow may write frontier <pred> too, after its where and\n before follow: it applies to the SEEDS (read like the body's where) \u2014 a\n matching seed stays in the result with $stop == \"frontier\" and is not\n expanded; each destination's frontier is its own. There is no options block;\n the old `by` is $key. A bare follow ^docs (no brace, no consumer) is an\n error naming both block spellings.\n Type-preserving relations: block.children, section.children, section.subsections;\n the citation graph is out_links collect { from dst } / in_links collect { from doc }.\n from links where dst_path == \"/notes/x.md\" inbound links to a doc, as rows\n select d: dst.title, k: dst_kind from links the destination document's title\n from docs where out_links exists { where dst.layer == \"draft\" } links into drafts\n select b: in_links collect distinct { from doc } from docs backlinks as { id, path }\n select doc_id, dst_path from links where predicate == \"depends_on\"\n from docs where not doc.in_links exists { } orphans (nothing links in)\n (The `graph` tool is a convenience wrapper that compiles to a follow query.)\n\n## semantic grain\n\nfrom docs ranks whole DOCUMENTS (one hit per file); from blocks ranks PASSAGES.\ntext()/where prune the candidate set; only order by reweights.\n\n## Examples\n\n from docs where layer == \"working\"\n from docs where path.startsWith(\"/guides/\") and updated_at >= \"2026-08-01\"\n select layer, tags from docs where \"pricing\" in list(tags)\n from docs where inline.owner == \"alice\"\n from docs where title.lower().contains(\"q3 plan\")\n from docs where format == \"yaml\"\n from docs where type == \"practitioner\" order by era desc limit 2\n from docs where nodes none { where kind == \"md:task\" and not checked }\n from docs where tags exists { where $it == \"tria-prima\" }\n select fm: entries(data) collect { k: $key, v: $it } from docs where path == \"/x.md\"\n select owner_id, owner: ^nodes single { where kind == \"person\" and attrs.id == ^owner_id } from docs\n from blocks where type == \"task\" and not checked and under_heading(\"Launch\") and doc.layer == \"working\"\n from blocks where type == \"paragraph\" and has_edge(\"references\", \"d_92aaaaa\")\n select path, $depth from docs where path == \"/timeline/review.md\" follow refs(before), refs(after)\n select path, next: ^docs collect { path where ^path in list(before) } from docs where type == \"milestone\"\n select path, n: size(^docs), repo: ^id from docs limit 1\n select p: data.path, t: data.title from docs where has(data.path)\n docs { type == \"substance\" and not verified }\n select subject, process: ^docs[slug == ^subject]!.path from docs where type == \"lab-note\"\n select path, back: nodes first { select p: 0^docs collect { path where ^^path in list(after) } values } from docs where type == \"milestone\"\n select path, prior: refs(before) collect { path, when } from docs where type == \"milestone\"\n from blocks order by semantic(\"identity preservation across edits\") desc limit 10\n select uri from links where dst_kind == \"external\"\n docs count { where layer == \"canon\" }\n\nCommon mistake: `$path`/`$id` (the 2.x spelling) \u2014 omgbase has no $ names; write\n`path`/`id`. Another: `where tags == \"x\"` on a list-valued key is false \u2014 write\n`\"x\" in tags`. Another: `select data.path` \u2014 alias it (`p: data.path`).\n";
@@ -10,20 +10,20 @@ export const QUERY_SYNTAX = `# query — OQX syntax reference
10
10
  The \`query\` tool takes ONE plain OQX string (+ optional \`limit\`/\`cursor\`). There
11
11
  is no {from, filter, order} envelope: every concern is a clause of the string.
12
12
 
13
- [select <items>] from <target> [where <pred>] [follow <dest>, … [{ … }]]
13
+ [select <items>] from <target> [where <pred>] [frontier <pred>] [follow <dest> [{ … }], …]
14
14
  [order by <expr> [asc|desc], …] [limit N] [offset N]
15
15
  <target> count|exists|none|first|single { <block> } (scalar/one-row form: a bare target at the root scope)
16
16
 
17
- CLAUSE ORDER IS FIXED: select, from, where, follow, order by, limit, offset —
17
+ CLAUSE ORDER IS FIXED: select, from, where, frontier, follow, order by, limit, offset —
18
18
  each at most once; an out-of-order clause is a parse error naming the order.
19
19
  Only \`select\` may drop its keyword, and only as the first clause
20
- (\`$path, era from docs where …\`). A top-level predicate needs \`where\`, because
20
+ (\`path, era from docs where …\`). A top-level predicate needs \`where\`, because
21
21
  \`where\` follows \`from\` (\`era > 1600 from docs\` is the clause-order error); a
22
22
  block may LEAD with a predicate (see Sugar), but a predicate after a projection
23
23
  still needs the keyword (\`{ name where kind == "md:task" }\`, not
24
24
  \`{ name, kind == "md:task" }\`); \`from docs count\` is an error (write
25
25
  \`docs count { … }\`). \`where\` and later \`select\` items may use the same body's
26
- \`select\` aliases (\`select $path, old: era < 1000 from docs where old\`; an alias
26
+ \`select\` aliases (\`select path, old: era < 1000 from docs where old\`; an alias
27
27
  shadows a same-named field there). \`and\` / \`or\` / \`not\` are the connectives;
28
28
  the symbols \`&&\` / \`||\` / \`!\` are accepted synonyms (the words are canonical and
29
29
  what \`print\` writes).
@@ -36,98 +36,155 @@ Every form here is shorthand for an explicit directive (the AST is the explicit
36
36
  (comparison, call, !/is/not, in, literal, (…), a consumer test — anything
37
37
  but a bare name, a dotted path or a ^lift) is where-first. Bare names still
38
38
  project: nodes { name } ≡ nodes collect { name }; filter a bare field with
39
- nodes { is checked }. Not after follow: follow children { depth 2 } is options.
39
+ nodes { is checked }. Wherever a value stands (0.20), call arguments included:
40
+ size(children { is checked }); x distinct { … } ≡ x collect distinct { … }. After a
41
+ follow destination too: follow children { depth 2 } is that destination's block.
42
+ (a, b) a tuple (0.20): two or more expressions, concatenated one level flat — a value
43
+ (size((tags, aliases)), "x" in (tags, aliases)) and a receiver ((a, b) { … },
44
+ follow (before, after) { depth 2 }); (a) alone is grouping
40
45
  refs(x)[0] ≡ refs(x) first { offset 0 } positional (integer literal or \${binding});
41
- out of range ⇒ absent; refs(x)[0].$path navigates the row
42
- ^docs[$path == ^company] ≡ ^docs first { where $path == ^company } first match or absent
43
- ^docs[$path == ^company]! ≡ … single { … } required: exactly one, else filter_invalid
44
- title! required: title, or an error naming the expression (and the row's $id)
46
+ out of range ⇒ absent; refs(x)[0].path navigates the row
47
+ ^docs[path == ^company] ≡ ^docs first { where path == ^company } first match or absent
48
+ ^docs[path == ^company]! ≡ … single { … } required: exactly one, else filter_invalid
49
+ title! required: title, or an error naming the expression (and the row's $key — its id)
45
50
  — never a filter, never a coercion (0!, ""! are values); tightest: a!.b vs a.b!
46
51
  is x / not x truthiness of x, and its negation (prefix; \`not\` ≡ \`!\`)
47
- x is y / x is not y identity (a row's id, else structural) — compares rows, which == does not;
52
+ x is y / x is not y identity ($key: a store row's id, else structural) — compares rows, which == does not;
48
53
  x is null = absent; for scalars is ≡ ==; comparison precedence, no chaining
49
- a and b / a or b the connectives (≡ && / ||: same precedence, short-circuit, value: title or $path coalesces)
50
- boss: ^docs[$path == ^manager], bossName: boss.$title a select item may use the items to its LEFT
54
+ a and b / a or b the connectives (≡ && / ||: same precedence, short-circuit, value: data.title or path coalesces)
55
+ boss: ^docs[path == ^manager], bossName: boss.title a select item may use the items to its LEFT
51
56
  Reserved words (never a bare field name): from where select is not and or, true false null.
52
-
53
- Results are LEAN hits — {id, path} + whatever \`select\` projects (or \`values\`,
54
- \`count\`, \`exists\`, \`none\`). Hydrate full content by id via nodes_get/docs_read.
57
+ Contextual words (limit order offset follow collect count first distinct desc by in frontier depth) parse as
58
+ fields in expression position (select o: order from docs).
59
+
60
+ Results are LEAN hits — { id, path } + whatever \`select\` projects (or \`values\`,
61
+ \`count\`, \`exists\`, \`none\`). The envelope keys ARE the attributes: copy a hit's
62
+ \`id\` straight into \`where id == "…"\` (no translation); \`select id, path\` is simply
63
+ the envelope (no duplicate columns); any OTHER projection whose output key would
64
+ be \`id\` or \`path\` must be aliased — \`select data.path\` is refused ("projection
65
+ \`path\` collides with the hit envelope — alias it (\`p: data.path\`)"); a row
66
+ surfacing as a value (an empty \`collect { }\`) is the same \`{ id, path }\`. Every
67
+ other tool spells a row the same way (\`docs_read\` → \`{ id, path, rev, properties,
68
+ raw }\`, \`blocks_read\` → \`{ id, type, text, raw, hash, … }\`, \`docs_list\` → \`{ id,
69
+ path, updated_at, blocks }\`, a revision → \`{ id, path, hash }\`): hydrate by id via
70
+ blocks_read/docs_read.
55
71
 
56
72
  ## Paths
57
73
 
58
74
  Every path the surface returns is \`/\`-rooted — the form a reference is
59
75
  written in (\`[x](/projects/oqx.md)\`, \`before: [/timeline/kickoff.md]\`): a
60
- hit's \`path\`, \`$path\`, \`$dst_path\`, every tool's paths. Every path it
76
+ hit's \`path\`, an edge's \`dst_path\`, every tool's paths. Every path it
61
77
  accepts tolerates both forms (\`/a.md\` or \`a.md\`): tool arguments, \`refs(x)\`,
62
- \`within(...)\`, and a string LITERAL compared with \`$path\`/\`$dst_path\` by
63
- \`==\`/\`!=\` or passed to their \`.startsWith(...)\` (it is rooted first, so
64
- \`$path == "a.md"\` and \`$path == "/a.md"\` both match). A property holding a
65
- reference compares directly: \`where ^$path in list(after)\`,
66
- \`where customer == ^$path\` — no \`"/" + ...\` glue.
78
+ \`within(...)\`, and a string LITERAL compared with \`path\`/\`dst_path\` (bare, or
79
+ through a row handle: \`doc.path\`) by \`==\`/\`!=\` or passed to their
80
+ \`.startsWith(...)\` (it is rooted first, so \`path == "a.md"\` and \`path == "/a.md"\`
81
+ both match). A property holding a reference compares directly — and is never
82
+ rooted: \`where ^path in list(after)\`, \`where customer == ^path\`,
83
+ \`where data.path == "/x.md"\` (the author's \`path:\` key, as written) — no
84
+ \`"/" + ...\` glue.
67
85
 
68
86
  ## Targets & field namespaces
69
87
 
70
- The sigil rule: \`$\`-prefixed names are engine intrinsics; BARE identifiers are
71
- your content. On docs a bare first segment that collides with an intrinsic base
72
- name (id/path/updated_at/content_hash/body) is REJECTED with a "did you mean
73
- $X?" hint (bare \`path\` would silently read an absent frontmatter key). Force the
74
- property with \`frontmatter.<k>\`. \`title\`/\`tags\` are NOT reserved: \`$title\`/\`$tags\`
75
- are computed and never shadow an authored \`title:\`/\`tags:\` key.
88
+ THE NAME RULE: \`$\` belongs to OQX — $it, $key, $depth, $stop, $leaf, $frontier,
89
+ $ordinal are values the LANGUAGE injects into a scope; omgbase adds NO $ names
90
+ (a \`$id\` in a query is an unknown name, absent). Every engine attribute is a BARE
91
+ snake_case name that WINS on collision — the convention the relations (children,
92
+ blocks, out, in, doc, frontmatter, …) always followed: a bare name that is an
93
+ engine attribute reads the engine's value, never yours. Your data is bare too;
94
+ where a key of yours collides with an engine name, reach it through a source
95
+ handle: \`data.<k>\` (the merged authored view — frontmatter + inline, exactly the
96
+ read a bare non-colliding key performs), \`frontmatter.<k>\`, \`inline.<k>\`. So
97
+ \`path\` is the engine's /-rooted location and \`data.path\` your \`path:\` key;
98
+ \`tags\` is your tags ∪ the body #hashtags and \`data.tags\` the authored list alone;
99
+ \`title\` the computed title (the first H1) and \`data.title\` your \`title:\`;
100
+ \`entries(data)\` lists every authored top-level key. Same value, same name
101
+ everywhere: a hit's \`id\` is docs_read's \`id\` and the \`id\` argument of blocks_read.
102
+
103
+ Reserved bare names per kind (engine attributes; handles; self; relations):
104
+ docs id path raw hash updated_at title tags format | data frontmatter inline | doc | blocks nodes out in out_links in_links
105
+ blocks id path doc_id raw hash updated_at type text attrs depth ordinal | block doc | children nodes out_links section
106
+ nodes id path doc_id block_id kind name value attrs | section doc block | blocks subsections children
107
+ links id path doc_id block_id via_id dst_id dst_path uri field dst_kind predicate provenance anchor | doc
108
+ A key of yours with any of these names is \`data.<k>\` (or frontmatter.<k> /
109
+ inline.<k> by source; attrs.<k> on a block or node).
76
110
 
77
111
  docs:
78
112
  - bare identifier = a document PROPERTY (frontmatter + inline \`key:: value\`,
79
113
  unioned; nested via dots: meta.owner). YAML/JSON docs
80
114
  expose the parsed object's keys the same way.
81
- - source-scoped = frontmatter.<k> / inline.<k>; the whole bag as a collection
82
- via entries(frontmatter) / entries(inline) (see entries()).
83
- - computed = $title (first H1), $tags (body #hashtags), format
84
- (the doc's format — "markdown"/"yaml"/"json"; a bare
85
- computed field, present on EVERY doc, so has(format) is
86
- always true and \`format == "yaml"\` is the filter).
87
- - intrinsics = $id, $path (/-rooted, see Paths), $updated_at (ISO-8601,
88
- sorts chronologically), $body (whole file), $content_hash.
89
- - relations = nodes, blocks, doc.out / doc.in (citation graph),
90
- doc.out_edges / doc.in_edges (a doc's edges as rows).
115
+ - source handles = data.<k> (merged authored view) / frontmatter.<k> /
116
+ inline.<k>; the whole bag as a collection via entries(data)
117
+ / entries(frontmatter) / entries(inline) (see entries()).
118
+ - attributes = id, path (/-rooted, see Paths), updated_at (ISO-8601,
119
+ sorts chronologically), raw (the whole file, frontmatter
120
+ included), hash (its sha256, 64 hex), title (the computed
121
+ title: the first H1; data.title is the authored key), tags
122
+ (authored tags ∪ body #hashtags, deduped, authored first;
123
+ data.tags is the authored list), format (the doc's format —
124
+ "markdown"/"yaml"/"json"; present on EVERY doc, so
125
+ has(format) is always true and \`format == "yaml"\` is the
126
+ filter).
127
+ - relations = nodes, blocks, doc.out_links / doc.in_links (a doc's
128
+ links as rows; the documents at their other end are
129
+ out_links collect distinct { from dst } and
130
+ in_links collect distinct { from doc } — there is no
131
+ out / in relation).
91
132
 
92
133
  blocks:
93
- - bare fields = type, text, and every attrs key FLATTENED onto the row
94
- (bare \`checked\` reads attrs.checked; \`attrs.<k>\` still
95
- works; type/text win on collision; absent ⇒ false).
96
- - intrinsics = $id, $doc, $path, $ordinal, $depth, $updated_at, $body
97
- (the block text), $content_hash (the block's OWN raw hash —
98
- the value update/split expect).
99
- - reach-through = doc.<key> / doc.$path / doc.format = the CONTAINING doc.
100
- - relations = block.children, block.nodes, block.out_edges, section.
134
+ - bare fields = doc_id (the owning doc), type, text (normalized), ordinal
135
+ (0-based among siblings), depth (0 at the top level), and
136
+ every attrs key FLATTENED onto the row (bare \`checked\`
137
+ reads attrs.checked; \`attrs.<k>\` still works; the
138
+ structural fields win on collision; absent ⇒ false).
139
+ - attributes = id, path (the owning doc's path), raw (the block's raw
140
+ source — a container's raw holds its children), hash
141
+ (its sha256 — the value update/split expect as
142
+ expect.content_hash), updated_at.
143
+ - reach-through = doc.<key> / doc.path / doc.id / doc.format = the
144
+ CONTAINING doc (doc.id and doc.path cost nothing — they
145
+ read the row's own doc_id / joined path, no lookup).
146
+ - relations = block.children, block.nodes, block.out_links, section.
147
+ NOTE: inside a \`follow\`, $depth/$ordinal are ALWAYS the walk's metadata; a
148
+ block's own nesting is \`depth\` / \`ordinal\` (no $) everywhere.
101
149
 
102
150
  nodes:
103
- - bare fields = kind (md:task / md:link / md:section / yaml:…), name,
104
- value, + attrs keys flattened (checked, level).
105
- - intrinsics = $id (=$node_id), $doc_id, $block_id, $path.
106
- - reach-through = doc.<key>, block.type / block.text.
151
+ - bare fields = doc_id, block_id (null for a block-less node), kind
152
+ (md:task / md:link / md:section / yaml:…), name, value,
153
+ + attrs keys flattened (checked, level).
154
+ - attributes = id, path.
155
+ - reach-through = doc.<key> / doc.id / doc.path, block.type / block.text
156
+ / block.id / block.path.
107
157
  - relations = section.blocks, section.children, section.subsections.
108
158
 
109
- edges: (the authored link graph as rows — one per open edge)
110
- - bare fields = predicate ("references"/"embeds"/freeform), provenance
159
+ links: (the authored link graph as rows — one per open link: the relation a link
160
+ establishes; the md:link NODE is its occurrence in the text and via_id
161
+ points at it — a frontmatter link has a row and no node)
162
+ - bare fields = doc_id (the SOURCE doc), block_id (the source block; null
163
+ for a frontmatter edge), via_id, dst_id (the destination
164
+ node id: a doc id, an external node id or phantom:<path>),
165
+ dst_path (target doc path, /-rooted; NULL when
166
+ dangling/external), uri (external URL; NULL otherwise),
167
+ predicate ("references"/"embeds"/freeform), provenance
111
168
  ("link"/"frontmatter"/"inline_field"/…), dst_kind
112
- ("document"/"external"/"collection"), anchor, src_field
113
- - intrinsics = $id, $src, $dst, $dst_path (target doc path, /-rooted;
114
- NULL when dangling/external), $dst_uri (external URL;
115
- NULL otherwise), $src_block, $via, $from_commit; $path
116
- and doc.<key> reach the SOURCE document.
117
- - text()/semantic() are N/A on edges.
169
+ ("document"/"external"/"collection"), anchor, field (the
170
+ frontmatter key / inline field it was authored in).
171
+ - attributes = id, path (the SOURCE doc's path); doc.<key> reaches the
172
+ source document. There is no from_commit: a query sees
173
+ open edges only (history is blocks_history/changes_since).
174
+ - text()/semantic() are N/A on links.
118
175
 
119
176
  ## Scoping (ADR-015; the root row since surface 2.0): bare names are LOCAL
120
177
 
121
178
  name the CURRENT row only — never falls through to an enclosing row
122
179
  or the repository; an absent field is simply absent.
123
180
  ^name one scope OUT (the enclosing row's fields/intrinsics/lifts);
124
- ^^name two scopes out. \`^$path\` = the enclosing row's path.
125
- ^docs ^nodes ^blocks ^edges
181
+ ^^name two scopes out. \`^path\` = the enclosing row's path.
182
+ ^docs ^nodes ^blocks ^links
126
183
  THE ROOT ROW (surface 2.0, oqx 0.18): the repository is the root
127
184
  scope's row, one scope out from a top-level row, so ^docs there is
128
185
  the whole documents scan (uncorrelated until you add a ^ predicate);
129
186
  one caret per enclosing block (^^docs from depth two), or the
130
- absolute 0^docs from ANY depth. ^$id / 0^$id = the repository id.
187
+ absolute 0^docs from ANY depth. ^id / 0^id = the repository id.
131
188
  ^$it = the root object: ^$it.docs ≡ ^docs, entries(^$it) names the
132
189
  four collections. At the root scope itself a bare target is the scan
133
190
  (docs count { … }, from docs, entries(docs)).
@@ -140,7 +197,9 @@ edges: (the authored link graph as rows — one per open edge)
140
197
  receiver is a list property); ^$it = the enclosing row.
141
198
  $value is NOT an intrinsic (since oqx 0.15): it reads a
142
199
  property literally named $value, absent when there is none.
143
- $key inside an entries(x) block: the entry's key.
200
+ $key the row's identity (0.20): a store row's id (doc/block/node/edge), an entries(x)
201
+ entry's key, a follow occurrence's walk identity; project $key: <expr> to rekey the
202
+ rows a block yields (a destination block's $key: is not a column).
144
203
 
145
204
  ## Scalar semantics (@omgbase/oqx)
146
205
 
@@ -163,7 +222,7 @@ edges: (the authored link graph as rows — one per open edge)
163
222
  range-shaped STRING property as an interval: "2026-01-15" in range(window).
164
223
  strings x.contains("s") x.startsWith("p") x.endsWith("s") x.matches("^re$")
165
224
  (real regex) x.lower() x.upper() size(x). ALL CASE-SENSITIVE —
166
- fold with .lower(): $title.lower().contains("aurora").
225
+ fold with .lower(): title.lower().contains("aurora").
167
226
  functions has(f) size(x) list(x) entries(x) range(s)
168
227
  literals "str" 'str' 123 1.5 true false null
169
228
  determinism no now()/clock/random. Lists/structs are not literals; ternary is
@@ -177,7 +236,7 @@ edges: (the authored link graph as rows — one per open edge)
177
236
  via the node index); composes anywhere, incl. nested scopes.
178
237
  semantic("phrase") embedding cosine SCORE (docs/blocks): threshold it
179
238
  (semantic("x") > 0.6), project it, or \`order by semantic("x")
180
- desc\` for top-K. Needs a provider; else semantic_unavailable.
239
+ desc\` for top-K. Needs a provider that has embedded the repository; else provider_unavailable.
181
240
  blocks target only:
182
241
  under(id_or_heading) block in the containment subtree / section range
183
242
  under_heading("Launch") an ancestor section heading contains the text (ci)
@@ -198,8 +257,8 @@ edges: (the authored link graph as rows — one per open edge)
198
257
  a source, a receiver or a follow destination:
199
258
  follow refs(before), refs(after) walks a timeline both ways.
200
259
  The reverse direction needs no function: a document's
201
- $path is already the reference form, so
202
- ^docs collect { where ^$path in list(after) } is
260
+ path is already the reference form, so
261
+ ^docs { where ^path in list(after) } is
203
262
  "the documents whose \`after\` names me".
204
263
  A HIT IS A STORE ROW: a top-level row that is not a doc/block/node/edge fails
205
264
  (filter_invalid). \`follow before\` over a list of paths
@@ -208,9 +267,10 @@ edges: (the authored link graph as rows — one per open edge)
208
267
  ## Nested queries & consumers
209
268
 
210
269
  <receiver> collect|exists|none|count|first|single { <block> }
211
- receiver = a relation (nodes, blocks, doc.out_edges, section.blocks, …), a
270
+ receiver = a relation (nodes, blocks, doc.out_links, section.blocks, …), a
271
+ reach-through row (dst, doc: one row — dst { title }), a
212
272
  list property (tags), entries(x), or a root collection (^docs; 0^docs at depth ≥ 2).
213
- block = [select …] [where …] [follow …] [order by …] [limit N] [offset N]
273
+ block = [select …] [where …] [frontier …] [follow …] [order by …] [limit N] [offset N]
214
274
  — the same fixed order as the top level (\`from\` optional; the
215
275
  receiver supplies the rows). A leading name/alias list is the
216
276
  projection (\`select\` dropped); a predicate always needs \`where\`.
@@ -224,7 +284,7 @@ edges: (the authored link graph as rows — one per open edge)
224
284
  consumer reduces: nodes exists { offset 1 } = at least two;
225
285
  first { … offset 1 } = the second.
226
286
  lift \`^name:\` in a where-collect binds values outward:
227
- select $path, open from docs where nodes collect { ^open: value where kind == "md:task" and not checked }
287
+ select path, open from docs where nodes collect { ^open: value where kind == "md:task" and not checked }
228
288
  joins ^<t> exists { where slug == ^ref } semi-join; not … exists anti-join;
229
289
  ^nodes single { where kind == "person" and attrs.id == ^owner_id } lookup;
230
290
  x in ^keys membership over a lifted set.
@@ -232,54 +292,82 @@ edges: (the authored link graph as rows — one per open edge)
232
292
  ## select — projection
233
293
 
234
294
  select a, alias: expr, nested: nodes collect { … }
235
- bare key → the property (scalar-authored → scalar; list → array); $-intrinsics;
236
- a call/arithmetic needs an alias (n: size(tags)) unless in values mode.
237
- "$body" whole file bytes (docs) — docs_read is cheaper for one doc.
295
+ bare key → the attribute when it is one (id, path, title, …), else the property
296
+ (scalar-authored → scalar; list → array); data.<k> forces the property;
297
+ a call/arithmetic needs an alias (n: size(tags)) unless in values mode;
298
+ an output key of id/path other than the attribute itself needs an alias.
299
+ raw whole file bytes (docs) — docs_read is cheaper for one doc.
238
300
  distinct select distinct type (dedup hits by projected value)
239
301
  values select <ONE item> values → bare values instead of records:
240
- from docs select distinct type values → values: ["hub","lab-note",…]
241
- select $path values from docs → values: ["/index.md", …]
302
+ select distinct type values from docs → values: ["hub","lab-note",…]
303
+ select path values from docs → values: ["/index.md", …]
242
304
  tags: tags collect { $it values } → a plain array
243
305
  h: nodes first { name values where kind == "md:section" order by first_ordinal }
244
306
  entries(x) a record as a collection ($key / $it per entry), key order:
245
- select fm: entries(frontmatter) collect { k: $key, v: $it }
307
+ select fm: entries(data) collect { k: $key, v: $it }
246
308
  where entries(frontmatter) exists { where $key == "era" and $it > 1600 }
247
309
  entries(inline), entries(attrs), a nested map, or a list (index keys)
248
310
  $semantic_score is not a field — project semantic("…") under an alias instead.
249
311
 
250
312
  ## order by, limit/offset, pagination
251
313
 
252
- order by era desc, $path asc absent values sort LAST (asc); ties → (path, id)
314
+ order by era desc, path asc absent values sort LAST (asc); ties → (path, id)
253
315
  limit N / offset N define the result SET (integer literals);
254
316
  the tool's limit/cursor then page WITHIN it
255
317
  cursor opaque keyset on (path,id); a custom order by
256
318
  disables it (you get the top page + truncated)
257
319
 
258
- ## Graph traversal (follow) and edges
320
+ ## Graph traversal (follow) and links
259
321
 
260
- from docs where <seed> follow doc.out docs the seed links TO
261
- from docs where <seed> follow doc.in backlinks
262
- follow doc.out { depth 3 } bound (1..8, default 8)
263
- follow doc.out { where layer == "canon" } keep only matching successors
264
- follow doc.in { where ^$path in list(before) } ^ = the row being expanded
322
+ The citation graph is the LINK ROWS walked through a destination block:
323
+ dst is a link's destination document (absent for an external or dangling
324
+ link — it then contributes no successor), doc its source.
325
+ from docs where <seed> follow out_links collect { from dst } docs the seed links TO
326
+ from docs where <seed> follow in_links collect { from doc } backlinks
327
+ follow out_links collect { from dst depth 3 } bound (1..8, default 8) — a clause of the block
328
+ follow out_links collect { from dst where layer == "canon" } keep only matching successors
329
+ follow in_links collect { from doc where ^path in list(before) } ^ = the row being expanded
265
330
  (^^ = the walk's enclosing scope)
266
- follow doc.out, doc.in several destinations: their union
331
+ follow out_links collect { from dst where predicate == "depends_on" } one predicate only
332
+ follow out_links collect { from dst }, in_links collect { from doc }
333
+ several destinations: their union
267
334
  (one step dedups by identity)
268
- follow ^docs collect { where doc.out exists { where $path == ^^$path } }
269
- a destination block, re-read per
270
- frontier row (^ = that row; ^docs
271
- is the root's documents) = doc.in
272
- follow doc.out { frontier type == "practitioner" } cut, keeping the frontier row
273
- follow distinct doc.out / follow doc.out { by group } identity control
335
+ follow ^docs { where out_links exists { where dst_path == ^^path } }
336
+ a destination block over the root's
337
+ documents, re-read per frontier row
338
+ (^ = that row) = the backlinks walk
339
+ follow out_links collect { from dst frontier type == "practitioner" } cut, keeping the frontier row
340
+ from docs where type == "process" frontier !verified follow out_links collect { from dst }
341
+ the BODY's frontier applies to the seeds:
342
+ an unverified seed is kept, not expanded
343
+ follow distinct out_links collect { from dst } / … { $key: group from dst } identity control ($key)
344
+ follow refs(before), ^docs { where ^path in list(after) } forward over \`before\`, backward
345
+ over every doc whose \`after\` names me
274
346
  Per-occurrence metadata: $depth (seed = 1), $stop (interior|leaf|frontier|
275
- depth|cycle), $leaf/$frontier, $ordinal — usable in select/order by and the
276
- top-level post-walk where, NOT metadata of the candidates the follow-local
277
- where/frontier read.
278
- Type-preserving relations: doc.out/doc.in, block.children, section.children,
279
- section.subsections.
280
- from edges where $dst_path == "/notes/x.md" inbound edges to a doc, as rows
281
- from edges where predicate == "depends_on" select $src, $dst_path
282
- from docs where not doc.in_edges exists { } orphans (nothing links in)
347
+ depth|cycle), $leaf/$frontier, $ordinal, $key — usable in select/order by and
348
+ the top-level post-walk where, NOT metadata of the candidates a destination
349
+ block's where reads.
350
+ Destination blocks (OQX 0.20): a brace after ANY destination opens that
351
+ destination's block — children { depth 2 }, refs(x) { depth 2 }, out_links
352
+ collect { from dst depth 2 }, ^docs { where ^path in list(after) } — with the
353
+ 0.17 body rules (where-first, projections) and two clauses of its own after
354
+ \`where\`: frontier <pred> (occurrences reached through this destination that
355
+ match are frontiers; ^ there is the walk's enclosing scope) and depth <n>
356
+ (this destination expands a frontier row only while $depth < n). The body
357
+ that carries the follow may write frontier <pred> too, after its where and
358
+ before follow: it applies to the SEEDS (read like the body's where) — a
359
+ matching seed stays in the result with $stop == "frontier" and is not
360
+ expanded; each destination's frontier is its own. There is no options block;
361
+ the old \`by\` is $key. A bare follow ^docs (no brace, no consumer) is an
362
+ error naming both block spellings.
363
+ Type-preserving relations: block.children, section.children, section.subsections;
364
+ the citation graph is out_links collect { from dst } / in_links collect { from doc }.
365
+ from links where dst_path == "/notes/x.md" inbound links to a doc, as rows
366
+ select d: dst.title, k: dst_kind from links the destination document's title
367
+ from docs where out_links exists { where dst.layer == "draft" } links into drafts
368
+ select b: in_links collect distinct { from doc } from docs backlinks as { id, path }
369
+ select doc_id, dst_path from links where predicate == "depends_on"
370
+ from docs where not doc.in_links exists { } orphans (nothing links in)
283
371
  (The \`graph\` tool is a convenience wrapper that compiles to a follow query.)
284
372
 
285
373
  ## semantic grain
@@ -290,31 +378,32 @@ text()/where prune the candidate set; only order by reweights.
290
378
  ## Examples
291
379
 
292
380
  from docs where layer == "working"
293
- from docs where $path.startsWith("/guides/") and $updated_at >= "2026-08-01"
294
- from docs where "pricing" in list(tags) select layer, tags
381
+ from docs where path.startsWith("/guides/") and updated_at >= "2026-08-01"
382
+ select layer, tags from docs where "pricing" in list(tags)
295
383
  from docs where inline.owner == "alice"
296
- from docs where $title.lower().contains("q3 plan")
384
+ from docs where title.lower().contains("q3 plan")
297
385
  from docs where format == "yaml"
298
386
  from docs where type == "practitioner" order by era desc limit 2
299
387
  from docs where nodes none { where kind == "md:task" and not checked }
300
388
  from docs where tags exists { where $it == "tria-prima" }
301
- from docs where $path == "/x.md" select fm: entries(frontmatter) collect { k: $key, v: $it }
302
- from docs select owner_id, owner: ^nodes single { where kind == "person" and attrs.id == ^owner_id }
389
+ select fm: entries(data) collect { k: $key, v: $it } from docs where path == "/x.md"
390
+ select owner_id, owner: ^nodes single { where kind == "person" and attrs.id == ^owner_id } from docs
303
391
  from blocks where type == "task" and not checked and under_heading("Launch") and doc.layer == "working"
304
392
  from blocks where type == "paragraph" and has_edge("references", "d_92aaaaa")
305
- select $path, $depth from docs where $path == "/timeline/review.md" follow refs(before), refs(after)
306
- select $path, next: ^docs collect { $path where ^$path in list(before) } from docs where type == "milestone"
307
- select $path, n: size(^docs), repo: ^$id from docs limit 1
393
+ select path, $depth from docs where path == "/timeline/review.md" follow refs(before), refs(after)
394
+ select path, next: ^docs collect { path where ^path in list(before) } from docs where type == "milestone"
395
+ select path, n: size(^docs), repo: ^id from docs limit 1
396
+ select p: data.path, t: data.title from docs where has(data.path)
308
397
  docs { type == "substance" and not verified }
309
- select subject, process: ^docs[slug == ^subject]!.$path from docs where type == "lab-note"
310
- select $path, back: nodes first { select p: 0^docs collect { $path where ^^$path in list(after) } values } from docs where type == "milestone"
311
- select $path, prior: refs(before) collect { $path, when } from docs where type == "milestone"
398
+ select subject, process: ^docs[slug == ^subject]!.path from docs where type == "lab-note"
399
+ select path, back: nodes first { select p: 0^docs collect { path where ^^path in list(after) } values } from docs where type == "milestone"
400
+ select path, prior: refs(before) collect { path, when } from docs where type == "milestone"
312
401
  from blocks order by semantic("identity preservation across edits") desc limit 10
313
- from edges where dst_kind == "external" select $dst_uri
402
+ select uri from links where dst_kind == "external"
314
403
  docs count { where layer == "canon" }
315
404
 
316
- Common mistake: bare \`path.startsWith(...)\` meant the intrinsic — it fails loud
317
- with a hint; write \`$path.startsWith(...)\`. Another: \`where tags == "x"\` on a
318
- list-valued key is false — write \`"x" in tags\`.
405
+ Common mistake: \`$path\`/\`$id\` (the 2.x spelling) — omgbase has no $ names; write
406
+ \`path\`/\`id\`. Another: \`where tags == "x"\` on a list-valued key is false — write
407
+ \`"x" in tags\`. Another: \`select data.path\` — alias it (\`p: data.path\`).
319
408
  `;
320
409
  //# sourceMappingURL=reference.js.map