@omgbase/core 0.8.0 → 0.10.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.
- package/dist/src/core/cursor.d.ts +13 -1
- package/dist/src/core/cursor.js +21 -2
- package/dist/src/core/paths.d.ts +10 -0
- package/dist/src/core/paths.js +34 -0
- package/dist/src/core/read/document.d.ts +1 -0
- package/dist/src/core/read/document.js +3 -2
- package/dist/src/core/read/reader.d.ts +6 -3
- package/dist/src/core/read/reader.js +31 -16
- package/dist/src/graph/history.js +16 -10
- package/dist/src/graph/link-health.d.ts +2 -1
- package/dist/src/graph/link-health.js +13 -7
- package/dist/src/index.d.ts +3 -1
- package/dist/src/index.js +3 -1
- package/dist/src/mcp/errors.js +1 -1
- package/dist/src/mcp/graph.js +50 -15
- package/dist/src/mcp/reference.d.ts +1 -1
- package/dist/src/mcp/reference.js +111 -39
- package/dist/src/mcp/server.js +33 -29
- package/dist/src/oqx-js/context.d.ts +2 -1
- package/dist/src/oqx-js/context.js +93 -29
- package/dist/src/oqx-js/planner.d.ts +8 -1
- package/dist/src/oqx-js/planner.js +21 -8
- package/dist/src/oqx-js/root-row.d.ts +21 -0
- package/dist/src/oqx-js/root-row.js +55 -0
- package/dist/src/oqx-js/run.d.ts +3 -1
- package/dist/src/oqx-js/run.js +127 -163
- package/dist/src/oqx-js/sql/translate.d.ts +7 -0
- package/dist/src/oqx-js/sql/translate.js +42 -11
- package/dist/src/oqx-js/store-index.js +12 -3
- package/dist/src/spec-versions.d.ts +3 -3
- package/dist/src/spec-versions.js +2 -2
- package/dist/src/surface-paths.d.ts +33 -0
- package/dist/src/surface-paths.js +75 -0
- 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 $repo.<target> count|exists|none|first|single { <block> } (scalar/one-row form)\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`). Every other clause always carries its keyword:\na predicate is NEVER implicit (`nodes exists { where kind == \"md:task\" }`, not\n`{ kind == \"md:task\" }`), and `from docs count` is an error (write\n`$repo.docs count { \u2026 }`). `where` may use the same body's `select` aliases\n(`select $path, old: era < 1000 from docs where old`; an alias shadows a\nsame-named field there).\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## 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/repo/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, $updated_at (ISO-8601, sorts chronologically),\n $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; NULL when\n dangling/external), $dst_uri (external URL; NULL\n otherwise), $src_block, $via, $from_commit; $path and\n doc.<key> reach the SOURCE document.\n - text()/semantic() are N/A on edges.\n\n## Scoping (ADR-015): 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 $repo.<t> the root scan \u2014 $repo.docs / $repo.nodes / $repo.blocks /\n $repo.edges \u2014 available from ANY depth (uncorrelated until you\n add a ^ predicate). $repo.$id = the repository id.\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 && || ! grouping ( \u2026 )\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 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\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 ($repo.docs).\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\" && !checked }\n none 0 rows nodes none { where kind == \"md:task\" && !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\" && !checked }\n joins $repo.<t> exists { where slug == ^ref } semi-join; !\u2026 exists anti-join;\n $repo.nodes single { where kind == \"person\" && 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 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\" && $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 before.contains(^$path) } ^ = 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 $repo.docs collect { where doc.out exists { where $path == ^^$path } }\n a destination block, re-read per\n frontier row (^ = that row) = 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 !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/\") && $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\" && !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: $repo.nodes single { where kind == \"person\" && attrs.id == ^owner_id }\n from blocks where type == \"task\" && !checked && under_heading(\"Launch\") && doc.layer == \"working\"\n from blocks where type == \"paragraph\" && has_edge(\"references\", \"d_92aaaaa\")\n from blocks order by semantic(\"identity preservation across edits\") desc limit 10\n from edges where dst_kind == \"external\" select $dst_uri\n $repo.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>] [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";
|
|
@@ -12,26 +12,64 @@ is no {from, filter, order} envelope: every concern is a clause of the string.
|
|
|
12
12
|
|
|
13
13
|
[select <items>] from <target> [where <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
17
|
CLAUSE ORDER IS FIXED: select, from, where, 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 …\`).
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
20
|
+
(\`$path, era from docs where …\`). A top-level predicate needs \`where\`, because
|
|
21
|
+
\`where\` follows \`from\` (\`era > 1600 from docs\` is the clause-order error); a
|
|
22
|
+
block may LEAD with a predicate (see Sugar), but a predicate after a projection
|
|
23
|
+
still needs the keyword (\`{ name where kind == "md:task" }\`, not
|
|
24
|
+
\`{ name, kind == "md:task" }\`); \`from docs count\` is an error (write
|
|
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
|
|
27
|
+
shadows a same-named field there). \`and\` / \`or\` / \`not\` are the connectives;
|
|
28
|
+
the symbols \`&&\` / \`||\` / \`!\` are accepted synonyms (the words are canonical and
|
|
29
|
+
what \`print\` writes).
|
|
30
|
+
|
|
31
|
+
## Sugar (OQX 0.17)
|
|
32
|
+
|
|
33
|
+
Every form here is shorthand for an explicit directive (the AST is the explicit form):
|
|
34
|
+
nodes { kind == "md:task" } ≡ nodes collect { where kind == "md:task" } — a receiver block with
|
|
35
|
+
no consumer is collect; a block whose LEADING expression is a predicate
|
|
36
|
+
(comparison, call, !/is/not, in, literal, (…), a consumer test — anything
|
|
37
|
+
but a bare name, a dotted path or a ^lift) is where-first. Bare names still
|
|
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.
|
|
40
|
+
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)
|
|
45
|
+
— never a filter, never a coercion (0!, ""! are values); tightest: a!.b vs a.b!
|
|
46
|
+
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;
|
|
48
|
+
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
|
|
51
|
+
Reserved words (never a bare field name): from where select is not and or, true false null.
|
|
26
52
|
|
|
27
53
|
Results are LEAN hits — {id, path} + whatever \`select\` projects (or \`values\`,
|
|
28
54
|
\`count\`, \`exists\`, \`none\`). Hydrate full content by id via nodes_get/docs_read.
|
|
29
55
|
|
|
56
|
+
## Paths
|
|
57
|
+
|
|
58
|
+
Every path the surface returns is \`/\`-rooted — the form a reference is
|
|
59
|
+
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
|
|
61
|
+
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.
|
|
67
|
+
|
|
30
68
|
## Targets & field namespaces
|
|
31
69
|
|
|
32
70
|
The sigil rule: \`$\`-prefixed names are engine intrinsics; BARE identifiers are
|
|
33
71
|
your content. On docs a bare first segment that collides with an intrinsic base
|
|
34
|
-
name (id/path/
|
|
72
|
+
name (id/path/updated_at/content_hash/body) is REJECTED with a "did you mean
|
|
35
73
|
$X?" hint (bare \`path\` would silently read an absent frontmatter key). Force the
|
|
36
74
|
property with \`frontmatter.<k>\`. \`title\`/\`tags\` are NOT reserved: \`$title\`/\`$tags\`
|
|
37
75
|
are computed and never shadow an authored \`title:\`/\`tags:\` key.
|
|
@@ -46,8 +84,8 @@ docs:
|
|
|
46
84
|
(the doc's format — "markdown"/"yaml"/"json"; a bare
|
|
47
85
|
computed field, present on EVERY doc, so has(format) is
|
|
48
86
|
always true and \`format == "yaml"\` is the filter).
|
|
49
|
-
- intrinsics = $id, $path, $updated_at (ISO-8601,
|
|
50
|
-
$body (whole file), $content_hash.
|
|
87
|
+
- intrinsics = $id, $path (/-rooted, see Paths), $updated_at (ISO-8601,
|
|
88
|
+
sorts chronologically), $body (whole file), $content_hash.
|
|
51
89
|
- relations = nodes, blocks, doc.out / doc.in (citation graph),
|
|
52
90
|
doc.out_edges / doc.in_edges (a doc's edges as rows).
|
|
53
91
|
|
|
@@ -72,21 +110,32 @@ edges: (the authored link graph as rows — one per open edge)
|
|
|
72
110
|
- bare fields = predicate ("references"/"embeds"/freeform), provenance
|
|
73
111
|
("link"/"frontmatter"/"inline_field"/…), dst_kind
|
|
74
112
|
("document"/"external"/"collection"), anchor, src_field
|
|
75
|
-
- intrinsics = $id, $src, $dst, $dst_path (target doc path;
|
|
76
|
-
dangling/external), $dst_uri (external URL;
|
|
77
|
-
otherwise), $src_block, $via, $from_commit; $path
|
|
78
|
-
doc.<key> reach the SOURCE document.
|
|
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.
|
|
79
117
|
- text()/semantic() are N/A on edges.
|
|
80
118
|
|
|
81
|
-
## Scoping (ADR-015): bare names are LOCAL
|
|
119
|
+
## Scoping (ADR-015; the root row since surface 2.0): bare names are LOCAL
|
|
82
120
|
|
|
83
121
|
name the CURRENT row only — never falls through to an enclosing row
|
|
84
122
|
or the repository; an absent field is simply absent.
|
|
85
123
|
^name one scope OUT (the enclosing row's fields/intrinsics/lifts);
|
|
86
124
|
^^name two scopes out. \`^$path\` = the enclosing row's path.
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
125
|
+
^docs ^nodes ^blocks ^edges
|
|
126
|
+
THE ROOT ROW (surface 2.0, oqx 0.18): the repository is the root
|
|
127
|
+
scope's row, one scope out from a top-level row, so ^docs there is
|
|
128
|
+
the whole documents scan (uncorrelated until you add a ^ predicate);
|
|
129
|
+
one caret per enclosing block (^^docs from depth two), or the
|
|
130
|
+
absolute 0^docs from ANY depth. ^$id / 0^$id = the repository id.
|
|
131
|
+
^$it = the root object: ^$it.docs ≡ ^docs, entries(^$it) names the
|
|
132
|
+
four collections. At the root scope itself a bare target is the scan
|
|
133
|
+
(docs count { … }, from docs, entries(docs)).
|
|
134
|
+
ERRORS: a bare \`docs\` INSIDE a block reads a property of the current
|
|
135
|
+
row, which has none — refused ("did you mean ^docs"); \`^docs\` AT THE
|
|
136
|
+
TOP LEVEL reaches past the root — refused (write the bare \`docs\`);
|
|
137
|
+
so is \`$repo\` (surface < 2.0; the message names the replacement).
|
|
138
|
+
nodes/blocks on a doc row are that doc's RELATIONS, not the root.
|
|
90
139
|
$it the current item itself (a row, or each ELEMENT when the
|
|
91
140
|
receiver is a list property); ^$it = the enclosing row.
|
|
92
141
|
$value is NOT an intrinsic (since oqx 0.15): it reads a
|
|
@@ -102,7 +151,7 @@ edges: (the authored link graph as rows — one per open edge)
|
|
|
102
151
|
Relational (< <= > >=) on an absent operand → FALSE. Bare \`f\` in
|
|
103
152
|
boolean position uses JS truthiness (absent/false/0/"" ⇒ false;
|
|
104
153
|
NOTE: "false", [] and {} are truthy). has(f) = explicit presence.
|
|
105
|
-
boolean
|
|
154
|
+
boolean and or not grouping ( … ) — && || ! are accepted synonyms
|
|
106
155
|
arithmetic + - * / % (+ concatenates if either side is a string)
|
|
107
156
|
membership x in y array → typed membership; string → substring;
|
|
108
157
|
object → key exists; range → coverage. Any list
|
|
@@ -134,25 +183,39 @@ edges: (the authored link graph as rows — one per open edge)
|
|
|
134
183
|
under_heading("Launch") an ancestor section heading contains the text (ci)
|
|
135
184
|
under_kind(type[, name]) an ancestor block has that type (name: its text
|
|
136
185
|
contains / its key equals name)
|
|
137
|
-
within("path" | "d_..") containing doc = id, exact path, or path glob (*)
|
|
186
|
+
within("path" | "d_..") containing doc = id, exact path, or path glob (*);
|
|
187
|
+
either path form
|
|
138
188
|
yaml_path("a.b") YAML docs: the block at that key path
|
|
139
189
|
json_pointer("#/a/b") JSON docs: the block at that pointer
|
|
140
190
|
has_edge("pred"[, target]) an open authored edge with that predicate leaves here
|
|
141
191
|
has_anchor() block carries an authored ^ref
|
|
142
192
|
parent_type() == "x" parent block's type (must be compared)
|
|
143
193
|
child_count() > 0 number of direct children (must be compared)
|
|
194
|
+
refs(x) the live documents a property's document references name:
|
|
195
|
+
x is a string, a list or absent; each "/a/b.md", "a/b.md"
|
|
196
|
+
or "d_…" element resolves to that doc's row; dangling and
|
|
197
|
+
non-string elements are dropped. Yields docs rows, so it is
|
|
198
|
+
a source, a receiver or a follow destination:
|
|
199
|
+
follow refs(before), refs(after) walks a timeline both ways.
|
|
200
|
+
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
|
|
203
|
+
"the documents whose \`after\` names me".
|
|
204
|
+
A HIT IS A STORE ROW: a top-level row that is not a doc/block/node/edge fails
|
|
205
|
+
(filter_invalid). \`follow before\` over a list of paths
|
|
206
|
+
reaches the STRINGS — write \`follow refs(before)\`.
|
|
144
207
|
|
|
145
208
|
## Nested queries & consumers
|
|
146
209
|
|
|
147
210
|
<receiver> collect|exists|none|count|first|single { <block> }
|
|
148
211
|
receiver = a relation (nodes, blocks, doc.out_edges, section.blocks, …), a
|
|
149
|
-
list property (tags), entries(x), or a root (
|
|
212
|
+
list property (tags), entries(x), or a root collection (^docs; 0^docs at depth ≥ 2).
|
|
150
213
|
block = [select …] [where …] [follow …] [order by …] [limit N] [offset N]
|
|
151
214
|
— the same fixed order as the top level (\`from\` optional; the
|
|
152
215
|
receiver supplies the rows). A leading name/alias list is the
|
|
153
216
|
projection (\`select\` dropped); a predicate always needs \`where\`.
|
|
154
|
-
exists ≥1 row nodes exists { where kind == "md:task"
|
|
155
|
-
none 0 rows nodes none { where kind == "md:task"
|
|
217
|
+
exists ≥1 row nodes exists { where kind == "md:task" and not checked }
|
|
218
|
+
none 0 rows nodes none { where kind == "md:task" and not checked } ("every task done")
|
|
156
219
|
count a number nodes count { where kind == "md:task" } >= 2
|
|
157
220
|
collect rows (default) — in select for nested results; in where with a lift
|
|
158
221
|
first / single zero-or-one (single errors on >1) — lookups in select
|
|
@@ -161,9 +224,9 @@ edges: (the authored link graph as rows — one per open edge)
|
|
|
161
224
|
consumer reduces: nodes exists { offset 1 } = at least two;
|
|
162
225
|
first { … offset 1 } = the second.
|
|
163
226
|
lift \`^name:\` in a where-collect binds values outward:
|
|
164
|
-
select $path, open from docs where nodes collect { ^open: value where kind == "md:task"
|
|
165
|
-
joins
|
|
166
|
-
|
|
227
|
+
select $path, open from docs where nodes collect { ^open: value where kind == "md:task" and not checked }
|
|
228
|
+
joins ^<t> exists { where slug == ^ref } semi-join; not … exists anti-join;
|
|
229
|
+
^nodes single { where kind == "person" and attrs.id == ^owner_id } lookup;
|
|
167
230
|
x in ^keys membership over a lifted set.
|
|
168
231
|
|
|
169
232
|
## select — projection
|
|
@@ -175,11 +238,12 @@ edges: (the authored link graph as rows — one per open edge)
|
|
|
175
238
|
distinct select distinct type (dedup hits by projected value)
|
|
176
239
|
values select <ONE item> values → bare values instead of records:
|
|
177
240
|
from docs select distinct type values → values: ["hub","lab-note",…]
|
|
241
|
+
select $path values from docs → values: ["/index.md", …]
|
|
178
242
|
tags: tags collect { $it values } → a plain array
|
|
179
243
|
h: nodes first { name values where kind == "md:section" order by first_ordinal }
|
|
180
244
|
entries(x) a record as a collection ($key / $it per entry), key order:
|
|
181
245
|
select fm: entries(frontmatter) collect { k: $key, v: $it }
|
|
182
|
-
where entries(frontmatter) exists { where $key == "era"
|
|
246
|
+
where entries(frontmatter) exists { where $key == "era" and $it > 1600 }
|
|
183
247
|
entries(inline), entries(attrs), a nested map, or a list (index keys)
|
|
184
248
|
$semantic_score is not a field — project semantic("…") under an alias instead.
|
|
185
249
|
|
|
@@ -197,13 +261,14 @@ edges: (the authored link graph as rows — one per open edge)
|
|
|
197
261
|
from docs where <seed> follow doc.in backlinks
|
|
198
262
|
follow doc.out { depth 3 } bound (1..8, default 8)
|
|
199
263
|
follow doc.out { where layer == "canon" } keep only matching successors
|
|
200
|
-
follow doc.in { where
|
|
264
|
+
follow doc.in { where ^$path in list(before) } ^ = the row being expanded
|
|
201
265
|
(^^ = the walk's enclosing scope)
|
|
202
266
|
follow doc.out, doc.in several destinations: their union
|
|
203
267
|
(one step dedups by identity)
|
|
204
|
-
follow
|
|
268
|
+
follow ^docs collect { where doc.out exists { where $path == ^^$path } }
|
|
205
269
|
a destination block, re-read per
|
|
206
|
-
frontier row (^ = that row
|
|
270
|
+
frontier row (^ = that row; ^docs
|
|
271
|
+
is the root's documents) = doc.in
|
|
207
272
|
follow doc.out { frontier type == "practitioner" } cut, keeping the frontier row
|
|
208
273
|
follow distinct doc.out / follow doc.out { by group } identity control
|
|
209
274
|
Per-occurrence metadata: $depth (seed = 1), $stop (interior|leaf|frontier|
|
|
@@ -212,9 +277,9 @@ edges: (the authored link graph as rows — one per open edge)
|
|
|
212
277
|
where/frontier read.
|
|
213
278
|
Type-preserving relations: doc.out/doc.in, block.children, section.children,
|
|
214
279
|
section.subsections.
|
|
215
|
-
from edges where $dst_path == "notes/x.md"
|
|
280
|
+
from edges where $dst_path == "/notes/x.md" inbound edges to a doc, as rows
|
|
216
281
|
from edges where predicate == "depends_on" select $src, $dst_path
|
|
217
|
-
from docs where
|
|
282
|
+
from docs where not doc.in_edges exists { } orphans (nothing links in)
|
|
218
283
|
(The \`graph\` tool is a convenience wrapper that compiles to a follow query.)
|
|
219
284
|
|
|
220
285
|
## semantic grain
|
|
@@ -225,21 +290,28 @@ text()/where prune the candidate set; only order by reweights.
|
|
|
225
290
|
## Examples
|
|
226
291
|
|
|
227
292
|
from docs where layer == "working"
|
|
228
|
-
from docs where $path.startsWith("guides/")
|
|
293
|
+
from docs where $path.startsWith("/guides/") and $updated_at >= "2026-08-01"
|
|
229
294
|
from docs where "pricing" in list(tags) select layer, tags
|
|
230
295
|
from docs where inline.owner == "alice"
|
|
231
296
|
from docs where $title.lower().contains("q3 plan")
|
|
232
297
|
from docs where format == "yaml"
|
|
233
298
|
from docs where type == "practitioner" order by era desc limit 2
|
|
234
|
-
from docs where nodes none { where kind == "md:task"
|
|
299
|
+
from docs where nodes none { where kind == "md:task" and not checked }
|
|
235
300
|
from docs where tags exists { where $it == "tria-prima" }
|
|
236
|
-
from docs where $path == "x.md" select fm: entries(frontmatter) collect { k: $key, v: $it }
|
|
237
|
-
from docs select owner_id, owner:
|
|
238
|
-
from blocks where type == "task"
|
|
239
|
-
from blocks where type == "paragraph"
|
|
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 }
|
|
303
|
+
from blocks where type == "task" and not checked and under_heading("Launch") and doc.layer == "working"
|
|
304
|
+
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
|
|
308
|
+
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"
|
|
240
312
|
from blocks order by semantic("identity preservation across edits") desc limit 10
|
|
241
313
|
from edges where dst_kind == "external" select $dst_uri
|
|
242
|
-
|
|
314
|
+
docs count { where layer == "canon" }
|
|
243
315
|
|
|
244
316
|
Common mistake: bare \`path.startsWith(...)\` meant the intrinsic — it fails loud
|
|
245
317
|
with a hint; write \`$path.startsWith(...)\`. Another: \`where tags == "x"\` on a
|