omgbase 0.24.1 → 0.26.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -46,11 +46,11 @@ decisions.md 5 blocks 0s ago
46
46
  $ omg find sqlite
47
47
  b_v32ar6f decisions.md#heading[3] ADR-002 Embedded SQLite
48
48
 
49
- $ omg q 'from blocks where type == "task" && !attrs.checked'
49
+ $ omg q 'from blocks where _type == "task" && !checked'
50
50
  b_24nr0y0 README.md
51
51
  b_36p2zw1 README.md
52
52
 
53
- $ omg q 'blocks count { where type == "task" && !attrs.checked }'
53
+ $ omg q 'blocks count { where _type == "task" && !checked }'
54
54
  2
55
55
 
56
56
  $ omg outline README.md
@@ -72,7 +72,7 @@ ADR-002 Embedded SQLite
72
72
  `omg cat <path>` prints a document's exact bytes (frontmatter and all); given a block id it prints that block. Edits are structural mutations that update the file and the database together. The pipe is the changeset boundary — `--ids` emits block ids, `done -` reads them from stdin and completes them in one commit:
73
73
 
74
74
  ```console
75
- $ omg q 'from blocks where type == "task" && !attrs.checked' --ids | omg done -
75
+ $ omg q 'from blocks where _type == "task" && !checked' --ids | omg done -
76
76
  ok committed · 1 document touched
77
77
  b_24nr0y0
78
78
  b_36p2zw1
@@ -182,7 +182,7 @@ The global `--server <cmd|url>` flag runs the same command against a **remote en
182
182
  Global flags are recognized anywhere on the line: `-C <dir>`, `--repo <slug>`, `--json`, `--jsonl`, `--ids`, `--stale`, `--no-color`, `--dry-run`, `--server <cmd|url>`, `-H <header>`, `--help`/`-h`, `--version`/`-V`.
183
183
 
184
184
  - Human output (default) is colorized on a TTY; `--no-color`, `NO_COLOR`, or a pipe degrades to plain text.
185
- - Query hits (`omg q`, `omg run`) print as `<id> <path>` lines. When the query projects (`select …`), they print as an aligned table instead: a dim header row, the id first, then the path (unless the projection itself selects `path`), then the projected columns in `select` order. Strings and numbers are verbatim, an absent field is an empty cell, and a nested list/record is compact JSON clipped at 60 characters with `…`; `--jsonl` has the full values.
185
+ - Query hits (`omg q`, `omg run`) print as `<_id> <_path>` lines (the engine names are `_`-prefixed; a bare name is an authored key). When the query projects (`select …`), they print as an aligned table instead: a dim header row (`_id`, `_path`, then the projected keys), the id first, then the path (a projected `_id`/`_path` collapses onto the envelope), then the projected columns in `select` order. Strings and numbers are verbatim, an absent field is an empty cell, and a nested list/record is compact JSON clipped at 60 characters with `…`; `--jsonl` has the full values.
186
186
  - `--json` prints the library's result object verbatim as one JSON document (the same shape the MCP tools return). `--jsonl` flattens list results to one object per line; `--ids` prints bare ids/paths, one per line — pipe fuel for `omg cat -`, `omg show -`, `omg done -`.
187
187
  - `--dry-run` is global across every mutator: full validation and render, diffs printed, nothing committed.
188
188
  - Errors always go to stderr: `error[<code>]: <message>` plus any conflict payload, or the typed error object as JSON with `--json`.
@@ -1,4 +1,4 @@
1
1
  {
2
- "commit": "fd89bb5",
3
- "built": "2026-10-19T05:50:09.952Z"
2
+ "commit": "2637bbd",
3
+ "built": "2026-10-21T19:17:55.419Z"
4
4
  }
@@ -52,7 +52,9 @@ export declare function runSugar(cli: Cli, ws: Workspace, repo: RepoRow, sugar:
52
52
  * `refs` are the refs the verb received, refused there when one is a locator.
53
53
  */
54
54
  export declare function runSugarRemote(cli: Cli, tool: string, toolArgs: Record<string, unknown>, effects: () => unknown[] | Promise<unknown[]>, refs?: string[]): Promise<number>;
55
- /** A ref as an effect target for a remote `plan`: a `b_` id is the block, a `d_` id the document, anything else a document path. */
55
+ /** A ref as an effect target for a remote `plan` (spec/mutate §11, 4.0: the address is the
56
+ * row's engine attribute): a `b_` id is the block (`_id`), a `d_` id the document (`_id`),
57
+ * anything else a document path (`_path`). */
56
58
  export declare function targetOf(ref: string): EffectTarget;
57
59
  /**
58
60
  * `apply` given `{ effects }`: plan + execute as one transaction
@@ -162,13 +162,15 @@ export async function runSugarRemote(cli, tool, toolArgs, effects, refs = []) {
162
162
  return renderPlan(cli, await remoteCall(cli, "plan", { effects: await effects(), diffs: true }));
163
163
  return renderReceipt(cli, await remoteSugar(cli, tool, toolArgs, effects, refs));
164
164
  }
165
- /** A ref as an effect target for a remote `plan`: a `b_` id is the block, a `d_` id the document, anything else a document path. */
165
+ /** A ref as an effect target for a remote `plan` (spec/mutate §11, 4.0: the address is the
166
+ * row's engine attribute): a `b_` id is the block (`_id`), a `d_` id the document (`_id`),
167
+ * anything else a document path (`_path`). */
166
168
  export function targetOf(ref) {
167
169
  if (isValidId(ref, "b"))
168
- return { kind: "block", id: ref };
170
+ return { kind: "block", _id: ref };
169
171
  if (isValidId(ref, "d"))
170
- return { kind: "document", id: ref };
171
- return { kind: "document", path: ref };
172
+ return { kind: "document", _id: ref };
173
+ return { kind: "document", _path: ref };
172
174
  }
173
175
  // ---- the Effect layer (ADR-021; spec/cli 3.2 `apply -f { effects }`, `execute`) ----
174
176
  /**
@@ -25,13 +25,15 @@ export declare function isLocator(ref: string): boolean;
25
25
  * usage error there rather than a bogus document path.
26
26
  */
27
27
  export declare function remoteSugar<T>(cli: Cli, tool: string, toolArgs: Record<string, unknown>, effects: () => unknown[] | Promise<unknown[]>, refs?: string[]): Promise<T>;
28
- /** A document ref as the surface's locator (spec/surface §4 "Arguments", 3.0):
29
- * a `d_` id is the `id` argument, anything else the `path` — the two names a
30
- * `docs_*` tool takes (one of them required). */
28
+ /** A document ref as the surface's locator (spec/surface §4 "Arguments", 5.0):
29
+ * a `d_` id is the `_id` argument, anything else the `_path` — the two names a
30
+ * `docs_*` tool takes (one of them required; the bare `id`/`path` are refused
31
+ * there as `filter_invalid`). The same pair is a document effect target's
32
+ * address (`{ kind: "document", _id | _path }`, spec/mutate §11). */
31
33
  export declare function docLocator(ref: string): {
32
- id: string;
34
+ _id: string;
33
35
  } | {
34
- path: string;
36
+ _path: string;
35
37
  };
36
38
  /** The live document a ref names, for a LOCAL verb, or the surface's own
37
39
  * `doc_missing` ("no document with id or path "<ref>"", `data` the locator) —
@@ -85,11 +85,13 @@ export async function remoteSugar(cli, tool, toolArgs, effects, refs = []) {
85
85
  const reason = typeof toolArgs.reason === "string" ? toolArgs.reason : tool;
86
86
  return remoteCall(cli, "apply", { effects: await effects(), reason });
87
87
  }
88
- /** A document ref as the surface's locator (spec/surface §4 "Arguments", 3.0):
89
- * a `d_` id is the `id` argument, anything else the `path` — the two names a
90
- * `docs_*` tool takes (one of them required). */
88
+ /** A document ref as the surface's locator (spec/surface §4 "Arguments", 5.0):
89
+ * a `d_` id is the `_id` argument, anything else the `_path` — the two names a
90
+ * `docs_*` tool takes (one of them required; the bare `id`/`path` are refused
91
+ * there as `filter_invalid`). The same pair is a document effect target's
92
+ * address (`{ kind: "document", _id | _path }`, spec/mutate §11). */
91
93
  export function docLocator(ref) {
92
- return isValidId(ref, "d") ? { id: ref } : { path: ref };
94
+ return isValidId(ref, "d") ? { _id: ref } : { _path: ref };
93
95
  }
94
96
  /** The live document a ref names, for a LOCAL verb, or the surface's own
95
97
  * `doc_missing` ("no document with id or path "<ref>"", `data` the locator) —
@@ -105,7 +107,7 @@ export function requireDoc(store, repoId, ref) {
105
107
  export function requireNode(store, repoId, nodeId) {
106
108
  const row = store.db.prepare("SELECT 1 FROM nodes WHERE node_id = ? AND repo_id = ?").get(nodeId, repoId);
107
109
  if (!row)
108
- throw new EngineErrorLike("block_missing", `no node with id ${JSON.stringify(nodeId)}`, { data: { id: nodeId } });
110
+ throw new EngineErrorLike("block_missing", `no node with id ${JSON.stringify(nodeId)}`, { data: { _id: nodeId } });
109
111
  return nodeId;
110
112
  }
111
113
  /** Close the remote connection if one was opened (idempotent). Called after a
@@ -49,12 +49,12 @@ async function catOne(cli, ref, resolution) {
49
49
  if (cli.flags.server) {
50
50
  const tool = blockReadTool(ref);
51
51
  if (tool) {
52
- const r = await remoteCall(cli, tool, { id: ref, resolution });
53
- return { text: String(r.raw ?? r.text ?? r.label ?? ""), json: r, capture: r };
52
+ const r = await remoteCall(cli, tool, { _id: ref, resolution });
53
+ return { text: String(r._raw ?? r._text ?? r.label ?? ""), json: r, capture: r };
54
54
  }
55
55
  const r = await remoteCall(cli, "docs_read", docLocator(ref));
56
56
  warnDocResolution(cli, ref, resolution);
57
- return { text: String(r.raw ?? ""), json: r, capture: r.raw };
57
+ return { text: String(r._raw ?? ""), json: r, capture: r._raw };
58
58
  }
59
59
  const ws = cli.workspace();
60
60
  const repo = cli.repo(ws);
@@ -72,13 +72,13 @@ async function catOne(cli, ref, resolution) {
72
72
  throw new EngineErrorLike("doc_missing", `no document ${ref}`);
73
73
  warnDocResolution(cli, ref, resolution);
74
74
  // shell: the bytes (a string; @_ only). `--json` is the `docs_read` result.
75
- return { text: res.raw, json: res, capture: res.raw };
75
+ return { text: res._raw, json: res, capture: res._raw };
76
76
  }
77
77
  const node = nodesGet(ws.store, resolved.docId, resolved.blockId, { resolution });
78
78
  if (!node)
79
79
  throw new EngineErrorLike("block_missing", `no block ${ref}`);
80
80
  // shell: the block (single entity)
81
- return { text: node.raw ?? node.text ?? node.label ?? "", json: node, capture: node };
81
+ return { text: node._raw ?? node._text ?? node.label ?? "", json: node, capture: node };
82
82
  }
83
83
  async function runCat(cli, args) {
84
84
  const { values, positionals } = parseArgs({
@@ -3,7 +3,7 @@ import { findDoc, docDiffUnified, diffBlocks } from "@omgbase/core";
3
3
  import { CliUsageError, EngineErrorLike, EXIT_OK, emitMachine, renderHelp } from "../output.js";
4
4
  import { remoteCall, docLocator } from "./_remote.js";
5
5
  // `omg diff <doc>` (11 §5.5) — `docs_diff_unified`: the unified diff, `--json` the
6
- // tool's `{ doc, path, from, to, diff }`. With no revisions: current vs previous
6
+ // tool's `{ _id, _path, from, to, diff }` (spec/surface §3, 5.0). With no revisions: current vs previous
7
7
  // ("what did the last commit do here"). `--blocks` is the block-grain `diff`
8
8
  // tool: added/removed/changed blocks between the same two revisions.
9
9
  async function runDiff(cli, args) {
@@ -54,9 +54,9 @@ async function runDiff(cli, args) {
54
54
  if (cli.flags.server)
55
55
  entries = await remoteCall(cli, "docs_diff", { ...docLocator(ref), from_rev: result.from, to_rev: result.to });
56
56
  else
57
- entries = diffBlocks(cli.workspace().store, result.id, result.from, result.to);
57
+ entries = diffBlocks(cli.workspace().store, result._id, result.from, result.to);
58
58
  if (cli.flags.mode !== "human")
59
- return emitMachine(cli, entries, { items: entries, ids: entries.map((e) => e.id) });
59
+ return emitMachine(cli, entries, { items: entries, ids: entries.map((e) => e._id) });
60
60
  if (entries.length === 0) {
61
61
  io.err(style.dim(" no changes"));
62
62
  return EXIT_OK;
@@ -64,7 +64,7 @@ async function runDiff(cli, args) {
64
64
  for (const e of entries) {
65
65
  const mark = e.kind === "added" ? style.ok("+") : e.kind === "removed" ? style.err("-") : style.warn("~");
66
66
  const preview = (e.after ?? e.before ?? "").split("\n")[0] ?? "";
67
- io.out(`${mark} ${style.id(e.id)} ${preview}`.trimEnd());
67
+ io.out(`${mark} ${style.id(e._id)} ${preview}`.trimEnd());
68
68
  }
69
69
  return EXIT_OK;
70
70
  }
Binary file
@@ -38,10 +38,10 @@ function runEdit(cli, args) {
38
38
  const repo = cli.repo(ws);
39
39
  const resolved = resolveRef(ws.store, repo.repoId, ref);
40
40
  if (!resolved || resolved.kind !== "block")
41
- throw new EngineErrorLike("block_missing", `no block with id ${JSON.stringify(ref)}`, { data: { id: ref } });
41
+ throw new EngineErrorLike("block_missing", `no block with id ${JSON.stringify(ref)}`, { data: { _id: ref } });
42
42
  const blockId = resolved.blockId;
43
43
  const node = nodesGet(ws.store, resolved.docId, blockId, { resolution: "raw" });
44
- const before = node?.raw ?? "";
44
+ const before = node?._raw ?? "";
45
45
  const preHash = rawHashOf(ws, blockId); // pin BEFORE the editor opens
46
46
  const editor = process.env.OMG_EDITOR ?? process.env.VISUAL ?? process.env.EDITOR;
47
47
  if (!editor)
@@ -3,8 +3,9 @@ import { resolve as resolveThing, surfaceResolveHits } from "@omgbase/core";
3
3
  import { CliUsageError, EXIT_OK, emitMachine, renderHelp } from "../output.js";
4
4
  import { loadEmbedding } from "./_embed.js";
5
5
  import { remoteCall } from "./_remote.js";
6
- // `omg find <text>` (11 §5.2) — resolve: ranked {id, locator, preview,
7
- // evidence}. `-1` prints the top hit's id alone (pipe fuel: omg cat $(omg find … -1)).
6
+ // `omg find <text>` (11 §5.2) — resolve: ranked {_id, locator, preview,
7
+ // evidence} (spec/surface §4 `blocks_resolve`, 5.0). `-1` prints the top hit's
8
+ // id alone (pipe fuel: omg cat $(omg find … -1)).
8
9
  async function runFind(cli, args) {
9
10
  const { values, positionals } = parseArgs({
10
11
  args,
@@ -65,18 +66,18 @@ async function runFind(cli, args) {
65
66
  // -1: bare top id.
66
67
  if (values.one) {
67
68
  if (hits[0])
68
- cli.io.out(hits[0].id);
69
+ cli.io.out(hits[0]._id);
69
70
  return EXIT_OK;
70
71
  }
71
72
  if (cli.flags.mode !== "human")
72
- return emitMachine(cli, hits, { items: hits, ids: hits.map((h) => h.id) });
73
+ return emitMachine(cli, hits, { items: hits, ids: hits.map((h) => h._id) });
73
74
  const { style, io } = cli;
74
75
  if (hits.length === 0) {
75
76
  io.err(style.dim(" no matches"));
76
77
  return EXIT_OK;
77
78
  }
78
79
  for (const h of hits) {
79
- io.out(`${style.id(h.id)} ${style.accent(h.locator)} ${h.preview}`);
80
+ io.out(`${style.id(h._id)} ${style.accent(h.locator)} ${h.preview}`);
80
81
  if (values.verbose)
81
82
  io.err(style.dim(` ${JSON.stringify(h.evidence)}`));
82
83
  }
@@ -24,7 +24,7 @@ async function runHist(cli, args) {
24
24
  if (cli.flags.server) {
25
25
  // Remote: blocks_history keys on a block id (globally unique). Pass the ref
26
26
  // through — a locator would need a local resolve, so remote hist wants an id.
27
- changes = await remoteCall(cli, "blocks_history", { id: ref, ...(values.n ? { limit: Number(values.n) } : {}) });
27
+ changes = await remoteCall(cli, "blocks_history", { _id: ref, ...(values.n ? { limit: Number(values.n) } : {}) });
28
28
  }
29
29
  else {
30
30
  const ws = cli.workspace();
@@ -3,7 +3,10 @@ import { resolveRef, docLinks } from "@omgbase/core";
3
3
  import { CliUsageError, EngineErrorLike, EXIT_OK, emitMachine, renderHelp } from "../output.js";
4
4
  // `omg links <node>` (11 §5.4) — open links touching the node; default both
5
5
  // directions, grouped; doc-grain by default, --blocks for block-grain (one row
6
- // per edge, naming the source block). Backlinks = --in.
6
+ // per edge, naming the source block). Backlinks = --in. A row is
7
+ // `{ _predicate, _doc_id, kind, count, samples? | _block_id }` (spec/cli 5.0):
8
+ // the keys that carry a links-row attribute are `_`-prefixed; `kind` (what the
9
+ // far node is), `count` (the rollup) and `samples` name roles and stay bare.
7
10
  function runLinks(cli, args) {
8
11
  const { values, positionals } = parseArgs({
9
12
  args,
@@ -47,7 +50,7 @@ function runLinks(cli, args) {
47
50
  cli.capture?.(result); // shell: edges (out+in) become the addressable frame
48
51
  // `{ out, in }` is not a list: `--jsonl` prints the document; `--ids` the far nodes.
49
52
  if (cli.flags.mode !== "human")
50
- return emitMachine(cli, result, { ids: [...result.out, ...result.in].map((e) => e.doc_id) });
53
+ return emitMachine(cli, result, { ids: [...result.out, ...result.in].map((e) => e._doc_id) });
51
54
  const { render, style, io } = cli;
52
55
  const g = render.g;
53
56
  if (result.out.length === 0 && result.in.length === 0) {
@@ -56,23 +59,23 @@ function runLinks(cli, args) {
56
59
  }
57
60
  // Doc-grain: one row per (predicate, node) with its rollup count. Block-grain:
58
61
  // one row per edge, the source block beside the far node.
59
- const src = (e) => style.dim(`(${e.block_id ?? "frontmatter"})`);
62
+ const src = (e) => style.dim(`(${e._block_id ?? "frontmatter"})`);
60
63
  if (result.out.length > 0) {
61
64
  io.out(style.dim(" out"));
62
65
  for (const e of result.out) {
63
66
  if (values.blocks)
64
- io.out(` ${style.accent(e.predicate)} ${style.dim(g.arrow)} ${style.id(e.doc_id)} ${src(e)}`);
67
+ io.out(` ${style.accent(e._predicate)} ${style.dim(g.arrow)} ${style.id(e._doc_id)} ${src(e)}`);
65
68
  else
66
- io.out(` ${style.accent(e.predicate)} ${style.dim(g.arrow)} ${style.id(e.doc_id)} ${style.dim(`×${e.count}`)}`);
69
+ io.out(` ${style.accent(e._predicate)} ${style.dim(g.arrow)} ${style.id(e._doc_id)} ${style.dim(`×${e.count}`)}`);
67
70
  }
68
71
  }
69
72
  if (result.in.length > 0) {
70
73
  io.out(style.dim(" in (backlinks)"));
71
74
  for (const e of result.in) {
72
75
  if (values.blocks)
73
- io.out(` ${style.id(e.doc_id)} ${src(e)} ${style.dim(g.arrow)} ${style.accent(e.predicate)}`);
76
+ io.out(` ${style.id(e._doc_id)} ${src(e)} ${style.dim(g.arrow)} ${style.accent(e._predicate)}`);
74
77
  else
75
- io.out(` ${style.id(e.doc_id)} ${style.dim(g.arrow)} ${style.accent(e.predicate)} ${style.dim(`×${e.count}`)}`);
78
+ io.out(` ${style.id(e._doc_id)} ${style.dim(g.arrow)} ${style.accent(e._predicate)} ${style.dim(`×${e.count}`)}`);
76
79
  }
77
80
  }
78
81
  return EXIT_OK;
@@ -38,20 +38,20 @@ async function runLs(cli, args) {
38
38
  rows.push(...page.items);
39
39
  cursor = page.truncated ? page.cursor : null;
40
40
  } while (cursor);
41
- cli.capture?.(rows); // shell: docs become the addressable frame (ref = id, label path)
42
- // `--ids` prints the documents' `id`s (spec/cli 3.0; the paths before, a
43
- // stand-in for an identity the row did not carry).
41
+ cli.capture?.(rows); // shell: docs become the addressable frame (ref = _id, label _path)
42
+ // `--ids` prints the documents' `_id`s (spec/cli 5.0 — the row's engine
43
+ // attribute; the paths before 3.0, a stand-in for an identity the row did not carry).
44
44
  if (cli.flags.mode !== "human")
45
- return emitMachine(cli, rows, { items: rows, ids: rows.map((r) => r.id) });
45
+ return emitMachine(cli, rows, { items: rows, ids: rows.map((r) => r._id) });
46
46
  const { style, io } = cli;
47
47
  if (rows.length === 0) {
48
48
  io.err(style.dim(" no documents"));
49
49
  return EXIT_OK;
50
50
  }
51
51
  const table = rows.map((r) => [
52
- style.accent(r.path),
52
+ style.accent(r._path),
53
53
  style.dim(`${r.blocks} blocks`),
54
- style.dim(relTime(r.updated_at)),
54
+ style.dim(relTime(r._updated_at)),
55
55
  ]);
56
56
  for (const line of columns(table, [{}, { align: "right" }, { align: "right" }]))
57
57
  io.out(line);
@@ -12,34 +12,38 @@ import { remoteCall, remoteHasTool } from "./_remote.js";
12
12
  // conflict rendering). `apply` is the primitive — a changeset of `effects`
13
13
  // (ADR-021, the intentional-write vocabulary; the kernel `ops` input is retired
14
14
  // at cli 4.0) — and `execute` commits a frozen plan; the rest are sugar.
15
+ // Error data spells a row attribute as the attribute (spec/surface §4, 5.0):
16
+ // `block_missing { _id }`, and the CLI's own `type_mismatch` names the block's
17
+ // `_id` and `_type`.
15
18
  function block(cli, ws, repoId, ref) {
16
19
  const r = resolveRef(ws.store, repoId, ref);
17
20
  if (!r || r.kind !== "block")
18
- throw new EngineErrorLike("block_missing", `no block with id ${JSON.stringify(ref)}`, { data: { id: ref } });
21
+ throw new EngineErrorLike("block_missing", `no block with id ${JSON.stringify(ref)}`, { data: { _id: ref } });
19
22
  return r.blockId;
20
23
  }
21
24
  // A parent ref for insert/move: a block ref names that block as the parent; a
22
25
  // document ref (id or path) names the document's top level — the same reading
23
- // the server gives `blocks_insert.to` / `blocks_move.to`.
26
+ // the server gives `blocks_insert.to` / `blocks_move.to`. The target's address
27
+ // is the row's `_id` (spec/mutate §11, 4.0).
24
28
  function parent(ws, repoId, ref) {
25
29
  const r = resolveRef(ws.store, repoId, ref);
26
30
  if (!r)
27
- throw new EngineErrorLike("block_missing", `no block with id ${JSON.stringify(ref)}`, { data: { id: ref } });
31
+ throw new EngineErrorLike("block_missing", `no block with id ${JSON.stringify(ref)}`, { data: { _id: ref } });
28
32
  if (r.kind === "document")
29
- return { kind: "document", id: r.docId };
30
- return { kind: "block", id: r.blockId };
33
+ return { kind: "document", _id: r.docId };
34
+ return { kind: "block", _id: r.blockId };
31
35
  }
32
36
  // `done` addresses task blocks only (spec/cli §6): any other live block type is
33
37
  // a type_mismatch before an op is built — the macro itself would record a no-op
34
- // `edited` commit (its `attrs.checked` only rewrites a task's marker).
38
+ // `edited` commit (its `_attrs.checked` only rewrites a task's marker).
35
39
  function taskBlock(ws, ref, blockId) {
36
40
  const row = ws.store.db
37
41
  .prepare("SELECT type FROM blocks WHERE block_id = ? AND deleted_commit IS NULL")
38
42
  .get(blockId);
39
43
  if (!row)
40
- throw new EngineErrorLike("block_missing", `no block with id ${JSON.stringify(ref)}`, { data: { id: ref } });
44
+ throw new EngineErrorLike("block_missing", `no block with id ${JSON.stringify(ref)}`, { data: { _id: ref } });
41
45
  if (row.type !== "task") {
42
- throw new EngineErrorLike("type_mismatch", `not a task: ${ref} is a ${row.type}`, { data: { block: blockId, type: row.type } });
46
+ throw new EngineErrorLike("type_mismatch", `not a task: ${ref} is a ${row.type}`, { data: { _id: blockId, _type: row.type } });
43
47
  }
44
48
  return blockId;
45
49
  }
@@ -198,7 +202,7 @@ async function runUpdate(cli, args) {
198
202
  const expect = values.expect ? { content_hash: values.expect } : undefined;
199
203
  // Remote: the server pins CAS from current bytes when --expect is absent.
200
204
  if (cli.flags.server) {
201
- return runSugarRemote(cli, "blocks_update", { id: ref, markdown, ...(expect ? { expect } : {}) }, () => updateBlockEffects(ref, { markdown, expect }).effects, [ref]);
205
+ return runSugarRemote(cli, "blocks_update", { _id: ref, markdown, ...(expect ? { expect } : {}) }, () => updateBlockEffects(ref, { markdown, expect }).effects, [ref]);
202
206
  }
203
207
  const ws = cli.workspace();
204
208
  const repo = cli.repo(ws);
@@ -306,7 +310,7 @@ async function runDone(cli, args) {
306
310
  throw new CliUsageError("done requires one or more task blocks (or - for stdin)");
307
311
  const checked = !values.undo;
308
312
  if (cli.flags.server)
309
- return runSugarRemote(cli, "blocks_check", { ids: refs, ...(values.undo ? { checked: false } : {}) }, () => checkEffects(refs, checked).effects, refs);
313
+ return runSugarRemote(cli, "blocks_check", { _ids: refs, ...(values.undo ? { checked: false } : {}) }, () => checkEffects(refs, checked).effects, refs);
310
314
  const ws = cli.workspace();
311
315
  const repo = cli.repo(ws);
312
316
  const blocks = refs.map((r) => taskBlock(ws, r, block(cli, ws, repo.repoId, r)));
@@ -341,13 +345,13 @@ async function runAppend(cli, args) {
341
345
  // Remote: blocks_append resolves the ref and decides by the block's kind
342
346
  // server-side (spec/surface §4); a remote dry run reads the kind first.
343
347
  if (cli.flags.server) {
344
- return runSugarRemote(cli, "blocks_append", { id: target, text: markdown }, async () => {
345
- // The block's type decides the effect; `blocks_read` reads it on a full
348
+ return runSugarRemote(cli, "blocks_append", { _id: target, text: markdown }, async () => {
349
+ // The block's `_type` decides the effect; `blocks_read` reads it on a full
346
350
  // server, a `query` over the blocks target on a minimal one (§2.3).
347
351
  const type = (await remoteHasTool(cli, "blocks_read"))
348
- ? (await remoteCall(cli, "blocks_read", { id: target, resolution: "skeleton" })).type
349
- : (await remoteCall(cli, "query", { query: `select type from blocks where id == ${JSON.stringify(target)}` })).hits[0]?.type;
350
- return appendEffects(type === "heading" ? { kind: "section", heading_id: target } : { kind: "block", id: target }, markdown).effects;
352
+ ? (await remoteCall(cli, "blocks_read", { _id: target, resolution: "skeleton" }))._type
353
+ : (await remoteCall(cli, "query", { query: `select _type from blocks where _id == ${JSON.stringify(target)}` })).hits[0]?._type;
354
+ return appendEffects(type === "heading" ? { kind: "section", heading_id: target } : { kind: "block", _id: target }, markdown).effects;
351
355
  }, [target]);
352
356
  }
353
357
  const ws = cli.workspace();
@@ -363,13 +367,13 @@ function appendSugar(store, blockId, markdown) {
363
367
  .prepare("SELECT b.type AS type, d.format AS format, (SELECT count(*) FROM blocks c WHERE c.parent_block = b.block_id AND c.deleted_commit IS NULL) AS children FROM blocks b JOIN docs d ON d.doc_id = b.doc_id WHERE b.block_id = ? AND b.deleted_commit IS NULL")
364
368
  .get(blockId);
365
369
  if (!row)
366
- throw new EngineErrorLike("block_missing", `no block with id ${JSON.stringify(blockId)}`, { data: { id: blockId } });
370
+ throw new EngineErrorLike("block_missing", `no block with id ${JSON.stringify(blockId)}`, { data: { _id: blockId } });
367
371
  if (row.type === "heading")
368
372
  return appendEffects({ kind: "section", heading_id: blockId }, markdown);
369
373
  if (isContainerBlock(row.format ?? "markdown", { type: row.type, children: new Array(row.children) })) {
370
- return appendEffects({ kind: "block", id: blockId }, markdown);
374
+ return appendEffects({ kind: "block", _id: blockId }, markdown);
371
375
  }
372
- throw new EngineErrorLike("block_not_container", `block ${blockId} is a ${row.type}, not a heading or a container; append means into — insert after it with blocks_insert { to: <its parent>, at: { after: ${JSON.stringify(blockId)} } }`, { data: { id: blockId, type: row.type } });
376
+ throw new EngineErrorLike("block_not_container", `block ${blockId} is a ${row.type}, not a heading or a container; append means into — insert after it with blocks_insert { to: <its parent>, at: { after: ${JSON.stringify(blockId)} } }`, { data: { _id: blockId, _type: row.type } });
373
377
  }
374
378
  // ---- split / merge ----------------------------------------------------------
375
379
  async function runSplit(cli, args) {
@@ -403,7 +407,7 @@ async function runSplit(cli, args) {
403
407
  return n;
404
408
  });
405
409
  if (cli.flags.server)
406
- return runSugarRemote(cli, "blocks_split", { id: ref, at }, () => splitEffects(ref, at).effects, [ref]);
410
+ return runSugarRemote(cli, "blocks_split", { _id: ref, at }, () => splitEffects(ref, at).effects, [ref]);
407
411
  const ws = cli.workspace();
408
412
  const repo = cli.repo(ws);
409
413
  const blockId = block(cli, ws, repo.repoId, ref);
@@ -39,9 +39,9 @@ async function runNode(cli, args) {
39
39
  if (!nodeId || !prop || valueParts.length === 0)
40
40
  throw new CliUsageError("node set <nodeId> <prop> <value>");
41
41
  const value = valueParts.join(" ");
42
- // Remote: nodes_set resolves node → block + rewrites the span server-side.
42
+ // Remote: nodes_set (`_id` the node, spec/surface §4 "Arguments", 5.0) resolves node → block + rewrites the span server-side.
43
43
  if (cli.flags.server)
44
- return runSugarRemote(cli, "nodes_set", { id: nodeId, prop, value }, () => nodeSetEffects(nodeId, prop, value).effects, [nodeId]);
44
+ return runSugarRemote(cli, "nodes_set", { _id: nodeId, prop, value }, () => nodeSetEffects(nodeId, prop, value).effects, [nodeId]);
45
45
  const ws = cli.workspace();
46
46
  const repo = cli.repo(ws);
47
47
  return runSugar(cli, ws, repo, nodeSetEffects(requireNode(ws.store, repo.repoId, nodeId), prop, value), values.actor ? { actor: values.actor } : {});
@@ -5,8 +5,8 @@ import type { Cli } from "../context.js";
5
5
  * Render an `OqxResult` in the current mode (shared with `run`). `--json` is the
6
6
  * whole result verbatim (incl. consumer + any scalar), so count/exists round-trip
7
7
  * without special-casing; `--jsonl` streams the hits (or the values, as JSON);
8
- * `--ids` the hit ids — a scalar result has neither, so those modes print the
9
- * document, and so does a `bare` result (its hits carry no envelope). The
8
+ * `--ids` the hits' `_id`s — a scalar result has neither, so those modes print
9
+ * the document, and so does a `bare` result (its hits carry no envelope). The
10
10
  * truncation footer prints in every mode but `--json`.
11
11
  */
12
12
  export declare function renderOqxResult(cli: Cli, result: OqxResult, bare?: boolean): number;
@@ -6,15 +6,17 @@ import { loadEmbedding } from "./_embed.js";
6
6
  import { remoteCall } from "./_remote.js";
7
7
  import { readStdin } from "./_mutate.js";
8
8
  // `omg query <source>` — OQX composable query. Dot navigation belongs to the host
9
- // object model (`doc.layer`, `section.blocks`); whitespace query directives
10
- // belong to OQX (`<receiver> collect|exists|count|first|single { <block> }`).
9
+ // object model (`_doc.layer`, `$it._blocks`; spec/surface §1.2, 5.0: an engine
10
+ // name is `_`-prefixed, a bare name is an authored key, `$it` is the self);
11
+ // whitespace query directives belong to OQX (`<receiver>
12
+ // collect|exists|count|first|single { <block> }`).
11
13
  // `from E` selects + flattens a relation relative to the current source scope
