@omgbase/core 0.27.1 → 0.29.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 (71) hide show
  1. package/dist/src/core/read/document.d.ts +11 -10
  2. package/dist/src/core/read/document.js +12 -11
  3. package/dist/src/core/read/nodes.d.ts +10 -10
  4. package/dist/src/core/read/nodes.js +11 -11
  5. package/dist/src/core/read/reader.d.ts +37 -24
  6. package/dist/src/core/read/reader.js +44 -31
  7. package/dist/src/graph/history.d.ts +25 -22
  8. package/dist/src/graph/history.js +17 -17
  9. package/dist/src/graph/inbound-links.d.ts +7 -7
  10. package/dist/src/graph/inbound-links.js +7 -7
  11. package/dist/src/graph/link-health.d.ts +9 -9
  12. package/dist/src/graph/link-health.js +9 -9
  13. package/dist/src/graph/links.d.ts +3 -3
  14. package/dist/src/graph/links.js +4 -4
  15. package/dist/src/mcp/arguments.d.ts +10 -3
  16. package/dist/src/mcp/arguments.js +32 -9
  17. package/dist/src/mcp/errors.d.ts +1 -1
  18. package/dist/src/mcp/graph.d.ts +22 -22
  19. package/dist/src/mcp/graph.js +53 -46
  20. package/dist/src/mcp/reference.d.ts +29 -1
  21. package/dist/src/mcp/reference.js +350 -222
  22. package/dist/src/mcp/server.d.ts +9 -0
  23. package/dist/src/mcp/server.js +169 -107
  24. package/dist/src/mutate/apply.d.ts +6 -6
  25. package/dist/src/mutate/apply.js +1 -1
  26. package/dist/src/mutate/canonical.d.ts +1 -1
  27. package/dist/src/mutate/canonical.js +2 -2
  28. package/dist/src/mutate/commit.d.ts +3 -3
  29. package/dist/src/mutate/commit.js +4 -4
  30. package/dist/src/mutate/docs.d.ts +1 -1
  31. package/dist/src/mutate/docs.js +8 -8
  32. package/dist/src/mutate/effects.d.ts +17 -17
  33. package/dist/src/mutate/effects.js +113 -113
  34. package/dist/src/mutate/macros.d.ts +5 -5
  35. package/dist/src/mutate/macros.js +1 -1
  36. package/dist/src/mutate/materialize.js +2 -2
  37. package/dist/src/mutate/ops.js +2 -2
  38. package/dist/src/mutate/opset.d.ts +7 -7
  39. package/dist/src/mutate/plan-update.js +2 -2
  40. package/dist/src/mutate/plan.d.ts +8 -8
  41. package/dist/src/mutate/plan.js +25 -25
  42. package/dist/src/mutate/select.d.ts +4 -4
  43. package/dist/src/mutate/select.js +52 -52
  44. package/dist/src/mutate/sugar.d.ts +10 -10
  45. package/dist/src/mutate/sugar.js +21 -21
  46. package/dist/src/oqx-js/context.d.ts +8 -4
  47. package/dist/src/oqx-js/context.js +206 -189
  48. package/dist/src/oqx-js/names.d.ts +28 -17
  49. package/dist/src/oqx-js/names.js +115 -78
  50. package/dist/src/oqx-js/planner.d.ts +4 -8
  51. package/dist/src/oqx-js/planner.js +6 -10
  52. package/dist/src/oqx-js/root-row.d.ts +6 -5
  53. package/dist/src/oqx-js/root-row.js +15 -16
  54. package/dist/src/oqx-js/run.d.ts +18 -17
  55. package/dist/src/oqx-js/run.js +57 -54
  56. package/dist/src/oqx-js/sql/translate.d.ts +5 -5
  57. package/dist/src/oqx-js/sql/translate.js +162 -161
  58. package/dist/src/oqx-js/store-index.d.ts +1 -1
  59. package/dist/src/oqx-js/store-index.js +28 -30
  60. package/dist/src/search/resolve.d.ts +2 -2
  61. package/dist/src/search/resolve.js +1 -1
  62. package/dist/src/search/rrf.js +2 -2
  63. package/dist/src/search/text.d.ts +5 -5
  64. package/dist/src/search/text.js +2 -2
  65. package/dist/src/spec-versions.d.ts +6 -6
  66. package/dist/src/spec-versions.js +6 -6
  67. package/dist/src/surface-paths.d.ts +18 -17
  68. package/dist/src/surface-paths.js +23 -21
  69. package/dist/src/sync/admin.d.ts +2 -2
  70. package/dist/src/sync/admin.js +1 -1
  71. package/package.json +1 -1
@@ -2,14 +2,14 @@ import type { Database } from "better-sqlite3";
2
2
  import type { Store } from "../store/store.js";
3
3
  import { type PropertySource } from "../store/properties.js";
