@omgbase/core 0.5.0 → 0.6.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.
@@ -25,8 +25,16 @@ export interface DiffEntry {
25
25
  after?: string;
26
26
  }
27
27
  export declare function diffBlocks(store: Store, docId: string, fromRev: string, toRev: string): DiffEntry[];
28
- /** `diff_unified` (spec/surface §3): a unified diff of two revisions' rendered
29
- * texts — each revision's live raws joined by `\n` — via {@link unifiedDiff}. */
28
+ /**
29
+ * `diff_unified` (spec/surface §3): a unified diff of the two revisions'
30
+ * **reconstructed file text** — exactly the bytes `docs_read_at` returns for
31
+ * each revision ({@link readDocumentAtRevision}: frontmatter, trivia and
32
+ * top-level order included) — via {@link unifiedDiff}, so hunk line numbers
33
+ * are real file lines. Until 1.4.x this diffed the Merkle-tree walk (every
34
+ * live raw at every depth joined by `\n`), which repeated each nested block
35
+ * after its container's raw and put an appended list item before the list's
36
+ * first item (§9). An unknown revision is {@link RevisionNotFound}.
37
+ */
30
38
  export declare function diffUnified(store: Store, docId: string, fromRev: string, toRev: string): string;
31
39
  /** The `diff_unified` tool's result (spec/surface §3): the resolved revision pair and the diff. */