12
14
  // (top-level `from docs` = the repository's docs; `docs collect { from
13
15
  // nodes … }` re-projects each doc through its nodes). Clause order is fixed
14
16
  // (ADR-020): `select … from … where … follow … order by … limit … offset …`,
15
17
  // each at most once, at the top level and inside every block. Only `select` may
16
- // drop its keyword, and only as the first clause (`nodes collect { attrs.text }`
17
- // projects); a predicate always needs `where` (`nodes exists { where kind ==
18
+ // drop its keyword, and only as the first clause (`_nodes collect { _name }`
19
+ // projects); a predicate always needs `where` (`_nodes exists { where _kind ==
18
20
  // "md:task" }`), so a bare boolean property projects rather than filters.
19
21
  // `where` may use the body's select aliases. Features: a boolean where
20
22
  // tree over scalar predicates and consumer directives, count comparisons
@@ -29,13 +31,13 @@ import { readStdin } from "./_mutate.js";
29
31
  // `docs count { … }` / `docs exists { … }` reduce to a scalar,
30
32
  // `docs first { … }` / `docs single { … }` to zero-or-one row (bare
31
33
  // `from …` = collect). Coexists with `omg q` (CEL). Source is a positional
32
- // string or -f file|-. Human output: one hit per line (id + path), or an aligned
33
- // id/path/columns table when the query projects (`select`), or the scalar for
34
- // count/exists/none (see output.ts renderHits); --json/--jsonl/--ids. `--bare`
35
- // is the tool's `bare` passed through (ADR-012: a thin client; spec/surface
36
- // §1.4, 4.2): every hit is its projection alone, rendered as a table without
37
- // the id/path columns (`--json`/`--jsonl` the bare hits; `--ids` has no id to
38
- // print and falls back to the document, like a scalar result).
34
+ // string or -f file|-. Human output: one hit per line (_id + _path), or an
35
+ // aligned _id/_path/columns table when the query projects (`select`), or the
36
+ // scalar for count/exists/none (see output.ts renderHits); --json/--jsonl/--ids.
37
+ // `--bare` is the tool's `bare` passed through (ADR-012: a thin client;
38
+ // spec/surface §1.4, 4.2): every hit is its projection alone, rendered as a
39
+ // table without the _id/_path columns (`--json`/`--jsonl` the bare hits;
40
+ // `--ids` has no id to print and falls back to the document, like a scalar result).
39
41
  async function runOqx(cli, args) {
40
42
  const { values, positionals } = parseArgs({
41
43
  args,
@@ -58,39 +60,40 @@ async function runOqx(cli, args) {
58
60
  ["-f <file|->", "read the query from a file, or stdin with `-`"],
59
61
  ["-n <N>", "page size"],
60
62
  ["--cursor <c>", "continue a truncated result"],
61
- ["--bare", "hits as the projection only — no automatic id/path columns (needs a `select`; `values` is the bare-values form)"],
63
+ ["--bare", "hits as the projection only — no automatic _id/_path columns (needs a `select`; `values` is the bare-values form)"],
62
64
  ["--ids", `bare ids, one per line — pipe fuel (\`… --ids | ${cli.prog} cat -\`)`],
63
65
  ],
64
66
  notes: [
65
67
  `semantic ranking: \`order by semantic("phrase") desc\` or \`where semantic("phrase") > 0.6\` (needs embedding.provider; see \`${cli.prog} embed --help\`).`,
66
68
  "full syntax: the examples below, the MCP `query_syntax` tool, and docs/query-language.md.",
69
+ "names: an engine field is `_`-prefixed (_id _path _title _tags _updated_at _type _text _doc …); a bare name is an authored key (title, tags, layer); `$it` is the row itself.",
67
70
  "examples:",
68
71
  ],
69
72
  });
70
- cli.io.out(" e.g. query 'from docs where nodes count { where kind == \"md:task\" } >= 2'");
71
- cli.io.out(" query 'select items: section.blocks collect { where type == \"list_item\" } from nodes where kind == \"md:section\"'");
72
- cli.io.out(" query 'select path, open from docs where nodes collect { ^open: value where kind == \"md:task\" and not attrs.checked }'");
73
- cli.io.out(" query 'select owner_id, owner: ^nodes single { where kind == \"person\" and attrs.id == ^owner_id } from docs' # ^docs/^nodes/^blocks/^links: the repository (the root row) from inside a row; 0^docs from any depth; ^id / 0^id = the repository id");
73
+ cli.io.out(" e.g. query 'from docs where _nodes count { where _kind == \"md:task\" } >= 2'");
74
+ cli.io.out(" query 'select items: _blocks collect { where _type == \"list_item\" } from nodes where _kind == \"md:section\"'");
75
+ cli.io.out(" query 'select _title, open from docs where _nodes collect { ^open: _value where _kind == \"md:task\" and not checked }'");
76
+ cli.io.out(" query 'select owner_id, owner: ^nodes single { where _kind == \"person\" and id == ^owner_id } from docs' # ^docs/^nodes/^blocks/^links: the repository (the root row) from inside a row; 0^docs from any depth; ^_id / 0^_id = the repository id");
74
77
  cli.io.out(" query 'docs count { where layer == \"canon\" }' # scalar; also <target> exists/none/first/single { … } at the top level");
75
- cli.io.out(" query 'from docs where nodes none { where kind == \"md:task\" and not checked }' # none = zero rows (≡ not … exists; \"every\" = none over the complement)");
78
+ cli.io.out(" query 'from docs where _nodes none { where _kind == \"md:task\" and not checked }' # none = zero rows (≡ not … exists; \"every\" = none over the complement)");
76
79
  cli.io.out(" query 'from docs where era is not null and (layer == \"canon\" or layer == \"working\")' # and / or / not are the connectives (&& || ! accepted); x is y / x is not y = identity, x is null = absent");
77
- cli.io.out(" query 'docs { type == \"substance\" and not verified }' # sugar: a block without a consumer is collect; a leading predicate drops `where` (bare names still project — filter a bare field with `is x`); after a follow destination too: follow children { depth 2 } is its block");
78
- cli.io.out(" query 'select subject, process: ^docs[slug == ^subject].path from docs where type == \"lab-note\"' # x[p] = first match or absent; x[p]! = exactly one, required; x[n] = positional; title! = the value or a loud error; a select item may use the items to its left");
79
- cli.io.out(" query 'select path, $depth from docs where path == \"/timeline/review.md\" follow refs(before), refs(after)' # refs(field): the live documents a property's references name (either path form or a d_ id; dangling dropped) — a receiver, a source or a follow destination");
80
- cli.io.out(" query 'select path, next: ^docs { ^path in list(before) } from docs where type == \"milestone\"' # paths are /-rooted on the surface (path == \"/x.md\"; inputs accept both forms), so a property's reference compares with ^path directly");
80
+ cli.io.out(" query 'docs { type == \"substance\" and not verified }' # sugar: a block without a consumer is collect; a leading predicate drops `where` (bare names still project — filter a bare field with `is x`); after a follow destination too: follow _children { depth 2 } is its block");
81
+ cli.io.out(" query 'select subject, process: ^docs[slug == ^subject]._path from docs where type == \"lab-note\"' # x[p] = first match or absent; x[p]! = exactly one, required; x[n] = positional; _title! = the value or a loud error; a select item may use the items to its left");
82
+ cli.io.out(" query 'select _title, $depth from docs where _path == \"/timeline/review.md\" follow refs(before), refs(after)' # refs(field): the live documents a property's references name (either path form or a d_ id; dangling dropped) — a receiver, a source or a follow destination");
83
+ cli.io.out(" query 'select _title, next: ^docs { ^_path in list(before) } from docs where type == \"milestone\"' # paths are /-rooted on the surface (_path == \"/x.md\"; inputs accept both forms), so a property's reference compares with ^_path directly");
81
84
  cli.io.out(" query 'from docs where type == \"practitioner\" order by era desc limit 2 offset 1' # limit/offset bound the set (after order/distinct, before the consumer)");
82
- cli.io.out(" query 'select fm: entries(frontmatter) collect { k: $key, v: $it } from docs where path == \"/x.md\"' # a record as a collection ($key/$it); also entries(attrs), entries(inline)");
83
- cli.io.out(" query 'select path values from docs where layer == \"canon\"' # `values`: bare values, no {id,path} hits (one item only)");
84
- cli.io.out(" query 'select path, tags: tags collect { $it values where $it != \"draft\" } from docs' # $it = the current item (here: each tag)");
85
- cli.io.out(" query 'docs collect { from nodes where kind == \"md:task\" }' # `from E` re-projects the source (→ nodes)");
85
+ cli.io.out(" query 'select fm: entries(_frontmatter) collect { k: $key, v: $it } from docs where _path == \"/x.md\"' # a record as a collection ($key/$it); also entries(_data), entries(_attrs), entries(_inline)");
86
+ cli.io.out(" query 'select _path values from docs where layer == \"canon\"' # `values`: bare values, no {_id,_path} hits (one item only)");
87
+ cli.io.out(" query 'select _title, tags: _tags collect { $it values where $it != \"draft\" } from docs' # $it = the current item (here: each tag); _tags = authored tags ∪ #hashtags, bare `tags` the authored key alone");
88
+ cli.io.out(" query 'docs collect { from _nodes where _kind == \"md:task\" }' # `from E` re-projects the source (→ nodes)");
86
89
  cli.io.out(" query 'from docs where text(\"philosophers stone\") and layer == \"canon\"' # full-text prune");
87
90
  cli.io.out(" query 'select s: semantic(\"the great work\") from blocks where semantic(\"the great work\") > 0.6' # embedding score (needs a provider)");
88
91
  cli.io.out(" query 'from docs where type == \"practitioner\" order by era desc' # order by <expr> [asc|desc]");
89
- cli.io.out(" query 'select t: text, d: $depth, s: $stop from blocks where id == \"b_x\" follow block.children' # recursive walk ($depth/$stop metadata)");
90
- cli.io.out(" query 'from nodes where name == \"Overview\" follow section.subsections { depth 3 }' # follow [distinct] <dest> [{ where … frontier … depth n }] — a brace after any destination is that destination's block (identity is $key: project $key: <expr> to rekey a walk)");
91
- cli.io.out(" query 'select p: path, d: $depth, s: $stop from docs where path == \"/index.md\" follow out_links collect { from dst }' # citation graph (cyclic-safe: $stop=cycle)");
92
- cli.io.out(" query 'from blocks where type == \"list_item\" and $leaf follow block.children' # $leaf/$depth/$stop filter the walk result post-walk");
93
- cli.io.out(" query 'select src: doc_id, to: dst_path from links where predicate == \"depends_on\"' # the links target: predicate/provenance/dst_kind + doc_id/dst_path/uri");
92
+ cli.io.out(" query 'select t: _text, d: $depth, s: $stop from blocks where _id == \"b_x\" follow _children' # recursive walk ($depth/$stop metadata)");
93
+ cli.io.out(" query 'from nodes where _name == \"Overview\" follow _subsections { depth 3 }' # follow [distinct] <dest> [{ where … frontier … depth n }] — a brace after any destination is that destination's block (identity is $key: project $key: <expr> to rekey a walk)");
94
+ cli.io.out(" query 'select p: _doc._path, d: $depth, s: $stop from docs where _path == \"/index.md\" follow _out_links collect { from _dst }' # citation graph (cyclic-safe: $stop=cycle)");
95
+ cli.io.out(" query 'from blocks where _type == \"list_item\" and $leaf follow _children' # $leaf/$depth/$stop filter the walk result post-walk");
96
+ cli.io.out(" query 'select src: _doc_id, to: _dst_path from links where _predicate == \"depends_on\"' # the links target: _predicate/_provenance/_dst_kind + _doc_id/_dst_path/_uri");
94
97
  return EXIT_OK;
95
98
  }