4
4
  export interface DocsReadResult {
5
- id: string;
5
+ _id: string;
6
6
  /** The reference form (`/a/b.md`, spec/surface §1 "Paths"). */
7
- path: string;
7
+ _path: string;
8
8
  rev: string | null;
9
9
  /** Properties grouped by source: { frontmatter, inline, computed }. */
10
10
  properties: Record<string, Record<string, unknown>>;
11
11
  /** The reconstructed file bytes (spec/store §6.1) — the document's `raw`. */
12
- raw: string;
12
+ _raw: string;
13
13
  /** Block ids in document order, for follow-up edits (include_ids). */
14
14
  ids?: string[];
15
15
  /**
@@ -46,12 +46,13 @@ export declare function currentFrontmatterRaw(db: Database, revId: string): stri
46
46
  */
47
47
  export declare function reconstructContent(db: Database, docId: string): string | null;
48
48
  /**
49
- * The surface's `properties` (spec/surface §2, 3.0): the `spec/properties` §5
49
+ * The surface's `properties` (spec/surface §2, 5.0): the `spec/properties` §5
50
50
  * grouped shape with the computed group's keys spelled as the attributes that
51
51
  * read them — the store writes the computed rows under `$title`/`$tags`
52
- * (spec/properties §3.3, unchanged), the surface has no `$` names, so
53
- * `properties.computed` is `{ title, tags }`. Authored groups are untouched (an
54
- * authored `$x:` key keeps its sigil, as `frontmatter.$x` reads it).
52
+ * (spec/properties §3.3, unchanged), the surface has no `$` names and its
53
+ * engine names are `_`-prefixed, so `properties.computed` is `{ _title, _tags }`.
54
+ * Authored groups are untouched (bare keys; an authored `$x:` key keeps its
55
+ * sigil, as `_frontmatter.$x` reads it).
55
56
  */
56
57
  export declare function surfaceProperties(grouped: Record<PropertySource, Record<string, unknown>>): Record<string, Record<string, unknown>>;
57
58
  /** Read a whole document by id: full file bytes + properties. Null if missing. */
@@ -93,13 +94,13 @@ export declare function docsReadMany(store: Store, repoId: string, refs: string[
93
94
  * the CURRENT bag flagged `propertiesAreCurrent: true`).
94
95
  */
95
96
  export interface DocsReadAtResult {
96
- id: string;
97
+ _id: string;
97
98
  /** The path as of that revision, in the reference form. */
98
- path: string;
99
+ _path: string;
99
100
  /** The revision travelled to (the argument, echoed for confirmation). */
100
101
  rev: string;
101
102
  /** The reconstructed file bytes at `rev` — the document's `raw` then. */
102
- raw: string;
103
+ _raw: string;
103
104
  /** True when the reconstructed bytes' sha256 matched the revision's stored
104
105
  * rendered_hash — a byte-faithful round-trip. False flags the fidelity gap
105
106
  * described below (doc-level trivia may differ for an old revision). */
@@ -63,17 +63,18 @@ export function reconstructContent(db, docId) {
63
63
  return out.join("");
64
64
  }
65
65
  /**
66
- * The surface's `properties` (spec/surface §2, 3.0): the `spec/properties` §5
66
+ * The surface's `properties` (spec/surface §2, 5.0): the `spec/properties` §5
67
67
  * grouped shape with the computed group's keys spelled as the attributes that
68
68
  * read them — the store writes the computed rows under `$title`/`$tags`
69
- * (spec/properties §3.3, unchanged), the surface has no `$` names, so
70
- * `properties.computed` is `{ title, tags }`. Authored groups are untouched (an
71
- * authored `$x:` key keeps its sigil, as `frontmatter.$x` reads it).
69
+ * (spec/properties §3.3, unchanged), the surface has no `$` names and its
70
+ * engine names are `_`-prefixed, so `properties.computed` is `{ _title, _tags }`.
71
+ * Authored groups are untouched (bare keys; an authored `$x:` key keeps its
72
+ * sigil, as `_frontmatter.$x` reads it).
72
73
  */
73
74
  export function surfaceProperties(grouped) {
74
75
  const computed = {};
75
76
  for (const [k, v] of Object.entries(grouped.computed))
76
- computed[k.startsWith("$") ? k.slice(1) : k] = v;
77
+ computed[k.startsWith("$") ? `_${k.slice(1)}` : k] = v;
77
78
  return { frontmatter: grouped.frontmatter, inline: grouped.inline, computed };
78
79
  }
79
80
  /** Read a whole document by id: full file bytes + properties. Null if missing. */
@@ -85,11 +86,11 @@ export function docsRead(store, docId, opts = {}) {
85
86
  if (content === null)
86
87
  return null;
87
88
  const result = {
88
- id: info.docId,
89
- path: referencePath(info.path),
89
+ _id: info.docId,
90
+ _path: referencePath(info.path),
90
91
  rev: info.currentRev,
91
92
  properties: surfaceProperties(docPropertiesGrouped(store.db, docId)),
92
- raw: content,
93
+ _raw: content,
93
94
  };
94
95
  if (opts.includeIds) {
95
96
  const ids = [];
@@ -231,11 +232,11 @@ export function readDocumentAtRevision(store, docId, revId) {
231
232
  const content = out.join("");
232
233
  const renderedHashMatch = sha256(content).equals(rev.rendered_hash);
233
234
  return {
234
- id: docId,
235
+ _id: docId,
235
236
  // The revision records the path as of that revision (docs may be moved).
236
- path: referencePath(rev.path),
237
+ _path: referencePath(rev.path),
237
238
  rev: revId,
238
- raw: content,
239
+ _raw: content,
239
240
  renderedHashMatch,
240
241
  };
241
242
  }
@@ -1,20 +1,20 @@
1
1
  import type { Store } from "../store/store.js";
2
2
  export type Resolution = "skeleton" | "outline" | "text" | "raw" | "full";
3
3
  export interface GetNode {
4
- id: string;
5
- type: string;
4
+ _id: string;
5
+ _type: string;
6
6
  label?: string;
7
- text?: string;
8
- /** The block's raw source bytes (`raw` / `full`). */
9
- raw?: string;
7
+ _text?: string;
8
+ /** The block's raw source bytes (`raw` / `full` resolutions). */
9
+ _raw?: string;
10
10
  /** The block's raw_hash (hex) — the value update/split expect as `expect.content_hash`. */
11
- hash?: string;
12
- attrs?: Record<string, unknown>;
13
- ordinal?: number;
14
- depth?: number;
11
+ _hash?: string;
12
+ _attrs?: Record<string, unknown>;
13
+ _ordinal?: number;
14
+ _depth?: number;
15
15
  /** The parent block's id, null at the top level (`full`). */
16
16
  parent_id?: string | null;
17
- children?: GetNode[];
17
+ _children?: GetNode[];
18
18
  }
19
19
  export interface NodesGetOptions {
20
20
  resolution?: Resolution;
@@ -4,7 +4,7 @@ function firstWords(text, n) {
4
4
  return w.length <= n ? w.join(" ") : w.slice(0, n).join(" ") + "…";
5
5
  }
6
6
  function project(store, node, resolution, includeChildren) {
7
- const out = { id: node.blockId, type: node.type };
7
+ const out = { _id: node.blockId, _type: node.type };
8
8
  switch (resolution) {
9
9
  case "skeleton":
10
10
  out.label = node.type === "heading" ? node.text : node.type;
@@ -13,24 +13,24 @@ function project(store, node, resolution, includeChildren) {
13
13
  out.label = firstWords(node.text, 10);
14
14
  break;
15
15
  case "text":
16
- out.text = node.text;
16
+ out._text = node.text;
17
17
  break;
18
18
  case "raw":
19
- out.raw = blockRaw(store, node.rawHashHex);
20
- out.hash = node.rawHashHex;
19
+ out._raw = blockRaw(store, node.rawHashHex);
20
+ out._hash = node.rawHashHex;
21
21
  break;
22
22
  case "full":
23
- out.raw = blockRaw(store, node.rawHashHex);
24
- out.hash = node.rawHashHex;
25
- out.text = node.text;
26
- out.attrs = node.attrs;
27
- out.ordinal = node.ordinal;
28
- out.depth = node.depth;
23
+ out._raw = blockRaw(store, node.rawHashHex);
24
+ out._hash = node.rawHashHex;
25
+ out._text = node.text;
26
+ out._attrs = node.attrs;
27
+ out._ordinal = node.ordinal;
28
+ out._depth = node.depth;
29
29
  out.parent_id = node.parentBlock;
30
30
  break;
31
31
  }
32
32
  if (includeChildren && node.children.length > 0) {
33
- out.children = node.children.map((c) => project(store, c, resolution, true));
33
+ out._children = node.children.map((c) => project(store, c, resolution, true));
34
34
  }
35
35
  return out;
36
36
  }
@@ -1,11 +1,11 @@
1
1
  import type { Store } from "../store/store.js";
2
2
  /** One row of the `ls` / docs_list surface: a live document with its block
3
3
  * count and last-commit timestamp, under the row attributes' own names
4
- * (spec/surface §1.2, 3.0: `id`, `path`, `updated_at`). */
4
+ * (spec/surface §1.2, 5.0: `_id`, `_path`, `_updated_at`). */
5
5
  export interface DocListRow {
6
- id: string;
7
- path: string;
8
- updated_at: string | null;
6
+ _id: string;
7
+ _path: string;
8
+ _updated_at: string | null;
9
9
  blocks: number;
10
10
  }
11
11
  /** A page of the docs_list surface. Honors the uniform list contract (mcp-api
@@ -34,18 +34,21 @@ export declare const DOCS_LIST_DEFAULT_LIMIT = 200;
34
34
  * implementation for both surfaces). For a directory-aware summary use
35
35
  * `docsTree`. */
36
36
  export declare function docsList(store: Store, repoId: string, opts?: DocListOptions): DocListPage;
37
- /** One entry of the docs_tree surface: a live document (`kind: "doc"`, with
38
- * its `id`) or a collapsed directory (`kind: "dir"`, `path` ends in `/`, no
39
- * id) at the requested depth. `docs`/`blocks` are totals under the entry (a
40
- * doc counts 1 doc); `updated_at` is the latest last-commit timestamp under
41
- * it (spec/surface §1.2, 3.0 names). */
37
+ /** One entry of the docs_tree surface (spec/surface §2, 5.1): a directory
38
+ * within `depth` segments under the prefix (`_path` ends in `/`, no `_id`;
39
+ * `docs`/`blocks` are the totals beneath it) or — only with `files: true` — a
40
+ * live document shallow enough to be listed (`_id`, `_path`, `blocks`; no
41
+ * `docs`: a document is one). `_updated_at` (the latest last-commit timestamp
42
+ * under the entry) rides only with `updated: true`. Through 5.0 every entry
43
+ * carried `kind: "dir" | "doc"`, `docs` and `_updated_at` and the documents
44
+ * were always listed — the 29 kB page the 2026-10-21 agent-bench replayed on
45
+ * every turn; the directory rollup is the orientation call now. */
42
46
  export interface DocTreeEntry {
43
- id?: string;
44
- path: string;
45
- kind: "dir" | "doc";
46
- docs: number;
47
+ _id?: string;
48
+ _path: string;
49
+ docs?: number;
47
50
  blocks: number;
48
- updated_at: string | null;
51
+ _updated_at?: string | null;
49
52
  }
50
53
  export interface DocTreePage {
51
54
  /** The normalized directory prefix the tree was taken under, in the
@@ -57,6 +60,9 @@ export interface DocTreePage {
57
60
  docs: number;
58
61
  blocks: number;
59
62
  };
63
+ /** The documents directly in `prefix` (one segment below it) — counted
64
+ * whether or not `files` lists them. */
65
+ files_here: number;
60
66
  entries: DocTreeEntry[];
61
67
  truncated: boolean;
62
68
  cursor: string | null;
@@ -65,9 +71,13 @@ export interface DocTreeOptions {
65
71
  /** Directory to take the tree under (`projects` or `projects/`; leading `/`
66
72
  * tolerated). Omit for the repo root. */
67
73
  path?: string;
68
- /** How many path segments below `prefix` to expand before collapsing
69
- * (default 1 = immediate children, like `tree -L 1`). */
74
+ /** How many path segments below `prefix` to descend (default 1 = the
75
+ * immediate subdirectories, like `tree -L 1 -d`). */
70
76
  depth?: number;
77
+ /** List the documents within `depth` segments too, as lean rows. */
78
+ files?: boolean;
79
+ /** Add `_updated_at` to every entry. */
80
+ updated?: boolean;
71
81
  limit?: number;
72
82
  cursor?: string | null;
73
83
  budgetTokens?: number;
@@ -76,14 +86,17 @@ export declare const DOCS_TREE_DEFAULT_LIMIT = 200;
76
86
  /** Normalize a tree prefix to its storage form: no leading `/`, and either
77
87
  * empty or ending in `/` (either path form is accepted). */
78
88
  export declare function normalizeTreePrefix(path: string | undefined): string;
79
- /** The path-separator-aware orientation read (`tree -L depth` + `du`): every
80
- * live doc under `path` is collapsed at `depth` segments into one entry per
81
- * directory (with doc/block totals + latest ts) or listed as a doc when it is
82
- * shallow enough. Entries are ordered by path; dirs carry a trailing `/`. The
83
- * result is paged under the same limit/cursor/budget contract as docsList,
84
- * and always carries `total` for the whole prefix so an agent sees the size of
85
- * what it is looking at even when the page is cut. Backs the `docs_tree` MCP
86
- * tool. */
89
+ /** The path-separator-aware orientation read (`tree -L depth -d` + `du`):
90
+ * every directory within `depth` segments under `path` is one entry —
91
+ * `{ _path (rooted, ending "/"), docs, blocks }`, the totals over everything
92
+ * beneath it, a nested directory listed after its parent in path order — and
93
+ * the documents directly in `path` are counted as `files_here`. With `files:
94
+ * true` the documents within `depth` segments are listed too, as `{ _id,
95
+ * _path, blocks }`; with `updated: true` every entry gains `_updated_at` (a
96
+ * directory's is the latest beneath it). The result is paged under the same
97
+ * limit/cursor/budget contract as docsList, and always carries `total` and
98
+ * `files_here` for the whole prefix, so an agent sees the size of what it is
99
+ * looking at even when the page is cut. Backs the `docs_tree` MCP tool. */
87
100
  export declare function docsTree(store: Store, repoId: string, opts?: DocTreeOptions): DocTreePage;
88
101
  export interface BlockNode {
89
102
  blockId: string;
@@ -32,7 +32,7 @@ function liveDocRows(store, repoId, like, after, limit) {
32
32
  .all(...params);
33
33
  // The surface speaks the reference form (spec/surface §1 "Paths"): rooted on
34
34
  // the way out, the storage form never leaves this module.
35
- return rows.map((r) => ({ id: r.id, path: referencePath(r.path), updated_at: r.ts, blocks: r.blocks }));
35
+ return rows.map((r) => ({ _id: r.id, _path: referencePath(r.path), _updated_at: r.ts, blocks: r.blocks }));
36
36
  }
37
37
  // Path-keyset cursors for the path-ordered list surfaces: the shared kernel
38
38
  // encoding (core/cursor.ts) with a one-part tuple holding the row's (rooted)
@@ -64,7 +64,7 @@ function pagePathOrdered(rows, limit, budgetTokens, moreBeyond) {
64
64
  items.push(row);
65
65
  }
66
66
  const last = items[items.length - 1];
67
- return { items, truncated, cursor: truncated && last ? encodePathCursor(last.path) : null };
67
+ return { items, truncated, cursor: truncated && last ? encodePathCursor(last._path) : null };
68
68
  }
69
69
  /** List a repo's live documents (path, block count, last-commit ts), ordered by
70
70
  * path, as a page: `{ items, truncated, cursor }`. `pathGlob` is a simple LIKE
@@ -88,53 +88,66 @@ export function normalizeTreePrefix(path) {
88
88
  const trimmed = storagePath(path ?? "").replace(/\/+$/, "");
89
89
  return trimmed === "" ? "" : trimmed + "/";
90
90
  }
91
- /** The path-separator-aware orientation read (`tree -L depth` + `du`): every
92
- * live doc under `path` is collapsed at `depth` segments into one entry per
93
- * directory (with doc/block totals + latest ts) or listed as a doc when it is
94
- * shallow enough. Entries are ordered by path; dirs carry a trailing `/`. The
95
- * result is paged under the same limit/cursor/budget contract as docsList,
96
- * and always carries `total` for the whole prefix so an agent sees the size of
97
- * what it is looking at even when the page is cut. Backs the `docs_tree` MCP
98
- * tool. */
91
+ /** The path-separator-aware orientation read (`tree -L depth -d` + `du`):
92
+ * every directory within `depth` segments under `path` is one entry —
93
+ * `{ _path (rooted, ending "/"), docs, blocks }`, the totals over everything
94
+ * beneath it, a nested directory listed after its parent in path order — and
95
+ * the documents directly in `path` are counted as `files_here`. With `files:
96
+ * true` the documents within `depth` segments are listed too, as `{ _id,
97
+ * _path, blocks }`; with `updated: true` every entry gains `_updated_at` (a
98
+ * directory's is the latest beneath it). The result is paged under the same
99
+ * limit/cursor/budget contract as docsList, and always carries `total` and
100
+ * `files_here` for the whole prefix, so an agent sees the size of what it is
101
+ * looking at even when the page is cut. Backs the `docs_tree` MCP tool. */
99
102
  export function docsTree(store, repoId, opts = {}) {
100
103
  const prefix = normalizeTreePrefix(opts.path);
101
104
  const depth = Math.max(1, Math.floor(opts.depth ?? 1));
102
105
  const limit = Math.max(1, Math.floor(opts.limit ?? DOCS_TREE_DEFAULT_LIMIT));
106
+ const files = opts.files === true;
107
+ const updated = opts.updated === true;
103
108
  const like = prefix === "" ? "%" : globToLike(prefix) + "%";
104
109
  const rows = liveDocRows(store, repoId, like, null, null);
105
- // `liveDocRows` hands out reference paths; the tree is built over the storage
106
- // form and rooted again on the way out, so the entries, the prefix and the
107
- // cursor all speak the reference form.
108
110
  const byPath = new Map();
109
111
  const total = { docs: 0, blocks: 0 };
112
+ let filesHere = 0;
110
113
  for (const row of rows) {
111
- const storage = storagePath(row.path);
114
+ const storage = storagePath(row._path);
112
115
  total.docs += 1;
113
116
  total.blocks += row.blocks;
114
117
  const segs = storage.slice(prefix.length).split("/");
115
- if (segs.length <= depth) {
116
- byPath.set(storage, { id: row.id, path: row.path, kind: "doc", docs: 1, blocks: row.blocks, updated_at: row.updated_at });
117
- continue;
118
- }
119
- const dir = referencePath(prefix + segs.slice(0, depth).join("/") + "/");
120
- const cur = byPath.get(dir);
121
- if (cur) {
122
- cur.docs += 1;
123
- cur.blocks += row.blocks;
124
- if (row.updated_at !== null && (cur.updated_at === null || row.updated_at > cur.updated_at))
125
- cur.updated_at = row.updated_at;
126
- }
127
- else {
128
- byPath.set(dir, { path: dir, kind: "dir", docs: 1, blocks: row.blocks, updated_at: row.updated_at });
118
+ if (segs.length === 1)
119
+ filesHere += 1;
120
+ // Every directory above the document within `depth` segments rolls it up.
121
+ for (let d = 1; d < segs.length && d <= depth; d++) {
122
+ const dir = referencePath(prefix + segs.slice(0, d).join("/") + "/");
123
+ const cur = byPath.get(dir);
124
+ if (cur) {
125
+ cur.docs += 1;
126
+ cur.blocks += row.blocks;
127
+ if (row._updated_at !== null && (cur.ts === null || row._updated_at > cur.ts))
128
+ cur.ts = row._updated_at;
129
+ }
130
+ else {
131
+ byPath.set(dir, { _path: dir, dir: true, docs: 1, blocks: row.blocks, ts: row._updated_at });
132
+ }
129
133
  }
134
+ if (files && segs.length <= depth)
135
+ byPath.set(row._path, { _id: row._id, _path: row._path, dir: false, docs: 1, blocks: row.blocks, ts: row._updated_at });
130
136
  }
131
- let entries = [...byPath.values()].sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));
137
+ let accs = [...byPath.values()].sort((a, b) => (a._path < b._path ? -1 : a._path > b._path ? 1 : 0));
132
138
  if (opts.cursor) {
133
139
  const after = decodeListCursor(opts.cursor);
134
- entries = entries.filter((e) => e.path > after);
140
+ accs = accs.filter((e) => e._path > after);
135
141
  }
142
+ const entries = accs.map((a) => ({
143
+ ...(a.dir ? {} : { _id: a._id }),
144
+ _path: a._path,
145
+ ...(a.dir ? { docs: a.docs } : {}),
146
+ blocks: a.blocks,
147
+ ...(updated ? { _updated_at: a.ts } : {}),
148
+ }));
136
149
  const { items, truncated, cursor } = pagePathOrdered(entries, limit, opts.budgetTokens, false);
137
- return { prefix: referencePath(prefix), depth, total, entries: items, truncated, cursor };
150
+ return { prefix: referencePath(prefix), depth, total, files_here: filesHere, entries: items, truncated, cursor };
138
151
  }
139
152
  function toNode(r) {
140
153
  return {
@@ -18,11 +18,11 @@ export declare class RevisionNotFound extends Error {
18
18
  readonly rev: string;
19
19
  constructor(docId: string, rev: string);
20
20
  }
21
- /** One block-grain diff entry: the block's `id` (spec/surface §1.2, 3.0) and
21
+ /** One block-grain diff entry: the block's `_id` (spec/surface §1.2, 5.0) and
22
22
  * its raw `before`/`after` as applicable. */
23
23
  export interface DiffEntry {
24
24
  kind: "added" | "removed" | "changed" | "unchanged";
25
- id: string;
25
+ _id: string;
26
26
  before?: string;
27
27
  after?: string;
28
28
  }
@@ -40,8 +40,8 @@ export declare function diffBlocks(store: Store, docId: string, fromRev: string,
40
40
  export declare function diffUnified(store: Store, docId: string, fromRev: string, toRev: string): string;
41
41
  /** The `docs_diff_unified` tool's result (spec/surface §3): the resolved revision pair and the diff. */
42
42
  export interface DocDiffUnified {
43
- id: string;
44
- path: string;
43
+ _id: string;
44
+ _path: string;
45
45
  from: string;
46
46
  to: string;
47
47
  diff: string;
@@ -84,14 +84,14 @@ export interface CommitDigest {
84
84
  actor: string | null;
85
85
  summary: string;
86
86
  /** The revisions the commit wrote, each the document's row attributes
87
- * (spec/surface §1.2, 3.0): `id`, `path` as of the commit, `hash` (hex of
87
+ * (spec/surface §1.2, 5.0): `_id`, `_path` as of the commit, `_hash` (hex of
88
88
  * the revision's rendered_hash) — the hash lets a puller decide "changed vs
89
89
  * echo" without a follow-up docs_read/docs_history (ADR-014 §4.2). The same
90
90
  * three keys an `apply` revision carries. */
91
91
  revisions: {
92
- id: string;
93
- path: string;
94
- hash: string;
92
+ _id: string;
93
+ _path: string;
94
+ _hash: string;
95
95
  }[];
96
96
  }
97
97
  /** changes_since: commit digests after a cursor (repo commit seq) — the change
@@ -110,26 +110,29 @@ export declare function changesSince(store: Store, repoId: string, opts?: {
110
110
  truncated: boolean;
111
111
  head: number;
112
112
  };
113
- /** One stored version (revision) of a document. */
113
+ /** One stored version (revision) of a document — a `revisions` row under the
114
+ * row's own names (spec/surface §1.2, 5.0): `_rev _seq _commit _ts _origin
115
+ * _actor _hash _is_current _archived` (through 4.11 `rev seq commit
116
+ * updated_at origin actor hash isCurrent archived`). */
114
117
  export interface DocVersion {
115
- rev: string;
116
- seq: number;
117
- commit: string;
118
- /** the commit's ts — the document's `updated_at` as of this version (spec/surface §1.2, 3.0). */
119
- updated_at: string;
120
- origin: string;
121
- actor: string | null;
122
- /** hex of the revision's rendered_hash — the document's `hash` as of this version; spot no-op vs real change, feed into docs_read_at/diff. */
123
- hash: string;
118
+ _rev: string;
119
+ _seq: number;
120
+ _commit: string;
121
+ /** the commit's `ts` — the `revisions` row's `_ts` (spec/surface §1.2, 5.0; `updated_at` through 4.11). */
122
+ _ts: string;
123
+ _origin: string;
124
+ _actor: string | null;
125
+ /** hex of the revision's rendered_hash — the row's `_hash` — the document's `_hash` as of this version; spot no-op vs real change, feed into docs_read_at/diff. */
126
+ _hash: string;
124
127
  /** true when this rev == the doc's current_rev. */
125
- isCurrent: boolean;
128
+ _is_current: boolean;
126
129
  /** true when the revision's bodies live in an archive segment (spec/store §7 "Retention", 15.0; `docs_read { at }` reads through it when attached). */
127
- archived: boolean;
130
+ _archived: boolean;
128
131
  }
129
132
  /** A matching document with its ordered version history. */
130
133
  export interface DocVersions {
131
- id: string;
132
- path: string;
134
+ _id: string;
135
+ _path: string;
133
136
  deleted: boolean;
134
137
  currentRev: string | null;
135
138
  versions: DocVersion[];
@@ -39,13 +39,13 @@ export function diffBlocks(store, docId, fromRev, toRev) {
39
39
  const entries = [];
40
40
  for (const [id, raw] of before) {
41
41
  if (!after.has(id))
42
- entries.push({ kind: "removed", id: id, before: raw });
42
+ entries.push({ kind: "removed", _id: id, before: raw });
43
43
  else if (after.get(id) !== raw)
44
- entries.push({ kind: "changed", id: id, before: raw, after: after.get(id) });
44
+ entries.push({ kind: "changed", _id: id, before: raw, after: after.get(id) });
45
45
  }
46
46
  for (const [id, raw] of after)
47
47
  if (!before.has(id))
48
- entries.push({ kind: "added", id: id, after: raw });
48
+ entries.push({ kind: "added", _id: id, after: raw });
49
49
  return entries;
50
50
  }
51
51
  // Reconstruct the (block id → raw) map for a document at a revision by walking
@@ -96,7 +96,7 @@ export function diffUnified(store, docId, fromRev, toRev) {
96
96
  const at = readDocumentAtRevision(store, docId, rev);
97
97
  if (!at)
98
98
  throw new RevisionNotFound(docId, rev);
99
- return at.raw;
99
+ return at._raw;
100
100
  };
101
101
  return unifiedDiff(rendered(fromRev), rendered(toRev));
102
102
  }
@@ -117,7 +117,7 @@ export function docDiffUnified(store, docId, opts = {}) {
117
117
  if (!to || !from)
118
118
  throw new EngineError("target_missing", `no revisions to diff for ${opts.label ?? docId}`);
119
119
  const path = store.db.prepare("SELECT path FROM docs WHERE doc_id = ?").get(docId)?.path;
120
- return { id: docId, path: path === undefined ? "" : referencePath(path), from, to, diff: diffUnified(store, docId, from, to) };
120
+ return { _id: docId, _path: path === undefined ? "" : referencePath(path), from, to, diff: diffUnified(store, docId, from, to) };
121
121
  }
122
122
  // ---- unified diff (spec/surface §3) ------------------------------------------
123
123
  // A line-grain unified diff with a deterministic Myers script, so both engines
@@ -219,7 +219,7 @@ export function changesSince(store, repoId, opts = {}) {
219
219
  // Every path in a digest — the revisions', a deletion's, a move's two, and
220
220
  // the summary's — is the reference form (spec/surface §1 "Paths").
221
221
  const digests = page.map((c) => {
222
- const revs = store.db.prepare("SELECT r.doc_id AS doc, r.path AS path, r.rendered_hash AS rendered_hash FROM revisions r WHERE r.commit_id = ?").all(c.commit_id).map((r) => ({ id: r.doc, path: referencePath(r.path), hash: r.rendered_hash.toString("hex") }));
222
+ const revs = store.db.prepare("SELECT r.doc_id AS doc, r.path AS path, r.rendered_hash AS rendered_hash FROM revisions r WHERE r.commit_id = ?").all(c.commit_id).map((r) => ({ _id: r.doc, _path: referencePath(r.path), _hash: r.rendered_hash.toString("hex") }));
223
223
  const dispCounts = store.db.prepare("SELECT kind, count(*) n FROM dispositions WHERE commit_id = ? GROUP BY kind").all(c.commit_id);
224
224
  // A deletion writes no revision: the tombstone names the commit
225
225
  // (`docs.deleted_commit`). A move names itself by its reason (`move <from>
@@ -295,17 +295,17 @@ export function docHistory(store, repoId, opts = {}) {
295
295
  const docs = page.map((d) => {
296
296
  const rows = revStmt.all(d.doc_id);
297
297
  const versions = rows.map((r) => ({
298
- rev: r.rev,
299
- seq: r.seq,
300
- commit: r.commitId,
301
- updated_at: r.ts,
302
- origin: r.origin,
303
- actor: r.actor,
304
- hash: r.rendered_hash.toString("hex"),
305
- isCurrent: r.rev === d.current_rev,
306
- archived: r.archive !== null,
298
+ _rev: r.rev,
299
+ _seq: r.seq,
300
+ _commit: r.commitId,
301
+ _ts: r.ts,
302
+ _origin: r.origin,
303
+ _actor: r.actor,
304
+ _hash: r.rendered_hash.toString("hex"),
305
+ _is_current: r.rev === d.current_rev,
306
+ _archived: r.archive !== null,
307
307
  }));
308
- return { id: d.doc_id, path: referencePath(d.path), deleted: d.deleted_commit !== null, currentRev: d.current_rev, versions };
308
+ return { _id: d.doc_id, _path: referencePath(d.path), deleted: d.deleted_commit !== null, currentRev: d.current_rev, versions };
309
309
  });
310
310
  return { docs, truncated };
311
311
  }
@@ -341,7 +341,7 @@ function parseMove(reason) {
341
341
  * is unpinned by spec/surface).
342
342
  */
343
343
  function renderSummary(origin, actor, revs, disp, extra = { deleted: [], moved: null }) {
344
- let subject = revs.map((r) => r.path).join(", ");
344
+ let subject = revs.map((r) => r._path).join(", ");
345
345
  if (extra.moved)
346
346
  subject = `moved ${extra.moved.from} → ${extra.moved.to}${revs.length > 0 ? `; ${subject}` : ""}`;
347
347
  else if (revs.length === 0 && extra.deleted.length > 0)
@@ -1,17 +1,17 @@
1
1
  import type { Store } from "../core/store/store.js";
2
2
  export interface InboundLink {
3
3
  /** source document id */
4
- doc_id: string;
5
- /** source document repo-relative path (the surface roots it as `path`) */
6
- path: string;
4
+ _doc_id: string;
5
+ /** source document repo-relative path (the surface roots it as `_path`) */
6
+ _path: string;
7
7
  /** source block id; null for a frontmatter-level edge */
8
- block_id: string | null;
8
+ _block_id: string | null;
9
9
  /** the link's destination path as written (e.g. "/old.md", "../old.md", "old.md") */
10
10
  target: string;
11
11
  /** #heading / ^ref fragment, if any */
12
- anchor: string | null;
12
+ _anchor: string | null;
13
13
  /** frontmatter key or inline-field key for typed edges */
14
- field?: string;
14
+ _field?: string;
15
15
  }
16
16
  /**
17
17
  * Every link occurrence in the repo whose destination resolves to `docPath`,
@@ -19,7 +19,7 @@ export interface InboundLink {
19
19
  * re-scanned from the block raw so `target`/`anchor` are as written (and so a
20
20
  * self-doc pure-fragment link `#H`, which also has dst_node == docId, is NOT
21
21
  * reported — it does not depend on the path). Frontmatter edges are reported
22
- * with `block_id: null`, `target: docPath`, and their `field`.
22
+ * with `_block_id: null`, `target: docPath`, and their `_field`.
23
23
  */
24
24
  export declare function inboundLinksTo(store: Store, repoId: string, docId: string, docPath: string): InboundLink[];
25
25
  /**
@@ -17,7 +17,7 @@ function resolvesTo(dest, srcDir, docPath) {
17
17
  * re-scanned from the block raw so `target`/`anchor` are as written (and so a
18
18
  * self-doc pure-fragment link `#H`, which also has dst_node == docId, is NOT
19
19
  * reported — it does not depend on the path). Frontmatter edges are reported
20
- * with `block_id: null`, `target: docPath`, and their `field`.
20
+ * with `_block_id: null`, `target: docPath`, and their `_field`.
21
21
  */
22
22
  export function inboundLinksTo(store, repoId, docId, docPath) {
23
23
  const rows = store.db
@@ -30,7 +30,7 @@ export function inboundLinksTo(store, repoId, docId, docPath) {
30
30
  const seenBlocks = new Set();
31
31
  for (const r of rows) {
32
32
  if (r.srcBlock === null) {
33
- out.push({ doc_id: r.srcDoc, path: r.srcPath, block_id: null, target: docPath, anchor: null, ...(r.srcField ? { field: r.srcField } : {}) });
33
+ out.push({ _doc_id: r.srcDoc, _path: r.srcPath, _block_id: null, target: docPath, _anchor: null, ...(r.srcField ? { _field: r.srcField } : {}) });
34
34
  continue;
35
35
  }
36
36
  if (seenBlocks.has(r.srcBlock))
@@ -46,12 +46,12 @@ export function inboundLinksTo(store, repoId, docId, docPath) {
46
46
  if (e.dstKind === "external" || !resolvesTo(e.target, srcDir, docPath))
47
47
  continue;
48
48
  out.push({
49
- doc_id: r.srcDoc,
50
- path: r.srcPath,
51
- block_id: r.srcBlock,
49
+ _doc_id: r.srcDoc,
50
+ _path: r.srcPath,
51
+ _block_id: r.srcBlock,
52
52
  target: e.target,
53
- anchor: e.anchor,
54
- ...(e.srcField ? { field: e.srcField } : {}),
53
+ _anchor: e.anchor,
54
+ ...(e.srcField ? { _field: e.srcField } : {}),
55
55
  });
56
56
  }
57
57
  }