@omgbase/core 0.10.0 → 0.11.0

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 +215 -121
  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 +71 -24
  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
@@ -10,20 +10,23 @@ 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>] [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 …\`). Every other clause always carries its keyword:
21
- a predicate is NEVER implicit (\`nodes exists { where kind == "md:task" }\`, not
22
- \`{ name, kind == "md:task" }\` — but a block may LEAD with a predicate, see Sugar),
23
- and \`from docs count\` is an error (write \`docs count { … }\`). \`where\` and
24
- later \`select\` items may use the same body's \`select\` aliases
25
- (\`select $path, old: era < 1000 from docs where old\`; an alias shadows a
26
- same-named field there).
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).
27
30
 
28
31
  ## Sugar (OQX 0.17)
29
32
 
@@ -33,98 +36,155 @@ Every form here is shorthand for an explicit directive (the AST is the explicit
33
36
  (comparison, call, !/is/not, in, literal, (…), a consumer test — anything
34
37
  but a bare name, a dotted path or a ^lift) is where-first. Bare names still
35
38
  project: nodes { name } ≡ nodes collect { name }; filter a bare field with
36
- 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
37
45
  refs(x)[0] ≡ refs(x) first { offset 0 } positional (integer literal or \${binding});
38
- out of range ⇒ absent; refs(x)[0].$path navigates the row
39
- ^docs[$path == ^company] ≡ ^docs first { where $path == ^company } first match or absent
40
- ^docs[$path == ^company]! ≡ … single { … } required: exactly one, else filter_invalid
41
- 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)
42
50
  — never a filter, never a coercion (0!, ""! are values); tightest: a!.b vs a.b!
43
- is x / not x ≡ !!x / !x (truthiness)
44
- x is y / x is not y identity (a row's id, else structural) — compares rows, which == does not;
51
+ is x / not x truthiness of x, and its negation (prefix; \`not\` ≡ \`!\`)
52
+ x is y / x is not y identity ($key: a store row's id, else structural) — compares rows, which == does not;
45
53
  x is null = absent; for scalars is ≡ ==; comparison precedence, no chaining
46
- a and b / a or b ≡ a && b / a || b (same precedence, short-circuit, value: title or $path coalesces)
47
- 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
48
56
  Reserved words (never a bare field name): from where select is not and or, true false null.
49
-
50
- Results are LEAN hits — {id, path} + whatever \`select\` projects (or \`values\`,
51
- \`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.
52
71
 
53
72
  ## Paths
54
73
 
55
74
  Every path the surface returns is \`/\`-rooted — the form a reference is
56
75
  written in (\`[x](/projects/oqx.md)\`, \`before: [/timeline/kickoff.md]\`): a
57
- 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
58
77
  accepts tolerates both forms (\`/a.md\` or \`a.md\`): tool arguments, \`refs(x)\`,
59
- \`within(...)\`, and a string LITERAL compared with \`$path\`/\`$dst_path\` by
60
- \`==\`/\`!=\` or passed to their \`.startsWith(...)\` (it is rooted first, so
61
- \`$path == "a.md"\` and \`$path == "/a.md"\` both match). A property holding a
62
- reference compares directly: \`where ^$path in list(after)\`,
63
- \`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.
64
85
 
65
86
  ## Targets & field namespaces
66
87
 
67
- The sigil rule: \`$\`-prefixed names are engine intrinsics; BARE identifiers are
68
- your content. On docs a bare first segment that collides with an intrinsic base
69
- name (id/path/updated_at/content_hash/body) is REJECTED with a "did you mean
70
- $X?" hint (bare \`path\` would silently read an absent frontmatter key). Force the
71
- property with \`frontmatter.<k>\`. \`title\`/\`tags\` are NOT reserved: \`$title\`/\`$tags\`
72
- 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).
73
110
 
74
111
  docs:
75
112
  - bare identifier = a document PROPERTY (frontmatter + inline \`key:: value\`,
76
113
  unioned; nested via dots: meta.owner). YAML/JSON docs
77
114
  expose the parsed object's keys the same way.
78
- - source-scoped = frontmatter.<k> / inline.<k>; the whole bag as a collection
79
- via entries(frontmatter) / entries(inline) (see entries()).
80
- - computed = $title (first H1), $tags (body #hashtags), format
81
- (the doc's format — "markdown"/"yaml"/"json"; a bare
82
- computed field, present on EVERY doc, so has(format) is
83
- always true and \`format == "yaml"\` is the filter).
84
- - intrinsics = $id, $path (/-rooted, see Paths), $updated_at (ISO-8601,
85
- sorts chronologically), $body (whole file), $content_hash.
86
- - relations = nodes, blocks, doc.out / doc.in (citation graph),
87
- 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).
88
132
 
89
133
  blocks:
90
- - bare fields = type, text, and every attrs key FLATTENED onto the row
91
- (bare \`checked\` reads attrs.checked; \`attrs.<k>\` still
92
- works; type/text win on collision; absent ⇒ false).
93
- - intrinsics = $id, $doc, $path, $ordinal, $depth, $updated_at, $body
94
- (the block text), $content_hash (the block's OWN raw hash —
95
- the value update/split expect).
96
- - reach-through = doc.<key> / doc.$path / doc.format = the CONTAINING doc.
97
- - 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.
98
149
 
99
150
  nodes:
100
- - bare fields = kind (md:task / md:link / md:section / yaml:…), name,
101
- value, + attrs keys flattened (checked, level).
102
- - intrinsics = $id (=$node_id), $doc_id, $block_id, $path.
103
- - 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.
104
157
  - relations = section.blocks, section.children, section.subsections.
105
158
 
106
- edges: (the authored link graph as rows — one per open edge)
107
- - 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
108
168
  ("link"/"frontmatter"/"inline_field"/…), dst_kind
109
- ("document"/"external"/"collection"), anchor, src_field
110
- - intrinsics = $id, $src, $dst, $dst_path (target doc path, /-rooted;
111
- NULL when dangling/external), $dst_uri (external URL;
112
- NULL otherwise), $src_block, $via, $from_commit; $path
113
- and doc.<key> reach the SOURCE document.
114
- - 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.
115
175
 
116
176
  ## Scoping (ADR-015; the root row since surface 2.0): bare names are LOCAL
117
177
 
118
178
  name the CURRENT row only — never falls through to an enclosing row
119
179
  or the repository; an absent field is simply absent.
120
180
  ^name one scope OUT (the enclosing row's fields/intrinsics/lifts);
121
- ^^name two scopes out. \`^$path\` = the enclosing row's path.
122
- ^docs ^nodes ^blocks ^edges
181
+ ^^name two scopes out. \`^path\` = the enclosing row's path.
182
+ ^docs ^nodes ^blocks ^links
123
183
  THE ROOT ROW (surface 2.0, oqx 0.18): the repository is the root
124
184
  scope's row, one scope out from a top-level row, so ^docs there is
125
185
  the whole documents scan (uncorrelated until you add a ^ predicate);
126
186
  one caret per enclosing block (^^docs from depth two), or the
127
- 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.
128
188
  ^$it = the root object: ^$it.docs ≡ ^docs, entries(^$it) names the
129
189
  four collections. At the root scope itself a bare target is the scan
130
190
  (docs count { … }, from docs, entries(docs)).
@@ -137,7 +197,9 @@ edges: (the authored link graph as rows — one per open edge)
137
197
  receiver is a list property); ^$it = the enclosing row.
138
198
  $value is NOT an intrinsic (since oqx 0.15): it reads a
139
199
  property literally named $value, absent when there is none.
140
- $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).
141
203
 
142
204
  ## Scalar semantics (@omgbase/oqx)
143
205
 
@@ -148,7 +210,7 @@ edges: (the authored link graph as rows — one per open edge)
148
210
  Relational (< <= > >=) on an absent operand → FALSE. Bare \`f\` in
149
211
  boolean position uses JS truthiness (absent/false/0/"" ⇒ false;
150
212
  NOTE: "false", [] and {} are truthy). has(f) = explicit presence.
151
- boolean && || ! grouping ( … )
213
+ boolean and or not grouping ( … ) — && || ! are accepted synonyms
152
214
  arithmetic + - * / % (+ concatenates if either side is a string)
153
215
  membership x in y array → typed membership; string → substring;
154
216
  object → key exists; range → coverage. Any list
@@ -160,7 +222,7 @@ edges: (the authored link graph as rows — one per open edge)
160
222
  range-shaped STRING property as an interval: "2026-01-15" in range(window).
161
223
  strings x.contains("s") x.startsWith("p") x.endsWith("s") x.matches("^re$")
162
224
  (real regex) x.lower() x.upper() size(x). ALL CASE-SENSITIVE —
163
- fold with .lower(): $title.lower().contains("aurora").
225
+ fold with .lower(): title.lower().contains("aurora").
164
226
  functions has(f) size(x) list(x) entries(x) range(s)
165
227
  literals "str" 'str' 123 1.5 true false null
166
228
  determinism no now()/clock/random. Lists/structs are not literals; ternary is
@@ -174,7 +236,7 @@ edges: (the authored link graph as rows — one per open edge)
174
236
  via the node index); composes anywhere, incl. nested scopes.
175
237
  semantic("phrase") embedding cosine SCORE (docs/blocks): threshold it
176
238
  (semantic("x") > 0.6), project it, or \`order by semantic("x")
177
- 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.
178
240
  blocks target only:
179
241
  under(id_or_heading) block in the containment subtree / section range
180
242
  under_heading("Launch") an ancestor section heading contains the text (ci)
@@ -195,8 +257,8 @@ edges: (the authored link graph as rows — one per open edge)
195
257
  a source, a receiver or a follow destination:
196
258
  follow refs(before), refs(after) walks a timeline both ways.
197
259
  The reverse direction needs no function: a document's
198
- $path is already the reference form, so
199
- ^docs collect { where ^$path in list(after) } is
260
+ path is already the reference form, so
261
+ ^docs { where ^path in list(after) } is
200
262
  "the documents whose \`after\` names me".
201
263
  A HIT IS A STORE ROW: a top-level row that is not a doc/block/node/edge fails
202
264
  (filter_invalid). \`follow before\` over a list of paths
@@ -205,14 +267,15 @@ edges: (the authored link graph as rows — one per open edge)
205
267
  ## Nested queries & consumers
206
268
 
207
269
  <receiver> collect|exists|none|count|first|single { <block> }
208
- 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
209
272
  list property (tags), entries(x), or a root collection (^docs; 0^docs at depth ≥ 2).
210
273
  block = [select …] [where …] [follow …] [order by …] [limit N] [offset N]
211
274
  — the same fixed order as the top level (\`from\` optional; the
212
275
  receiver supplies the rows). A leading name/alias list is the
213
276
  projection (\`select\` dropped); a predicate always needs \`where\`.
214
- exists ≥1 row nodes exists { where kind == "md:task" && !checked }
215
- none 0 rows nodes none { where kind == "md:task" && !checked } ("every task done")
277
+ exists ≥1 row nodes exists { where kind == "md:task" and not checked }
278
+ none 0 rows nodes none { where kind == "md:task" and not checked } ("every task done")
216
279
  count a number nodes count { where kind == "md:task" } >= 2
217
280
  collect rows (default) — in select for nested results; in where with a lift
218
281
  first / single zero-or-one (single errors on >1) — lookups in select
@@ -221,62 +284,90 @@ edges: (the authored link graph as rows — one per open edge)
221
284
  consumer reduces: nodes exists { offset 1 } = at least two;
222
285
  first { … offset 1 } = the second.
223
286
  lift \`^name:\` in a where-collect binds values outward:
224
- select $path, open from docs where nodes collect { ^open: value where kind == "md:task" && !checked }
225
- joins ^<t> exists { where slug == ^ref } semi-join; !… exists anti-join;
226
- ^nodes single { where kind == "person" && attrs.id == ^owner_id } lookup;
287
+ select path, open from docs where nodes collect { ^open: value where kind == "md:task" and not checked }
288
+ joins ^<t> exists { where slug == ^ref } semi-join; not … exists anti-join;
289
+ ^nodes single { where kind == "person" and attrs.id == ^owner_id } lookup;
227
290
  x in ^keys membership over a lifted set.
228
291
 
229
292
  ## select — projection
230
293
 
231
294
  select a, alias: expr, nested: nodes collect { … }
232
- bare key → the property (scalar-authored → scalar; list → array); $-intrinsics;
233
- a call/arithmetic needs an alias (n: size(tags)) unless in values mode.
234
- "$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.
235
300
  distinct select distinct type (dedup hits by projected value)
236
301
  values select <ONE item> values → bare values instead of records:
237
302
  from docs select distinct type values → values: ["hub","lab-note",…]
238
- select $path values from docs → values: ["/index.md", …]
303
+ select path values from docs → values: ["/index.md", …]
239
304
  tags: tags collect { $it values } → a plain array
240
305
  h: nodes first { name values where kind == "md:section" order by first_ordinal }
241
306
  entries(x) a record as a collection ($key / $it per entry), key order:
242
- select fm: entries(frontmatter) collect { k: $key, v: $it }
243
- where entries(frontmatter) exists { where $key == "era" && $it > 1600 }
307
+ select fm: entries(data) collect { k: $key, v: $it }
308
+ where entries(frontmatter) exists { where $key == "era" and $it > 1600 }
244
309
  entries(inline), entries(attrs), a nested map, or a list (index keys)
245
310
  $semantic_score is not a field — project semantic("…") under an alias instead.
246
311
 
247
312
  ## order by, limit/offset, pagination
248
313
 
249
- 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)
250
315
  limit N / offset N define the result SET (integer literals);
251
316
  the tool's limit/cursor then page WITHIN it
252
317
  cursor opaque keyset on (path,id); a custom order by
253
318
  disables it (you get the top page + truncated)
254
319
 
255
- ## Graph traversal (follow) and edges
320
+ ## Graph traversal (follow) and links
256
321
 
257
- from docs where <seed> follow doc.out docs the seed links TO
258
- from docs where <seed> follow doc.in backlinks
259
- follow doc.out { depth 3 } bound (1..8, default 8)
260
- follow doc.out { where layer == "canon" } keep only matching successors
261
- 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
262
330
  (^^ = the walk's enclosing scope)
263
- 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
264
334
  (one step dedups by identity)
265
- follow ^docs collect { where doc.out exists { where $path == ^^$path } }
266
- a destination block, re-read per
267
- frontier row (^ = that row; ^docs
268
- is the root's documents) = doc.in
269
- follow doc.out { frontier type == "practitioner" } cut, keeping the frontier row
270
- 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
271
346
  Per-occurrence metadata: $depth (seed = 1), $stop (interior|leaf|frontier|
272
- depth|cycle), $leaf/$frontier, $ordinal — usable in select/order by and the
273
- top-level post-walk where, NOT metadata of the candidates the follow-local
274
- where/frontier read.
275
- Type-preserving relations: doc.out/doc.in, block.children, section.children,
276
- section.subsections.
277
- from edges where $dst_path == "/notes/x.md" inbound edges to a doc, as rows
278
- from edges where predicate == "depends_on" select $src, $dst_path
279
- from docs where !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
+ from links where predicate == "depends_on" select doc_id, dst_path
370
+ from docs where not doc.in_links exists { } orphans (nothing links in)
280
371
  (The \`graph\` tool is a convenience wrapper that compiles to a follow query.)
281
372
 
282
373
  ## semantic grain
@@ -287,29 +378,32 @@ text()/where prune the candidate set; only order by reweights.
287
378
  ## Examples
288
379
 
289
380
  from docs where layer == "working"
290
- from docs where $path.startsWith("/guides/") && $updated_at >= "2026-08-01"
381
+ from docs where path.startsWith("/guides/") and updated_at >= "2026-08-01"
291
382
  from docs where "pricing" in list(tags) select layer, tags
292
383
  from docs where inline.owner == "alice"
293
- from docs where $title.lower().contains("q3 plan")
384
+ from docs where title.lower().contains("q3 plan")
294
385
  from docs where format == "yaml"
295
386
  from docs where type == "practitioner" order by era desc limit 2
296
- from docs where nodes none { where kind == "md:task" && !checked }
387
+ from docs where nodes none { where kind == "md:task" and not checked }
297
388
  from docs where tags exists { where $it == "tria-prima" }
298
- from docs where $path == "/x.md" select fm: entries(frontmatter) collect { k: $key, v: $it }
299
- from docs select owner_id, owner: ^nodes single { where kind == "person" && attrs.id == ^owner_id }
300
- from blocks where type == "task" && !checked && under_heading("Launch") && doc.layer == "working"
301
- from blocks where type == "paragraph" && has_edge("references", "d_92aaaaa")
302
- select $path, $depth from docs where $path == "/timeline/review.md" follow refs(before), refs(after)
303
- select $path, next: ^docs collect { $path where ^$path in list(before) } from docs where type == "milestone"
304
- select $path, n: ^docs count { }, id: ^$id from docs limit 1
305
- select $path, back: nodes first { select p: 0^docs collect { $path where ^^$path in list(after) } values } from docs where type == "milestone"
306
- select $path, prior: refs(before) collect { $path, when } from docs where type == "milestone"
389
+ from docs where path == "/x.md" select fm: entries(data) collect { k: $key, v: $it }
390
+ from docs select owner_id, owner: ^nodes single { where kind == "person" and attrs.id == ^owner_id }
391
+ from blocks where type == "task" and not checked and under_heading("Launch") and doc.layer == "working"
392
+ from blocks where type == "paragraph" and has_edge("references", "d_92aaaaa")
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)
397
+ docs { type == "substance" and not verified }
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"
307
401
  from blocks order by semantic("identity preservation across edits") desc limit 10
308
- from edges where dst_kind == "external" select $dst_uri
402
+ from links where dst_kind == "external" select uri
309
403
  docs count { where layer == "canon" }
310
404
 
311
- Common mistake: bare \`path.startsWith(...)\` meant the intrinsic — it fails loud
312
- with a hint; write \`$path.startsWith(...)\`. Another: \`where tags == "x"\` on a
313
- 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\`).
314
408
  `;
315
409
  //# sourceMappingURL=reference.js.map
@@ -12,13 +12,16 @@ export interface ServerContext {
12
12
  rootPath?: string;
13
13
  /**
14
14
  * Embed a query string to a vector for semantic search. Absent ⇒ the `query`
15
- * tool's `semantic` param yields `semantic_unavailable` (no provider). The
15
+ * tool's `semantic` param yields `provider_unavailable` (no provider). The
16
16
  * host (CLI `omg mcp`) supplies this from the repo's configured embedder.
17
17
  */
18
18
  embedQuery?: (text: string) => Promise<{
19
19
  model: string;
20
20
  vec: Float32Array;
21
21
  }>;
22
+ /** The configured provider's name (`embedding.provider`), for the
23
+ * `provider_unavailable` error's `data` when the host knows it. */
24
+ embedProvider?: string;
22
25
  /**
23
26
  * Called after any successful write tool (apply/macros/doc ops). The host
24
27
  * (CLI `omg mcp`) uses this to schedule a background embed drain so a block's
@@ -35,4 +38,9 @@ export interface ServerContext {
35
38
  */
36
39
  host?: HostInfo;
37
40
  }
41
+ /** The message every tool uses for a `semantic(...)` call the server cannot rank. */
42
+ export declare const PROVIDER_UNAVAILABLE_MESSAGE = "semantic(...) needs an embedding provider; none is configured \u2014 set embedding.provider (omg config set embedding.provider <command|url>) and embed the repository (omg embed drain)";
38
43
  export declare function buildServer(ctx: ServerContext): McpServer;
44
+ /** The catalog's tool names in registration order with their descriptions —
45
+ * what `spec/surface/tool-descriptions.json` pins for both engines. */
46
+ export declare function toolDescriptions(server: McpServer): Promise<Record<string, string>>;