@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
@@ -12,7 +12,7 @@ export function defaultCtx(target) {
12
12
  return { self: "d", doc: "d" };
13
13
  if (target === "blocks")
14
14
  return { self: "b", doc: "d" };
15
- if (target === "edges")
15
+ if (target === "links")
16
16
  return { self: "e", doc: "d" };
17
17
  return { self: "n", doc: "d" };
18
18
  }
@@ -35,33 +35,14 @@ function jsonPath(segs) {
35
35
  // blob. A field access is one of: a scalar-value expression (comparisons), or
36
36
  // an existence/count predicate (membership, size, has, bool-context). All
37
37
  // correlate on `<doc>.doc_id` (the correlated docs row, present on every target).
38
- const PROP_SOURCES = new Set(["frontmatter", "inline", "computed"]);
38
+ const PROP_SOURCES = new Set(["data", "frontmatter", "inline", "computed"]);
39
39
  // Computed properties surfaced as document intrinsics (12 §4). Engine-derived,
40
40
  // stored as source='computed' rows keyed by their $ name. Filterable like any
41
41
  // property (scalar ==, list(), membership) but never shadow authored keys.
42
42
  const COMPUTED_INTRINSICS = new Set(["$title", "$tags"]);
43
- // Base names of the document intrinsics ($id, $path, ...) MINUS their `$`. On
44
- // the docs open namespace (and doc.<k> reach-through), a BARE first segment
45
- // equal to one of these is almost always a typo for the intrinsic: it would
46
- // otherwise resolve to an absent frontmatter key and silently match nothing
47
- // (a misleading empty). We reject it with a hint instead. `title`/`tags` are
48
- // deliberately EXCLUDED: their $-forms are `computed` intrinsics that do not
49
- // shadow authored frontmatter, so bare `title`/`tags` remain legitimate key
50
- // access (see COMPUTED_INTRINSICS and QUERY_SYNTAX). A source-scoped form
51
- // (frontmatter.path / inline.path) is an explicit request for that property
52
- // and is likewise not caught — only the bare first-segment case is the trap.
53
- const RESERVED_INTRINSIC_BASENAMES = new Set([
54
- "id", "path", "repo", "updated_at", "content_hash", "body",
55
- ]);
56
- // Guard the docs open namespace against reserved-intrinsic collisions. `segs`
57
- // are the property key segments (after any doc./source scope has been peeled).
58
- // Throws when the FIRST segment shadows an intrinsic base name.
59
- function guardReservedCollision(segs) {
60
- if (RESERVED_INTRINSIC_BASENAMES.has(segs[0])) {
61
- const name = segs[0];
62
- throw new FilterInvalid(`bare '${name}' reads a frontmatter key; did you mean the intrinsic $${name}? (use frontmatter.${name} to force the property)`, "10 §2");
63
- }
64
- }
43
+ // There is no reserved-basename guard any more (spec/surface §1.2, 3.0): the
44
+ // engine attributes are bare and win on collision; an authored key of the same
45
+ // name is reached through `data.<k>` / `frontmatter.<k>` / `inline.<k>`.
65
46
  // Validate + join identifier segments into a dotted property key. Segments come
66
47
  // from the lexer's identifier rule, so inlining is injection-safe (mirrors
67
48
  // jsonPath); keeping keys param-free preserves the "fields carry no params"
@@ -81,7 +62,7 @@ function propKey(segs) {
81
62
  function propRef(field, target) {
82
63
  let segs = field.segments;
83
64
  if (target === "docs") {
84
- // Computed intrinsic ($title, $tags): route to source='computed' rows.
65
+ // Computed intrinsic (title, tags): route to source='computed' rows.
85
66
  if (field.intrinsic) {
86
67
  if (segs.length === 1 && COMPUTED_INTRINSICS.has(segs[0]))
87
68
  return { key: segs[0], source: "computed" };
@@ -97,7 +78,7 @@ function propRef(field, target) {
97
78
  if (segs[0] !== "doc")
98
79
  return null;
99
80
  segs = segs.slice(1);
100
- // doc.$title reach-through to a computed intrinsic.
81
+ // doc.title reach-through to a computed intrinsic.
101
82
  if (segs.length === 1 && COMPUTED_INTRINSICS.has(segs[0]))
102
83
  return { key: segs[0], source: "computed" };
103
84
  if (segs.length === 0 || segs[0].startsWith("$") || segs[0] === "format")
@@ -107,10 +88,6 @@ function propRef(field, target) {
107
88
  const source = segs[0];
108
89
  return { key: propKey(segs.slice(1)), source };
109
90
  }
110
- // Bare (non-`$`, non-source-scoped) key: reject a first segment that shadows
111
- // a reserved intrinsic base name (path/id/repo/...) so a typo for `$path`
112
- // fails loud instead of silently resolving to an absent frontmatter key.
113
- guardReservedCollision(segs);
114
91
  return { key: propKey(segs) };
115
92
  }
116
93
  function srcClause(source, alias = "p") {
@@ -186,13 +163,12 @@ function fieldSql(field, target, ctx) {
186
163
  if (field.intrinsic) {
187
164
  if (target === "docs") {
188
165
  switch (head) {
189
- case "$id": return { expr: `${self}.doc_id` };
190
- case "$path": return { expr: `${self}.path` };
191
- case "$repo": return { expr: `${self}.repo_id` };
192
- case "$updated_at": return { expr: `(SELECT c.ts FROM revisions r JOIN commits c ON c.commit_id = r.commit_id WHERE r.rev_id = ${self}.current_rev)` };
193
- case "$content_hash": return { expr: `lower(hex(${self}.file_hash))` };
166
+ case "id": return { expr: `${self}.doc_id` };
167
+ case "path": return { expr: `${self}.path` };
168
+ case "updated_at": return { expr: `(SELECT c.ts FROM revisions r JOIN commits c ON c.commit_id = r.commit_id WHERE r.rev_id = ${self}.current_rev)` };
169
+ case "hash": return { expr: `lower(hex(${self}.file_hash))` };
194
170
  default: {
195
- // Computed property intrinsic ($title, $tags) → properties row scalar.
171
+ // Computed property intrinsic (title, tags) → properties row scalar.
196
172
  const ref = propRef(field, target);
197
173
  if (ref)
198
174
  return { expr: propScalarExpr(ref, ctx) };
@@ -202,41 +178,24 @@ function fieldSql(field, target, ctx) {
202
178
  }
203
179
  else if (target === "nodes") {
204
180
  switch (head) {
205
- case "$id": return { expr: `${self}.node_id` };
206
- case "$node_id": return { expr: `${self}.node_id` };
207
- case "$doc_id": return { expr: `${self}.doc_id` };
208
- case "$block_id": return { expr: `${self}.block_id` };
209
- case "$path": return { expr: `${doc}.path` };
181
+ case "id": return { expr: `${self}.node_id` };
182
+ case "path": return { expr: `${doc}.path` };
210
183
  default: throw new FilterInvalid(`unknown intrinsic ${head} on nodes`, "10 §2");
211
184
  }
212
185
  }
213
- else if (target === "edges") {
186
+ else if (target === "links") {
214
187
  switch (head) {
215
- case "$id": return { expr: `${self}.edge_id` };
216
- case "$src": return { expr: `${self}.src_doc` };
217
- case "$dst": return { expr: `${self}.dst_node` };
218
- case "$src_block": return { expr: `${self}.src_block` };
219
- case "$via": return { expr: `${self}.via_node` };
220
- case "$from_commit": return { expr: `${self}.from_commit` };
221
- case "$path": return { expr: `${doc}.path` }; // source document's path
222
- // Meaningful destination, resolved per dst_kind (cheap correlated lookups,
223
- // NULL when not applicable): the target document's path, or the external
224
- // node's URI. Lets `from edges` show real targets, not opaque node ids.
225
- case "$dst_path": return { expr: `(SELECT dd.path FROM docs dd WHERE dd.doc_id = ${self}.dst_node)` };
226
- case "$dst_uri": return { expr: `(SELECT xn.uri FROM external_nodes xn WHERE xn.node_id = ${self}.dst_node)` };
227
- default: throw new FilterInvalid(`unknown intrinsic ${head} on edges`, "10 §2");
188
+ case "id": return { expr: `${self}.edge_id` };
189
+ case "path": return { expr: `${doc}.path` }; // source document's path
190
+ default: throw new FilterInvalid(`unknown intrinsic ${head} on links`, "10 §2");
228
191
  }
229
192
  }
230
193
  else {
231
194
  switch (head) {
232
- case "$id": return { expr: `${self}.block_id` };
233
- case "$doc": return { expr: `${self}.doc_id` };
234
- case "$path": return { expr: `${doc}.path` };
235
- case "$ordinal": return { expr: `${self}.ordinal` };
236
- case "$depth": return { expr: `${self}.depth` };
237
- case "$content_hash": return { expr: `lower(hex(${self}.raw_hash))` };
238
- case "$body": return { expr: `${self}.text` };
239
- case "$updated_at": return { expr: `(SELECT MAX(c.ts) FROM block_changes bc JOIN commits c ON c.commit_id = bc.commit_id WHERE bc.block_id = ${self}.block_id)` };
195
+ case "id": return { expr: `${self}.block_id` };
196
+ case "path": return { expr: `${doc}.path` };
197
+ case "hash": return { expr: `lower(hex(${self}.raw_hash))` };
198
+ case "updated_at": return { expr: `(SELECT MAX(c.ts) FROM block_changes bc JOIN commits c ON c.commit_id = bc.commit_id WHERE bc.block_id = ${self}.block_id)` };
240
199
  default: throw new FilterInvalid(`unknown intrinsic ${head} on blocks`, "10 §2");
241
200
  }
242
201
  }
@@ -281,7 +240,7 @@ function fieldSql(field, target, ctx) {
281
240
  }
282
241
  throw new FilterInvalid(`unknown field '${segs.join(".")}' on nodes`, "10 §2");
283
242
  }
284
- if (target === "edges") {
243
+ if (target === "links") {
285
244
  if (head === "predicate")
286
245
  return { expr: `${self}.predicate` };
287
246
  if (head === "provenance")
@@ -290,7 +249,21 @@ function fieldSql(field, target, ctx) {
290
249
  return { expr: `${self}.dst_kind` };
291
250
  if (head === "anchor")
292
251
  return { expr: `${self}.anchor` };
293
- if (head === "src_field")
252
+ // The links target's plain fields (spec/surface §1.2, 3.0): foreign keys
253
+ // named for what they are, the `src_field` column as `field`.
254
+ if (head === "doc_id")
255
+ return { expr: `${self}.src_doc` };
256
+ if (head === "block_id")
257
+ return { expr: `${self}.src_block` };
258
+ if (head === "via_id")
259
+ return { expr: `${self}.via_node` };
260
+ if (head === "dst_id")
261
+ return { expr: `${self}.dst_node` };
262
+ if (head === "dst_path")
263
+ return { expr: `(SELECT dd.path FROM docs dd WHERE dd.doc_id = ${self}.dst_node)` };
264
+ if (head === "uri")
265
+ return { expr: `(SELECT xn.uri FROM external_nodes xn WHERE xn.node_id = ${self}.dst_node)` };
266
+ if (head === "field")
294
267
  return { expr: `${self}.src_field` };
295
268
  if (head === "doc") {
296
269
  // reach-through to the SOURCE document (edges join docs on src_doc).
@@ -305,7 +278,7 @@ function fieldSql(field, target, ctx) {
305
278
  return { expr: propScalarExpr(ref, ctx) };
306
279
  return { expr: `json_extract(${doc}.metadata, ${jsonPath(rest)})` };
307
280
  }
308
- throw new FilterInvalid(`unknown field '${segs.join(".")}' on edges`, "10 §2");
281
+ throw new FilterInvalid(`unknown field '${segs.join(".")}' on links`, "10 §2");
309
282
  }
310
283
  // blocks target
311
284
  if (head === "type")
@@ -674,8 +647,8 @@ function compileWithin(target, ctx) {
674
647
  // match their own row; docs match when any of their blocks does; nodes use the
675
648
  // node FTS index. Aliased by the scope so it works under OQX's per-scope aliases.
676
649
  function compileText(terms, target, ctx) {
677
- if (target === "edges")
678
- throw new FilterInvalid("text(...) is not available on the edges target (edges carry no text index)", "10 §5");
650
+ if (target === "links")
651
+ throw new FilterInvalid("text(...) is not available on the links target (links carry no text index)", "10 §5");
679
652
  const match = sanitizeFtsQuery(terms);
680
653
  if (match === "")
681
654
  return { sql: "(1 = 0)", params: [] };
@@ -701,8 +674,8 @@ function compileText(terms, target, ctx) {
701
674
  // threshold comparison excludes it (absence = false). The query vector + model
702
675
  // come from ctx.semantic (the runner embedded the literal); absent ⇒ loud.
703
676
  function compileSemantic(phrase, target, ctx) {
704
- if (target === "nodes" || target === "edges") {
705
- throw new FilterInvalid('semantic(...) is available on the docs and blocks targets (nodes/edges have no embeddings)', "OQX semantic");
677
+ if (target === "nodes" || target === "links") {
678
+ throw new FilterInvalid('semantic(...) is available on the docs and blocks targets (nodes/links have no embeddings)', "OQX semantic");
706
679
  }
707
680
  const resolved = ctx.semantic?.(phrase);
708
681
  if (!resolved) {
@@ -103,7 +103,7 @@ class Parser {
103
103
  while (this.peek().type === "dot") {
104
104
  this.next();
105
105
  // A `$`-segment after a dot is an intrinsic reach-through continuation
106
- // (`doc.$path`, `doc.$updated_at`): the lexer emits it as a `field` token
106
+ // (`doc.path`, `doc.updated_at`): the lexer emits it as a `field` token
107
107
  // (value keeps the `$`). It is never a method receiver, so append it as a
108
108
  // field segment directly. The compiler routes `doc.$…` to docs intrinsics.
109
109
  if (this.peek().type === "field") {
@@ -1,4 +1,6 @@
1
1
  import type { Store } from "../core/store/store.js";
2
+ /** A resolve hit (spec/search §4): the block's `id` (spec/surface §1.2, 3.0),
3
+ * its `locator`, a `preview` and the fusion `evidence`. */
2
4
  export interface ResolveHit {
3
5
  id: string;
4
6
  locator: string;
@@ -43,8 +43,8 @@ export function hybridSearch(store, input) {
43
43
  const ftsRanks = new Map();
44
44
  if (input.text) {
45
45
  textSearch(store, input.repoId, input.text, { limit: 200 }).hits.forEach((h, i) => {
46
- if (!ftsRanks.has(h.blockId))
47
- ftsRanks.set(h.blockId, i + 1);
46
+ if (!ftsRanks.has(h.id))
47
+ ftsRanks.set(h.id, i + 1);
48
48
  });
49
49
  }
50
50
  const vecRanks = new Map();
@@ -1,7 +1,7 @@
1
1
  import type { Store } from "../core/store/store.js";
2
2
  export interface TextHit {
3
- blockId: string;
4
- docId: string;
3
+ id: string;
4
+ doc_id: string;
5
5
  path: string;
6
6
  type: string;
7
7
  text: string;
@@ -5,7 +5,7 @@ export function textSearch(store, repoId, queryStr, opts = {}) {
5
5
  if (match === "")
6
6
  return { hits: [], truncated: false };
7
7
  const rows = store.db
8
- .prepare(`SELECT b.block_id AS blockId, b.doc_id AS docId, d.path AS path, b.type AS type,
8
+ .prepare(`SELECT b.block_id AS "id", b.doc_id AS doc_id, d.path AS "path", b.type AS type,
9
9
  b.text AS text, bm25(blocks_fts) AS score
10
10
  FROM blocks_fts
11
11
  JOIN blocks b ON b.rowid = blocks_fts.rowid
@@ -1,14 +1,14 @@
1
1
  export declare const SPEC_VERSIONS: {
2
- readonly oqx: "0.18";
2
+ readonly oqx: "0.20";
3
3
  readonly format: "0.2";
4
4
  readonly reconcile: "2.3";
5
5
  readonly store: "13.5";
6
6
  readonly properties: "1.1";
7
7
  readonly graph: "1.1";
8
- readonly search: "1.2";
9
- readonly mutate: "1.3";
10
- readonly sync: "1.3";
11
- readonly surface: "2.0";
12
- readonly cli: "2.0";
8
+ readonly search: "2.0";
9
+ readonly mutate: "2.0";
10
+ readonly sync: "2.0";
11
+ readonly surface: "3.0";
12
+ readonly cli: "3.0";
13
13
  };
14
14
  export type SpecName = keyof typeof SPEC_VERSIONS;
@@ -16,10 +16,10 @@ export const SPEC_VERSIONS = {
16
16
  store: "13.5",
17
17
  properties: "1.1",
18
18
  graph: "1.1",
19
- search: "1.2",
20
- mutate: "1.3",
21
- sync: "1.3",
22
- surface: "2.0",
23
- cli: "2.0",
19
+ search: "2.0",
20
+ mutate: "2.0",
21
+ sync: "2.0",
22
+ surface: "3.0",
23
+ cli: "3.0",
24
24
  };
25
25
  //# sourceMappingURL=spec-versions.js.map
@@ -3,22 +3,55 @@ import type { DocOpResult, DocMoveResult } from "./mutate/docs.js";
3
3
  import type { RetargetHit } from "./mutate/macros.js";
4
4
  import type { Opset } from "./mutate/opset.js";
5
5
  import type { DocsUpdateResult } from "./mutate/plan-update.js";
6
+ import type { InboundLink } from "./graph/inbound-links.js";
6
7
  import type { ObserveResult, ObserveDeleteResult } from "./sync/observe.js";
7
8
  import type { SweepResult } from "./sync/freshness.js";
8
9
  import type { TextSearchResult } from "./search/text.js";
9
10
  import type { ResolveHit } from "./search/resolve.js";
10
11
  /** `spec/mutate` §4's result: `revisions[].path` and the dry-run `diffs` keys. */
11
12
  export declare function surfaceApplyResult<T extends ApplyResult>(res: T): T;
12
- /** `docs_create` / `docs_delete` / `docs_set_meta`: `path` and the `diffs` keys. */
13
- export declare function surfaceDocOpResult<T extends DocOpResult>(res: T): T;
14
- /** `docs_move`: the document-op fields plus each dangling link's source `path`
15
- * (its `target` stays as authored — that is what the link says). */
16
- export declare function surfaceDocMoveResult(res: DocMoveResult): DocMoveResult;
13
+ /** A document operation's result on the wire (spec/surface §4, 3.0): the
14
+ * library's `{ docId, path, … }` (`spec/mutate` §6, unchanged there) renamed
15
+ * to the document's row attributes `id`/`path`, the path rooted, the
16
+ * dry-run `diffs` re-keyed; every other key (`committed`, `deleted`, …) as is. */
17
+ export type SurfaceDocOpResult<T extends DocOpResult> = Omit<T, "docId" | "path"> & {
18
+ id: string;
19
+ path: string;
20
+ };
21
+ /** `docs_create` / `docs_delete` / `docs_set_meta`. */
22
+ export declare function surfaceDocOpResult<T extends DocOpResult>(res: T): SurfaceDocOpResult<T>;
23
+ /** An inbound link on the wire (`docs_move.dangling[]`): an edge row under the
24
+ * `edges` target's names (spec/surface §1.2) — `doc_id`, `path` (rooted),
25
+ * `block_id`, `target` (as authored), `anchor`, `field?`. */
26
+ export interface SurfaceInboundLink {
27
+ doc_id: string;
28
+ path: string;
29
+ block_id: string | null;
30
+ target: string;
31
+ anchor: string | null;
32
+ field?: string;
33
+ }
34
+ export declare function surfaceInboundLink(l: InboundLink): SurfaceInboundLink;
35
+ /** `docs_move`: the document-op fields plus each dangling link (its `target`
36
+ * stays as authored — that is what the link says). */
37
+ export declare function surfaceDocMoveResult(res: DocMoveResult): Omit<SurfaceDocOpResult<DocMoveResult>, "dangling"> & {
38
+ dangling: SurfaceInboundLink[];
39
+ };
40
+ /** An observe outcome on the wire (spec/surface §4, 3.0): `spec/store` §5's
41
+ * `{ docId, path, … }` (unchanged there) renamed to the document's row
42
+ * attributes `id`/`path`, the path rooted. */
43
+ export type SurfaceObserveResult<T extends {
44
+ docId: string | null;
45
+ path: string;
46
+ }> = Omit<T, "docId" | "path"> & {
47
+ id: T["docId"];
48
+ path: string;
49
+ };
17
50
  /** `observe` / `observe_many` outcomes. */
18
- export declare function surfaceObserveResult<T extends ObserveResult>(res: T): T;
51
+ export declare function surfaceObserveResult<T extends ObserveResult>(res: T): SurfaceObserveResult<T>;
19
52
  /** `observe_delete`. */
20
- export declare function surfaceObserveDeleteResult(res: ObserveDeleteResult): ObserveDeleteResult;
21
- /** `text_search` hits. */
53
+ export declare function surfaceObserveDeleteResult(res: ObserveDeleteResult): SurfaceObserveResult<ObserveDeleteResult>;
54
+ /** `blocks_search` hits (`path` rooted). */
22
55
  export declare function surfaceTextSearch(res: TextSearchResult): TextSearchResult;
23
56
  /** `resolve` hits: the locator is `<path>#<type>[<ordinal>]`; its path half is
24
57
  * rooted (a locator without `#` names no path and is left alone). */
@@ -1,13 +1,22 @@
1
- // The surface's path rule applied at its boundary (spec/surface §1 "Paths",
2
- // 2.0): every path a tool or verb returns is `/`-rooted. The surface-owned
3
- // readers (`core/read`, `graph/history`, `graph/link-health`, the query
4
- // binding) root their own output; the results that belong to another spec —
5
- // `spec/mutate`'s apply and document-operation results and opsets,
6
- // `spec/store`'s observe outcomes, `spec/search`'s hits, `spec/sync`'s sweep —
7
- // keep their storage-form paths in the library (their own fixtures pin them)
8
- // and are re-shaped HERE, by the MCP server and the `omg` verbs alike, so the
9
- // two clients cannot drift. Each function returns a new value; the input is
10
- // not mutated.
1
+ // The surface's rules applied at its boundary. Two of them:
2
+ //
3
+ // Paths (spec/surface §1 "Paths", 2.0): every path a tool or verb returns is
4
+ // `/`-rooted. The surface-owned readers (`core/read`, `graph/history`,
5
+ // `graph/link-health`, the query binding) root their own output; the results
6
+ // that belong to another spec — `spec/mutate`'s apply and document-operation
7
+ // results and opsets, `spec/store`'s observe outcomes, `spec/search`'s hits,
8
+ // `spec/sync`'s sweep — keep their storage-form paths in the library (their
9
+ // own fixtures pin them) and are re-shaped HERE.
10
+ //
11
+ // Row names (spec/surface §1.2, 3.0): a result that renders a document, block
12
+ // or edge row spells it the way the query binding does — `id`, `path`,
13
+ // `hash`, `raw`, `updated_at`, plain `doc_id`/`block_id` foreign keys. The
14
+ // shapes other specs own under their own names — `spec/mutate`'s document
15
+ // operation result `{ docId, path, … }`, `spec/store`'s observe outcome — are
16
+ // re-shaped HERE too (`SurfaceDocOpResult`, `SurfaceObserveResult`, …).
17
+ //
18
+ // Both the MCP server and the `omg` verbs call these, so the two clients
19
+ // cannot drift. Each function returns a new value; the input is not mutated.
11
20
  import { referencePath, referenceKeyed } from "./core/paths.js";
12
21
  /** `spec/mutate` §4's result: `revisions[].path` and the dry-run `diffs` keys. */
13
22
  export function surfaceApplyResult(res) {
@@ -17,24 +26,31 @@ export function surfaceApplyResult(res) {
17
26
  ...(res.diffs ? { diffs: referenceKeyed(res.diffs) } : {}),
18
27
  };
19
28
  }
20
- /** `docs_create` / `docs_delete` / `docs_set_meta`: `path` and the `diffs` keys. */
29
+ /** `docs_create` / `docs_delete` / `docs_set_meta`. */
21
30
  export function surfaceDocOpResult(res) {
22
- return { ...res, path: referencePath(res.path), ...(res.diffs ? { diffs: referenceKeyed(res.diffs) } : {}) };
31
+ const { docId, path, ...rest } = res;
32
+ return { id: docId, path: referencePath(path), ...rest, ...(res.diffs ? { diffs: referenceKeyed(res.diffs) } : {}) };
23
33
  }
24
- /** `docs_move`: the document-op fields plus each dangling link's source `path`
25
- * (its `target` stays as authored — that is what the link says). */
34
+ export function surfaceInboundLink(l) {
35
+ const { path, ...rest } = l;
36
+ return { doc_id: rest.doc_id, path: referencePath(path), block_id: rest.block_id, target: rest.target, anchor: rest.anchor, ...(rest.field !== undefined ? { field: rest.field } : {}) };
37
+ }
38
+ /** `docs_move`: the document-op fields plus each dangling link (its `target`
39
+ * stays as authored — that is what the link says). */
26
40
  export function surfaceDocMoveResult(res) {
27
- return { ...surfaceDocOpResult(res), dangling: res.dangling.map((l) => ({ ...l, path: referencePath(l.path) })) };
41
+ return { ...surfaceDocOpResult(res), dangling: res.dangling.map(surfaceInboundLink) };
28
42
  }
29
43
  /** `observe` / `observe_many` outcomes. */
30
44
  export function surfaceObserveResult(res) {
31
- return { ...res, path: referencePath(res.path) };
45
+ const { docId, path, ...rest } = res;
46
+ return { id: docId, path: referencePath(path), ...rest };
32
47
  }
33
48
  /** `observe_delete`. */
34
49
  export function surfaceObserveDeleteResult(res) {
35
- return { ...res, path: referencePath(res.path) };
50
+ const { docId, path, ...rest } = res;
51
+ return { id: docId, path: referencePath(path), ...rest };
36
52
  }
37
- /** `text_search` hits. */
53
+ /** `blocks_search` hits (`path` rooted). */
38
54
  export function surfaceTextSearch(res) {
39
55
  return { ...res, hits: res.hits.map((h) => ({ ...h, path: referencePath(h.path) })) };
40
56
  }
@@ -10,7 +10,8 @@ export interface DiskStatus extends DiskDrift {
10
10
  checked: boolean;
11
11
  }
12
12
  export interface RepoStatus {
13
- repoId: string;
13
+ /** the repository's own id (spec/surface §1.2, 3.0: every row names its identity `id`) */
14
+ id: string;
14
15
  slug: string;
15
16
  rootPath: string;
16
17
  docs: number;
@@ -18,7 +18,7 @@ export function reposStatus(store, repoId, rootPath) {
18
18
  ? { ...detectDiskDrift(store, repoId, rootPath), checked: true }
19
19
  : { changed: 0, deleted: 0, untracked: 0, checked: false };
20
20
  return {
21
- repoId,
21
+ id: repoId,
22
22
  slug: repo?.slug ?? "",
23
23
  rootPath: rootPath ?? "",
24
24
  docs: count("SELECT count(*) c FROM docs WHERE repo_id = ? AND deleted_commit IS NULL"),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@omgbase/core",
3
- "version": "0.10.1",
3
+ "version": "0.11.1",
4
4
  "description": "Open Markdown Graph Base — embedded engine",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -55,7 +55,7 @@
55
55
  },
56
56
  "dependencies": {
57
57
  "@modelcontextprotocol/sdk": "^1.30.0",
58
- "@omgbase/oqx": "^0.18.0",
58
+ "@omgbase/oqx": "^0.20.0",
59
59
  "better-sqlite3": "^13.0.3",
60
60
  "mdast-util-from-markdown": "^2.0.2",
61
61
  "mdast-util-frontmatter": "^2.0.1",