96
99
  const source = values.file
@@ -153,8 +156,8 @@ async function runOqx(cli, args) {
153
156
  * Render an `OqxResult` in the current mode (shared with `run`). `--json` is the
154
157
  * whole result verbatim (incl. consumer + any scalar), so count/exists round-trip
155
158
  * without special-casing; `--jsonl` streams the hits (or the values, as JSON);
156
- * `--ids` the hit ids — a scalar result has neither, so those modes print the
157
- * document, and so does a `bare` result (its hits carry no envelope). The
159
+ * `--ids` the hits' `_id`s — a scalar result has neither, so those modes print
160
+ * the document, and so does a `bare` result (its hits carry no envelope). The
158
161
  * truncation footer prints in every mode but `--json`.
159
162
  */
160
163
  export function renderOqxResult(cli, result, bare = false) {
@@ -163,7 +166,7 @@ export function renderOqxResult(cli, result, bare = false) {
163
166
  const list = result.values ?? (result.consumer === "collect" || result.consumer === "first" || result.consumer === "single" ? result.hits : undefined);
164
167
  return emitMachine(cli, result, {
165
168
  ...(list ? { items: list } : {}),
166
- ...(list && !result.values && !bare ? { ids: result.hits.map((h) => h.id) } : {}),
169
+ ...(list && !result.values && !bare ? { ids: result.hits.map((h) => h._id) } : {}),
167
170
  cursor,
168
171
  });
169
172
  }
@@ -32,8 +32,8 @@ async function runOutline(cli, args) {
32
32
  let result;
33
33
  let header;
34
34
  if (cli.flags.server) {
35
- // Remote: the docs_outline tool resolves the locator (`id` for a `d_` id,
36
- // else `path`) + returns the same OutlineResult. The header shows the ref you passed (no local path lookup):
35
+ // Remote: the docs_outline tool resolves the locator (`_id` for a `d_` id,
36
+ // else `_path` — spec/surface §4 "Arguments", 5.0) + returns the same OutlineResult. The header shows the ref you passed (no local path lookup):
37
37
  // a path ref rooted (spec/surface §1 "Paths"), a `d_` id verbatim.
38
38
  result = await remoteCall(cli, "docs_outline", {
39
39
  ...docLocator(ref),
@@ -70,7 +70,7 @@ function renderHits(cli, from, to, hits) {
70
70
  }
71
71
  io.err(style.dim(` plan — ${hits.length} block(s) would change; re-run with --apply to commit`));
72
72
  for (const h of hits) {
73
- io.out(style.id(h.id));
73
+ io.out(style.id(h._id));
74
74
  for (const line of unifiedDiffLines(cli, h.oldRaw, h.newRaw))
75
75
  io.out(line);
76
76
  io.out("");
@@ -99,8 +99,8 @@ async function runRun(cli, args) {
99
99
  else {
100
100
  result = oqxRun(ws.store, repo.repoId, source);
101
101
  }
102
- // Same renderer as `omg query`, in every mode: `<id> <path>` lines, or a
103
- // column table when the fence projects (a selected `text` shows as its first line).
102
+ // Same renderer as `omg query`, in every mode: `<_id> <_path>` lines, or a
103
+ // column table when the fence projects (a selected `_text` shows as its first line).
104
104
  return renderOqxResult(cli, result);
105
105
  }
106
106
  export const cmdRun = { name: "run", summary: "Evaluate an ```omg fence (OQX, inert, read-only)", run: (c, a) => runRun(c, a) };
@@ -15,14 +15,14 @@ function showOne(cli, ref, include) {
15
15
  if (!res)
16
16
  throw new EngineErrorLike("doc_missing", `no document ${ref}`);
17
17
  const links = docLinks(ws.store, resolved.docId, { direction: "both" });
18
- // The authored keys, merged (frontmatter + inline — the `data` view); the
19
- // computed rows are rendered under their surface names (`title`/`tags`,
20
- // spec/surface §1.2 — the store's `$title`/`$tags` keys never reach the
18
+ // The authored keys, merged (frontmatter + inline — the `_data` view); the
19
+ // computed rows are rendered under their surface names (`_title`/`_tags`,
20
+ // spec/surface §1.2, 5.0 — the store's `$title`/`$tags` keys never reach the
21
21
  // terminal) from the re-keyed `properties.computed` the read carries.
22
22
  const properties = Object.fromEntries(Object.entries(docPropertiesMerged(ws.store.db, resolved.docId)).filter(([k]) => !k.startsWith("$")));
23
23
  const computed = res.properties.computed ?? {};
24
24
  const payload = { kind: "document", ...res };
25
- return { payload, render: () => renderDocCard(cli, res.path, properties, computed, links) };
25
+ return { payload, render: () => renderDocCard(cli, res._path, properties, computed, links) };
26
26
  }
27
27
  const node = nodesGet(ws.store, resolved.docId, resolved.blockId, { resolution: "full" });
28
28
  if (!node)
@@ -74,8 +74,8 @@ function renderDocCard(cli, path, fm, computed, links) {
74
74
  for (const k of keys)
75
75
  io.out(` ${style.accent(k)} ${style.dim("=")} ${fmt(fm[k])}`);
76
76
  }
77
- // The computed rows under their surface names (`title`, `tags` — spec/surface
78
- // §1.2), their own section so a computed title never masquerades as authored.
77
+ // The computed rows under their surface names (`_title`, `_tags` — spec/surface
78
+ // §1.2, 5.0), their own section so a computed title never masquerades as authored.
79
79
  const computedKeys = Object.keys(computed);
80
80
  if (computedKeys.length > 0) {
81
81
  io.out(` ${style.dim("computed")}`);
@@ -85,28 +85,28 @@ function renderDocCard(cli, path, fm, computed, links) {
85
85
  if (links.out.length > 0) {
86
86
  io.out(` ${style.dim("out links")}`);
87
87
  for (const e of links.out)
88
- io.out(` ${style.accent(e.predicate)} ${style.dim(render.g.arrow)} ${style.id(e.doc_id)} ${style.dim(`×${e.count}`)}`);
88
+ io.out(` ${style.accent(e._predicate)} ${style.dim(render.g.arrow)} ${style.id(e._doc_id)} ${style.dim(`×${e.count}`)}`);
89
89
  }
90
90
  if (links.in.length > 0) {
91
91
  io.out(` ${style.dim("backlinks")}`);
92
92
  for (const e of links.in)
93
- io.out(` ${style.id(e.doc_id)} ${style.dim(render.g.arrow)} ${style.accent(e.predicate)} ${style.dim(`×${e.count}`)}`);
93
+ io.out(` ${style.id(e._doc_id)} ${style.dim(render.g.arrow)} ${style.accent(e._predicate)} ${style.dim(`×${e.count}`)}`);
94
94
  }
95
95
  }
96
96
  function renderBlockCard(cli, node, history) {
97
97
  const { render, style, io } = cli;
98
- io.out(` ${render.typeGlyph(node.type)} ${style.bold(node.type)} ${style.id(node.id)}`);
98
+ io.out(` ${render.typeGlyph(node._type)} ${style.bold(node._type)} ${style.id(node._id)}`);
99
99
  io.out(render.rule(40));
100
- // The placement is flattened onto the row (3.0: `parent_id`/`ordinal`/`depth`); the line reads the same.
101
- if (node.ordinal !== undefined)
102
- io.out(` ${style.dim("placement")} parent=${style.id(node.parent_id ?? "—")} ordinal=${node.ordinal} depth=${node.depth}`);
103
- if (node.attrs && Object.keys(node.attrs).length > 0) {
100
+ // The placement is flattened onto the row (5.0: `parent_id`/`_ordinal`/`_depth`); the line reads the same.
101
+ if (node._ordinal !== undefined)
102
+ io.out(` ${style.dim("placement")} parent=${style.id(node.parent_id ?? "—")} ordinal=${node._ordinal} depth=${node._depth}`);
103
+ if (node._attrs && Object.keys(node._attrs).length > 0) {
104
104
  io.out(` ${style.dim("attrs")}`);
105
- for (const [k, v] of Object.entries(node.attrs))
105
+ for (const [k, v] of Object.entries(node._attrs))
106
106
  io.out(` ${style.accent(k)} ${style.dim("=")} ${fmt(v)}`);
107
107
  }
108
- if (node.text)
109
- io.out(` ${style.dim("text")} ${node.text}`);
108
+ if (node._text)
109
+ io.out(` ${style.dim("text")} ${node._text}`);
110
110
  if (history && history.length > 0) {
111
111
  io.out(` ${style.dim("history")}`);
112
112
  for (const h of history)
@@ -71,7 +71,7 @@ async function runDocUpdate(cli, args) {
71
71
  const repo = cli.repo(ws);
72
72
  if (!repo.rootPath)
73
73
  throw new CliUsageError(`repo '${repo.slug}' has no filesystem source; 'update' needs a working tree`);
74
- const effects = [{ op: "set", target: { kind: "document", id: requireDoc(ws.store, repo.repoId, doc) }, field: "content", value: bytes }];
74
+ const effects = [{ op: "set", target: { kind: "document", _id: requireDoc(ws.store, repo.repoId, doc) }, field: "content", value: bytes }];
75
75
  const req = {
76
76
  repoId: repo.repoId,
77
77
  rootPath: repo.rootPath,
@@ -93,11 +93,12 @@ async function runDocUpdate(cli, args) {
93
93
  return EXIT_OK;
94
94
  }
95
95
  if (receipt === undefined) {
96
- // The plan: its one effect names the document; `preview.identity[<id>]` is
97
- // the opset plan (one line per op, an empty line, the summary).
96
+ // The plan: its one effect names the document (a resolved target carries
97
+ // `_id` and `_path`, spec/mutate §11); `preview.identity[<id>]` is the opset
98
+ // plan (one line per op, an empty line, the summary).
98
99
  const target = plan.effects[0] && "target" in plan.effects[0] ? plan.effects[0].target : undefined;
99
- const docId = target && target.kind === "document" ? target.id : undefined;
100
- const path = target && target.kind === "document" ? referencePath(target.path) : doc;
100
+ const docId = target && target.kind === "document" ? target._id : undefined;
101
+ const path = target && target.kind === "document" ? referencePath(target._path) : doc;
101
102
  const identity = docId !== undefined ? plan.preview?.identity?.[docId] : undefined;
102
103
  cli.io.err(cli.style.dim(` plan for ${cli.style.accent(path)}`));
103
104
  if (identity)
@@ -83,8 +83,8 @@ interface MachineIO {
83
83
  /** Print `doc` in the current machine mode (`--json`/`--jsonl`/`--ids`); returns EXIT_OK. */
84
84
  export declare function emitMachine(cli: MachineIO, doc: unknown, shape?: MachineShape): number;
85
85
  export interface HitLike {
86
- id: string;
87
- path: string;
86
+ _id: string;
87
+ _path: string;
88
88
  [k: string]: unknown;
89
89
  }
90
90
  export declare const HIT_CELL_MAX = 60;
@@ -93,19 +93,19 @@ export declare function truncateCell(s: string, max?: number): string;
93
93
  /** Render one projected value as a table cell (unstyled). */
94
94
  export declare function hitCell(v: unknown): string;
95
95
  /** The projected column names across `hits`: every key but the envelope's
96
- * `id`/`path` (spec/surface §1.4, 3.0 — a `path` projection collapses onto
96
+ * `_id`/`_path` (spec/surface §1.4, 5.0 — a `_path` projection collapses onto
97
97
  * the envelope key, so it is never a column), in first-seen order. Empty when
98
98
  * nothing was projected. */
99
99
  export declare function hitColumns(hits: HitLike[]): string[];
100
- /** Human-tier hit list: `<id> <path>` lines, or an aligned table when projected
101
- * (header `id`, `path`, then the projected keys — spec/cli §4, 3.0). */
100
+ /** Human-tier hit list: `<_id> <_path>` lines, or an aligned table when projected
101
+ * (header `_id`, `_path`, then the projected keys — spec/cli §4, 5.0). */
102
102
  export declare function renderHits(cli: {
103
103
  io: IO;
104
104
  style: Style;
105
105
  }, hits: HitLike[]): void;
106
106
  /** The bare hit table (`query --bare`, spec/cli §4, 4.1): a hit is its
107
107
  * projection alone (`spec/surface` §1.4, 4.2), so the columns are every key in
108
- * first-seen order across the hits — a projected `id`/`path` is an ordinary
108
+ * first-seen order across the hits — a projected `_id`/`_path` is an ordinary
109
109
  * column, rendered as any other cell — with no envelope columns before them.
110
110
  * Header row, then one row per hit; cells as the enveloped table's. */
111
111
  export declare function renderBareHits(cli: {
@@ -191,12 +191,12 @@ export function hitCell(v) {
191
191
  return truncateCell(JSON.stringify(v) ?? String(v));
192
192
  }
193
193
  /** The projected column names across `hits`: every key but the envelope's
194
- * `id`/`path` (spec/surface §1.4, 3.0 — a `path` projection collapses onto
194
+ * `_id`/`_path` (spec/surface §1.4, 5.0 — a `_path` projection collapses onto
195
195
  * the envelope key, so it is never a column), in first-seen order. Empty when
196
196
  * nothing was projected. */
197
197
  export function hitColumns(hits) {
198
198
  const cols = [];
199
- const seen = new Set(["id", "path"]);
199
+ const seen = new Set(["_id", "_path"]);
200
200
  for (const h of hits) {
201
201
  for (const k of Object.keys(h)) {
202
202
  if (seen.has(k))
@@ -207,24 +207,24 @@ export function hitColumns(hits) {
207
207
  }
208
208
  return cols;
209
209
  }
210
- /** Human-tier hit list: `<id> <path>` lines, or an aligned table when projected
211
- * (header `id`, `path`, then the projected keys — spec/cli §4, 3.0). */
210
+ /** Human-tier hit list: `<_id> <_path>` lines, or an aligned table when projected
211
+ * (header `_id`, `_path`, then the projected keys — spec/cli §4, 5.0). */
212
212
  export function renderHits(cli, hits) {
213
213
  const { io, style } = cli;
214
214
  const projected = hitColumns(hits);
215
215
  if (projected.length === 0) {
216
216
  for (const h of hits)
217
- io.out(`${style.id(h.id)} ${style.accent(h.path)}`.trimEnd());
217
+ io.out(`${style.id(h._id)} ${style.accent(h._path)}`.trimEnd());
218
218
  return;
219
219
  }
220
- const cols = ["id", "path", ...projected];
220
+ const cols = ["_id", "_path", ...projected];
221
221
  const rows = [cols.map((c) => style.dim(c))];
222
222
  for (const h of hits) {
223
223
  rows.push(cols.map((c) => {
224
- if (c === "id")
225
- return style.id(h.id);
226
- if (c === "path")
227
- return style.accent(h.path);
224
+ if (c === "_id")
225
+ return style.id(h._id);
226
+ if (c === "_path")
227
+ return style.accent(h._path);
228
228
  return hitCell(h[c]);
229
229
  }));
230
230
  }
@@ -233,7 +233,7 @@ export function renderHits(cli, hits) {
233
233
  }
234
234
  /** The bare hit table (`query --bare`, spec/cli §4, 4.1): a hit is its
235
235
  * projection alone (`spec/surface` §1.4, 4.2), so the columns are every key in
236
- * first-seen order across the hits — a projected `id`/`path` is an ordinary
236
+ * first-seen order across the hits — a projected `_id`/`_path` is an ordinary
237
237
  * column, rendered as any other cell — with no envelope columns before them.
238
238
  * Header row, then one row per hit; cells as the enveloped table's. */
239
239
  export function renderBareHits(cli, hits) {
@@ -10,9 +10,9 @@
10
10
  // signal to use OQX instead.
11
11
  export class RefError extends Error {
12
12
  }
13
- // `.field` reads any projected key (`@1.id`, `@t[1].path`, `@1.layer` —
14
- // spec/surface §1.4, 3.0 spells the envelope as the bare attributes); a leading
15
- // `$` is admitted for an OQX-named projection (`$depth`).
13
+ // `.field` reads any projected key (`@1._id`, `@t[1]._path`, `@1.layer` —
14
+ // spec/surface §1.2, 5.0: an engine field is `_`-prefixed, a bare name is an
15
+ // authored key); a leading `$` is admitted for an OQX-named projection (`$depth`).
16
16
  const REF_RE = /^@(\d+|_|[A-Za-z][\w-]*)(?:\[(\d+)\])?(?:\.(\$?[A-Za-z_]\w*))?$/;
17
17
  /** Parse a whole-token reference; null if the token isn't a reference. */
18
18
  export function parseRef(token) {
@@ -48,7 +48,7 @@ export function deriveRows(value) {
48
48
  return o.ids.filter((id) => typeof id === "string").map((id) => ({ ref: id, label: id, value: id }));
49
49
  }
50
50
  if (Array.isArray(o.in) || Array.isArray(o.out)) {
51
- // links: both directions, each edge's far node is the selectable ref.
51
+ // links: both directions, each edge's far node (`_doc_id`) is the selectable ref.
52
52
  const edges = [...(o.out ?? []), ...(o.in ?? [])];
53
53
  return edges.map(rowOf);
54
54
  }
@@ -61,19 +61,24 @@ function rowOf(el) {
61
61
  if (el && typeof el === "object") {
62
62
  const o = el;
63
63
  const ref = firstString(o, REF_KEYS);
64
- const label = firstString(o, ["path", "locator", "name", "id", "node", "doc_id"]) ?? ref ?? preview(o);
64
+ const label = firstString(o, LABEL_KEYS) ?? ref ?? preview(o);
65
65
  return { ref: ref ?? label ?? "", label: label ?? "", value: el };
66
66
  }
67
67
  const s = String(el);
68
68
  return { ref: s, label: s, value: el };
69
69
  }
70
70
  // The keys that make an object an entity ref, in precedence order: a row's
71
- // `id` (spec/surface §1.2, 3.0 — every tool result spells a row's identity as
72
- // the attribute; a 2.x server's `id` is the same key), a `links` row's far node
73
- // (`doc_id`) and an edge's `block_id`, then the pre-3.0 envelopes (`node`,
74
- // `block`) — kept so a remote 2.x server's results still address — then
75
- // `locator` and a path.
76
- const REF_KEYS = ["id", "doc_id", "block_id", "node", "block", "locator", "path"];
71
+ // `_id` (spec/surface §1.2, 5.0 — every tool result spells a row's identity as
72
+ // the engine attribute, `_`-prefixed), a `links` row's far node (`_doc_id`) and
73
+ // an edge's `_block_id`, then the role names `node` and `block` (a `node props`
74
+ // card's `node`; a role key that names a block), then `locator` and the
75
+ // document's `_path`. The
76
+ // bare `id`/`path` (4.x) and `$id`/`$path` tolerance is gone (spec/cli 5.0): a
77
+ // bare key is an authored property, never the row's identity.
78
+ const REF_KEYS = ["_id", "_doc_id", "_block_id", "node", "block", "locator", "_path"];
79
+ // The human label of a row, in precedence order: its `_path`, a `locator`, a
80
+ // `name` (a repo, a source), then the id-like keys.
81
+ const LABEL_KEYS = ["_path", "locator", "name", "_id", "node", "_doc_id"];
77
82
  function firstString(o, keys) {
78
83
  for (const k of keys)
79
84
  if (typeof o[k] === "string")
@@ -107,7 +112,7 @@ export function coerce(value) {
107
112
  // A collection-shaped result object (e.g. {hits:[…]}) can't be one arg.
108
113
  if (deriveRows(value))
109
114
  throw new RefError("reference is a collection; select one with [i]");
110
- throw new RefError("reference has no id/locator to use as an argument");
115
+ throw new RefError("reference has no _id/locator to use as an argument");
111
116
  }
112
117
  throw new RefError("reference is not usable as an argument");
113
118
  }
@@ -141,7 +141,7 @@ export class ShellSession {
141
141
  line(style.dim("run any omg command; results become addressable:"));
142
142
  line(`${style.accent("@1 @2 …")} ${style.dim("rows of the last displayed collection")}`);
143
143
  line(`${style.accent("@_")} ${style.dim("the previous command's result")}`);
144
- line(`${style.accent("@name")} ${style.dim("a named binding (also @name[i], @name.field)")}`);
144
+ line(`${style.accent("@name")} ${style.dim("a named binding (also @name[i], @name.field — e.g. @1._id, @t[1]._path)")}`);
145
145
  io.out("");
146
146
  line(`${style.accent("@x = <cmd|@ref>")} ${style.dim("bind a snapshot of a result")}`);
147
147
  line(`${style.accent("unset x")} ${style.dim("drop a binding")}`);
@@ -295,7 +295,7 @@ export class ShellSession {
295
295
  return String(v);
296
296
  if (v && typeof v === "object") {
297
297
  const o = v;
298
- const id = o.id ?? o.id ?? o.node ?? o.path ?? o.path ?? o.locator; // a query hit leads with id/path (surface 3.0)
298
+ const id = o._id ?? o.node ?? o._path ?? o.locator; // a query hit leads with _id/_path (surface 5.0: engine names are `_`-prefixed)
299
299
  return id ? String(id) : "record";
300
300
  }
301
301
  return "empty";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "omgbase",
3
- "version": "0.24.1",
3
+ "version": "0.26.1",
4
4
  "description": "Open Markdown Graph Base — CLI",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -45,9 +45,9 @@
45
45
  "omg": "./dist/src/main.js"
46
46
  },
47
47
  "dependencies": {
48
- "@omgbase/core": "^0.27.1",
48
+ "@omgbase/core": "^0.29.1",
49
49
  "@omgbase/fs-adapter": "^0.2.0",
50
- "@omgbase/sync": "^0.5.21",
50
+ "@omgbase/sync": "^0.6.2",
51
51
  "yaml": "^2.9.0"
52
52
  },
53
53
  "devDependencies": {