32
40
  export interface DocDiffUnified {
@@ -1,4 +1,5 @@
1
1
  import { findDocByRef } from "../core/read/reader.js";
2
+ import { readDocumentAtRevision } from "../core/read/document.js";
2
3
  import { isValidId } from "../core/ids.js";
3
4
  import { EngineError } from "../mcp/errors.js";
4
5
  /** history_node: a block's biography from block_changes + commits (06 §3). */
@@ -50,7 +51,9 @@ export function diffBlocks(store, docId, fromRev, toRev) {
50
51
  // core/hash.ts serializeTreeEntries): [blockId, rawHashHex, childTreeHashHex,
51
52
  // type, attrs, triviaHashHex]. Diff needs only the first three; whole-document
52
53
  // byte-faithful reconstruction (which also needs trivia + top-level order) lives
53
- // in core/read/document.ts readDocumentAtRevision.
54
+ // in core/read/document.ts readDocumentAtRevision. This map is NOT a rendering
55
+ // of the document: a container's raw already holds its children's text, so
56
+ // joining the values would repeat every nested block (spec/surface §9).
54
57
  function blocksAtRevision(store, docId, revId) {
55
58
  const rev = store.db.prepare("SELECT root_tree FROM revisions WHERE rev_id = ? AND doc_id = ?").get(revId, docId);
56
59
  if (!rev)
@@ -73,10 +76,23 @@ function blocksAtRevision(store, docId, revId) {
73
76
  walk(rev.root_tree.toString("hex"));
74
77
  return out;
75
78
  }
76
- /** `diff_unified` (spec/surface §3): a unified diff of two revisions' rendered
77
- * texts — each revision's live raws joined by `\n` — via {@link unifiedDiff}. */
79
+ /**
80
+ * `diff_unified` (spec/surface §3): a unified diff of the two revisions'
81
+ * **reconstructed file text** — exactly the bytes `docs_read_at` returns for
82
+ * each revision ({@link readDocumentAtRevision}: frontmatter, trivia and
83
+ * top-level order included) — via {@link unifiedDiff}, so hunk line numbers
84
+ * are real file lines. Until 1.4.x this diffed the Merkle-tree walk (every
85
+ * live raw at every depth joined by `\n`), which repeated each nested block
86
+ * after its container's raw and put an appended list item before the list's
87
+ * first item (§9). An unknown revision is {@link RevisionNotFound}.
88
+ */
78
89
  export function diffUnified(store, docId, fromRev, toRev) {
79
- const rendered = (rev) => [...blocksAtRevision(store, docId, rev).values()].join("\n");
90
+ const rendered = (rev) => {
91
+ const at = readDocumentAtRevision(store, docId, rev);
92
+ if (!at)
93
+ throw new RevisionNotFound(docId, rev);
94
+ return at.content;
95
+ };
80
96
  return unifiedDiff(rendered(fromRev), rendered(toRev));
81
97
  }
82
98
  /**
@@ -809,11 +809,11 @@ export function buildServer(ctx) {
809
809
  }
810
810
  });
811
811
  server.registerTool("docs_move", {
812
- description: "Rename a document to a new repo-relative path; block identity and history are preserved. Fails path_taken if the destination exists. Links follow the PATH, not the identity: inbound links written against the old path now dangle (their edges become `phantom:<old path>`, so links_stale reports them), and links already written against the new path start resolving to this doc. The result lists those `dangling` inbound links ({doc, path, block, target, anchor, field?} per occurrence; block null = a frontmatter relation). Pass `retarget_inbound:true` to rewrite them in the same call — a destination-aware rewrite (anchors/link text/code spans preserved, absolute vs relative style kept) applied as one CAS-checked changeset, after which `dangling` holds only what could not be rewritten (frontmatter relations) and `retargeted` lists the touched blocks/docs. dry_run:true validates and returns the plan — `dangling`, `retargeted`, and the per-file `diffs` (the old path emptied, the new path filled, plus any rewritten sources) — without renaming or committing.",
812
+ description: "Rename a document to a new repo-relative path; block identity and history are preserved, and the inbound links FOLLOW it: every link written against the old path (Markdown links/images, wikilinks, bare-path inline fields — anchors, link text, titles and code spans preserved, absolute vs relative style kept) is rewritten to the new path in the same call as one CAS-checked changeset, so a move never leaves the graph broken. Fails path_taken if the destination exists. The result carries `retargeted` (the touched blocks/docs) and `dangling` — what could NOT be rewritten, i.e. frontmatter relations (block null; fix with docs_set_meta). Pass `retarget_inbound:false` to opt out for the rarer intent that the old path become unbound: the links are left as written, listed in `dangling` ({doc, path, block, target, anchor, field?} per occurrence) and reported by links_stale (their edges become `phantom:<old path>`); links already written against the new path start resolving to this doc either way. dry_run:true validates and returns the plan — `dangling`, `retargeted`, and the per-file `diffs` (the old path emptied, the new path filled, plus the rewritten sources) — without renaming or committing.",
813
813
  inputSchema: { doc: z.string(), to_path: z.string(), retarget_inbound: z.boolean().optional(), dry_run: z.boolean().optional(), ...REPO_ARG },
814
814
  }, async (args) => {
815
815
  try {
816
- return docResult(docsMove(store, docCtx(repoScope(args.repo), args.dry_run), args.doc, args.to_path, { retargetInbound: args.retarget_inbound === true }), args.dry_run);
816
+ return docResult(docsMove(store, docCtx(repoScope(args.repo), args.dry_run), args.doc, args.to_path, { retargetInbound: args.retarget_inbound !== false }), args.dry_run);
817
817
  }
818
818
  catch (e) {
819
819
  return fail(e);
@@ -940,7 +940,7 @@ export function buildServer(ctx) {
940
940
  }
941
941
  });
942
942
  server.registerTool("diff_unified", {
943
- description: "Unified diff between two revisions of a document (the `omg diff` rendering): returns `{ doc, path, from, to, diff }` where `diff` is a unified diff of the two rendered texts (Myers shortest edit script; `@@ -a,b +c,d @@` hunks with 3 lines of context; lines prefixed `-`, `+` or a space; no file header; identical texts → \"\"). `from_rev`/`to_rev` default to the previous and current revisions ('what did the last commit change here'). Contrast `diff`, which returns block-grain added/removed/changed entries.",
943
+ description: "Unified diff between two revisions of a document (the `omg diff` rendering): returns `{ doc, path, from, to, diff }` where `diff` is a unified diff of the two revisions' file texts — for each revision exactly the bytes docs_read_at returns, so hunk line numbers are real file lines (Myers shortest edit script; `@@ -a,b +c,d @@` hunks with 3 lines of context; lines prefixed `-`, `+` or a space; no file header; identical texts → \"\"). `from_rev`/`to_rev` default to the previous and current revisions ('what did the last commit change here'). Contrast `diff`, which returns block-grain added/removed/changed entries.",
944
944
  inputSchema: { doc: z.string(), from_rev: z.string().optional(), to_rev: z.string().optional(), ...REPO_ARG },
945
945
  }, async (args) => {
946
946
  try {
@@ -40,22 +40,30 @@ export interface DocOpResult {
40
40
  }
41
41
  export interface DocMoveOptions {
42
42
  /**
43
- * Also rewrite the inbound links that named the OLD path so they point at the
44
- * new one — a destination-aware rewrite (anchors, link text, titles, and code
43
+ * Rewrite the inbound links that named the OLD path so they point at the new
44
+ * one — a destination-aware rewrite (anchors, link text, titles, and code
45
45
  * spans preserved; absolute/relative style kept) applied as a CAS-checked
46
- * `update` per source block in one follow-up api changeset. Frontmatter-level
47
- * relations are not rewritten (reported in `dangling`).
46
+ * `update` per source block in one follow-up api changeset. **Default `true`**
47
+ * (spec/mutate 1.3): a move means "same document, new place", so the authored
48
+ * links follow it; pass `false` for the rarer intent that the old path become
49
+ * unbound. Frontmatter-level relations are never rewritten (reported in
50
+ * `dangling`).
48
51
  */
49
52
  retargetInbound?: boolean;
50
53
  }
51
54
  export interface DocMoveResult extends DocOpResult {
52
55
  /**
53
56
  * Inbound links still pointing at the OLD path after this call — the source
54
- * blocks `links_stale` will now report (empty when `retargetInbound` rewrote
55
- * them all). Computed from the open edge index before the move.
57
+ * blocks `links_stale` will now report (with the default retarget: only the
58
+ * frontmatter relations, which are never rewritten). Computed from the open
59
+ * edge index before the move.
56
60
  */
57
61
  dangling: InboundLink[];
58
- /** With `retargetInbound`: the source blocks rewritten and the docs re-ingested. */
62
+ /**
63
+ * The source blocks rewritten and the docs re-ingested (empty lists when the
64
+ * rewrite ran but only frontmatter relations linked in); `null` when the
65
+ * retarget was opted out (`retargetInbound: false`) or nothing linked in.
66
+ */
59
67
  retargeted: {
60
68
  blocks: string[];
61
69
  docs: string[];
@@ -68,8 +76,10 @@ export declare function docsCreate(store: Store, ctx: DocOpContext, path: string
68
76
  * index follows the path, not the identity: links written against the OLD path
69
77
  * now dangle (their open edges are re-pointed at `phantom:<old>`, exactly what
70
78
  * re-extracting the source would produce, so `links_stale` sees them), and
71
- * phantom edges already written against the NEW path are adopted. The result
72
- * lists the dangling inbound links; `retargetInbound` rewrites them.
79
+ * phantom edges already written against the NEW path are adopted. By default
80
+ * the dangling inbound links are then rewritten to the new path in one
81
+ * follow-up changeset (spec/mutate 1.3); `retargetInbound: false` leaves them
82
+ * as written and the result lists them.
73
83
  */
74
84
  export declare function docsMove(store: Store, ctx: DocOpContext, docRef: string, toPath: string, opts?: DocMoveOptions): DocMoveResult;
75
85
  /** docs_delete: mark a document deleted and remove its file (resurrection-poolable). */
@@ -60,10 +60,13 @@ export function docsCreate(store, ctx, path, markdown, frontmatter) {
60
60
  * index follows the path, not the identity: links written against the OLD path
61
61
  * now dangle (their open edges are re-pointed at `phantom:<old>`, exactly what
62
62
  * re-extracting the source would produce, so `links_stale` sees them), and
63
- * phantom edges already written against the NEW path are adopted. The result
64
- * lists the dangling inbound links; `retargetInbound` rewrites them.
63
+ * phantom edges already written against the NEW path are adopted. By default
64
+ * the dangling inbound links are then rewritten to the new path in one
65
+ * follow-up changeset (spec/mutate 1.3); `retargetInbound: false` leaves them
66
+ * as written and the result lists them.
65
67
  */
66
68
  export function docsMove(store, ctx, docRef, toPath, opts = {}) {
69
+ const retargetInbound = opts.retargetInbound !== false;
67
70
  const info = findDocByRef(store, ctx.repoId, docRef);
68
71
  if (!info)
69
72
  throw new MutationError("doc_missing", `no document ${docRef}`);
@@ -79,7 +82,7 @@ export function docsMove(store, ctx, docRef, toPath, opts = {}) {
79
82
  const content = docStore.read(info.path) ?? "";
80
83
  const diffs = { [info.path]: { before: content, after: "" }, [toRel]: { before: "", after: content } };
81
84
  const planned = { docId: info.docId, path: toRel, committed: false, diffs };
82
- if (!opts.retargetInbound || inbound.length === 0)
85
+ if (!retargetInbound || inbound.length === 0)
83
86
  return { ...planned, dangling: inbound, retargeted: null };
84
87
  const plan = planInboundRetarget(store, info.docId, info.path, toRel, inbound);
85
88
  if (plan.ops.length > 0) {
@@ -134,7 +137,7 @@ export function docsMove(store, ctx, docRef, toPath, opts = {}) {
134
137
  }
135
138
  return { docId: info.docId, path: toRel, committed: true };
136
139
  });
137
- if (!opts.retargetInbound || inbound.length === 0)
140
+ if (!retargetInbound || inbound.length === 0)
138
141
  return { ...moved, dangling: inbound, retargeted: null };
139
142
  // Rewrite the dangling links block by block (one coalesced update per block,
140
143
  // CAS on the block's current hash) and apply as one changeset; the re-ingest
@@ -6,9 +6,9 @@ export declare const SPEC_VERSIONS: {
6
6
  readonly properties: "1.1";
7
7
  readonly graph: "1.1";
8
8
  readonly search: "1.2";
9
- readonly mutate: "1.2";
9
+ readonly mutate: "1.3";
10
10
  readonly sync: "1.3";
11
11
  readonly surface: "1.4";
12
- readonly cli: "1.1";
12
+ readonly cli: "1.2";
13
13
  };
14
14
  export type SpecName = keyof typeof SPEC_VERSIONS;
@@ -17,9 +17,9 @@ export const SPEC_VERSIONS = {
17
17
  properties: "1.1",
18
18
  graph: "1.1",
19
19
  search: "1.2",
20
- mutate: "1.2",
20
+ mutate: "1.3",
21
21
  sync: "1.3",
22
22
  surface: "1.4",
23
- cli: "1.1",
23
+ cli: "1.2",
24
24
  };
25
25
  //# sourceMappingURL=spec-versions.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@omgbase/core",
3
- "version": "0.5.0",
3
+ "version": "0.6.1",
4
4
  "description": "Open Markdown Graph Base — embedded engine",
5
5
  "license": "MIT",
6
6
  "repository": {