@omgbase/core 0.4.1 → 0.6.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.
@@ -1,4 +1,6 @@
1
1
  export { VERSION } from "./core/index.js";
2
+ export { versionInfo, coreVersion, nearestPackageVersion, COMPONENT_PACKAGES, type VersionInfo, type HostInfo } from "./version.js";
3
+ export { SPEC_VERSIONS, type SpecName } from "./spec-versions.js";
2
4
  export type { FormatAdapter, AdapterEdge, ProjectedNode, EmbeddingChunk, ReconcileHints } from "./format/adapter.js";
3
5
  export { AdapterCapability } from "./format/adapter.js";
4
6
  export { registerAdapter, adapterForFormat, adapterForPath, registeredFormats, detectFormat } from "./format/registry.js";
package/dist/src/index.js CHANGED
@@ -1,4 +1,6 @@
1
1
  export { VERSION } from "./core/index.js";
2
+ export { versionInfo, coreVersion, nearestPackageVersion, COMPONENT_PACKAGES } from "./version.js";
3
+ export { SPEC_VERSIONS } from "./spec-versions.js";
2
4
  export { AdapterCapability } from "./format/adapter.js";
3
5
  export { registerAdapter, adapterForFormat, adapterForPath, registeredFormats, detectFormat } from "./format/registry.js";
4
6
  export { markdownAdapter, MARKDOWN_FORMAT } from "./format/markdown.js";
@@ -1,5 +1,6 @@
1
1
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
2
  import type { Store } from "../core/store/store.js";
3
+ import { type HostInfo } from "../version.js";
3
4
  export interface ServerContext {
4
5
  store: Store;
5
6
  /** The DEFAULT repo for calls that omit `repo` (ADR-014). Tools accept an
@@ -26,5 +27,12 @@ export interface ServerContext {
26
27
  * embedding happens off it. Absent ⇒ mutations don't auto-drain.
27
28
  */
28
29
  onMutation?: () => void;
30
+ /**
31
+ * The serving binary's identity for the `version` tool (spec/surface §4): its
32
+ * own version, build info and the module its component packages resolve from.
33
+ * Set by `omg mcp`; absent for a library embedding (the tool then reports the
34
+ * engine's own version and whatever components resolve from core).
35
+ */
36
+ host?: HostInfo;
29
37
  }
30
38
  export declare function buildServer(ctx: ServerContext): McpServer;
@@ -23,6 +23,7 @@ import { resolve as resolveThing } from "../search/resolve.js";
23
23
  import { reposStatus, syncStatus, reposList } from "../sync/admin.js";
24
24
  import { observeFile, observeMany, observeDelete } from "../sync/observe.js";
25
25
  import { QUERY_SYNTAX } from "./reference.js";
26
+ import { versionInfo } from "../version.js";
26
27
  // Kernel-op schemas (04 §1) published as the `apply` tool's op grammar. Giving
27
28
  // each op a real schema (rather than z.any()) makes the required fields
28
29
  // discoverable in the tool's JSON schema and turns a malformed op — e.g. an
@@ -808,11 +809,11 @@ export function buildServer(ctx) {
808
809
  }
809
810
  });
810
811
  server.registerTool("docs_move", {
811
- 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.",
812
813
  inputSchema: { doc: z.string(), to_path: z.string(), retarget_inbound: z.boolean().optional(), dry_run: z.boolean().optional(), ...REPO_ARG },
813
814
  }, async (args) => {
814
815
  try {
815
- 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);
816
817
  }
817
818
  catch (e) {
818
819
  return fail(e);
@@ -1069,6 +1070,20 @@ export function buildServer(ctx) {
1069
1070
  return fail(e);
1070
1071
  }
1071
1072
  });
1073
+ // Which engine and which versions (spec/surface §4, 1.4). No repo scope: the
1074
+ // answer is about the binary and its database, not a repo.
1075
+ server.registerTool("version", {
1076
+ description: "Which engine and which versions you are talking to: `{ engine: \"typescript\"|\"rust\", version, components: { <package>: <version> }, specs: { oqx, format, reconcile, store, properties, graph, search, mutate, sync, surface, cli }, schema, mcp: { protocol, sdk? }, runtime, commit, built }`. " +
1077
+ "`version` is the serving binary's own; `components` every omgbase package it is built from; `specs` the spec versions it implements (compile-time constants); `schema` the database's `PRAGMA user_version`; `commit`/`built` the build's git revision and RFC 3339 time (null when unknown). No arguments, no repo scope.",
1078
+ inputSchema: {},
1079
+ }, async () => {
1080
+ try {
1081
+ return ok(versionInfo(store, ctx.host));
1082
+ }
1083
+ catch (e) {
1084
+ return fail(e);
1085
+ }
1086
+ });
1072
1087
  return server;
1073
1088
  }
1074
1089
  //# sourceMappingURL=server.js.map
@@ -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
@@ -0,0 +1,14 @@
1
+ export declare const SPEC_VERSIONS: {
2
+ readonly oqx: "0.13";
3
+ readonly format: "0.2";
4
+ readonly reconcile: "2.3";
5
+ readonly store: "13.5";
6
+ readonly properties: "1.1";
7
+ readonly graph: "1.1";
8
+ readonly search: "1.2";
9
+ readonly mutate: "1.3";
10
+ readonly sync: "1.3";
11
+ readonly surface: "1.4";
12
+ readonly cli: "1.2";
13
+ };
14
+ export type SpecName = keyof typeof SPEC_VERSIONS;
@@ -0,0 +1,25 @@
1
+ import { LANGUAGE_VERSION } from "@omgbase/oqx";
2
+ // The spec versions this reference implements (spec/surface §4, the `version`
3
+ // tool's `specs`): one compile-time constant per `spec/<x>/VERSION`, NOT a file
4
+ // read — a published package has no `spec/` next to it. Hand-maintained; the
5
+ // test beside this module (`spec-versions.test.ts`) asserts every entry equals
6
+ // the corresponding `spec/<x>/VERSION` file and that no spec is missing, so a
7
+ // spec bump that forgets this map fails the build. `oqx` is the one entry with
8
+ // a constant of its own: `@omgbase/oqx`'s `LANGUAGE_VERSION`.
9
+ //
10
+ // Key order is the order spec/surface §4 lists them (the CLI prints `specs` in
11
+ // this order).
12
+ export const SPEC_VERSIONS = {
13
+ oqx: LANGUAGE_VERSION,
14
+ format: "0.2",
15
+ reconcile: "2.3",
16
+ store: "13.5",
17
+ properties: "1.1",
18
+ graph: "1.1",
19
+ search: "1.2",
20
+ mutate: "1.3",
21
+ sync: "1.3",
22
+ surface: "1.4",
23
+ cli: "1.2",
24
+ };
25
+ //# sourceMappingURL=spec-versions.js.map
@@ -0,0 +1,39 @@
1
+ import type { Store } from "./core/store/store.js";
2
+ import { SPEC_VERSIONS } from "./spec-versions.js";
3
+ export interface HostInfo {
4
+ /** the serving binary's own version — `omgbase`'s package.json (what `--version` prints) */
5
+ version: string;
6
+ /** the build's short git revision, from `build-info.json`; null when unknown */
7
+ commit: string | null;
8
+ /** the build's RFC 3339 time, from `build-info.json`; null when unknown */
9
+ built: string | null;
10
+ /** the host module's `import.meta.url`: the component packages are resolved from its dependency graph */
11
+ resolveFrom?: string;
12
+ }
13
+ export interface VersionInfo {
14
+ engine: "typescript";
15
+ version: string;
16
+ /** every omgbase package the binary is built from, keys sorted bytewise */
17
+ components: Record<string, string>;
18
+ specs: typeof SPEC_VERSIONS;
19
+ /** `PRAGMA user_version` of the open store; null without one */
20
+ schema: number | null;
21
+ mcp: {
22
+ protocol: string;
23
+ sdk: string;
24
+ };
25
+ runtime: string;
26
+ commit: string | null;
27
+ built: string | null;
28
+ }
29
+ /** The omgbase packages the reference binary is built from (spec/surface §4). */
30
+ export declare const COMPONENT_PACKAGES: readonly ["omgbase", "@omgbase/core", "@omgbase/oqx", "@omgbase/sync", "@omgbase/fs-adapter"];
31
+ /** The nearest `package.json` named `name` at or above `fromUrl`'s directory, or null. */
32
+ export declare function nearestPackageVersion(fromUrl: string, name: string): string | null;
33
+ /** The engine's own package version (`@omgbase/core`), from the manifest above this module. */
34
+ export declare function coreVersion(): string;
35
+ /**
36
+ * The `version` result. `store` supplies `schema`; `host` the binary's own
37
+ * version, build info and resolution anchor (see the module comment).
38
+ */
39
+ export declare function versionInfo(store?: Store | null, host?: HostInfo): VersionInfo;
@@ -0,0 +1,103 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { LATEST_PROTOCOL_VERSION } from "@modelcontextprotocol/sdk/types.js";
5
+ import { SPEC_VERSIONS } from "./spec-versions.js";
6
+ /** The omgbase packages the reference binary is built from (spec/surface §4). */
7
+ export const COMPONENT_PACKAGES = ["omgbase", "@omgbase/core", "@omgbase/oqx", "@omgbase/sync", "@omgbase/fs-adapter"];
8
+ const MCP_SDK = "@modelcontextprotocol/sdk";
9
+ /** The nearest `package.json` named `name` at or above `fromUrl`'s directory, or null. */
10
+ export function nearestPackageVersion(fromUrl, name) {
11
+ let dir = dirname(fileURLToPath(fromUrl));
12
+ for (;;) {
13
+ const candidate = join(dir, "package.json");
14
+ if (existsSync(candidate)) {
15
+ const pkg = readManifest(candidate);
16
+ if (pkg?.name === name && typeof pkg.version === "string")
17
+ return pkg.version;
18
+ }
19
+ const parent = dirname(dir);
20
+ if (parent === dir)
21
+ return null;
22
+ dir = parent;
23
+ }
24
+ }
25
+ function readManifest(path) {
26
+ try {
27
+ return JSON.parse(readFileSync(path, "utf8"));
28
+ }
29
+ catch {
30
+ return null;
31
+ }
32
+ }
33
+ /**
34
+ * `name`'s installed version as seen from `anchors` (module URLs). Node's own
35
+ * resolver is no use here: the omgbase packages are ESM-only (`exports` with an
36
+ * `import` condition alone), which a CJS `require.resolve` cannot see, and no
37
+ * package exports its `package.json`. So this walks what the algorithm would
38
+ * absent `exports`: the anchor's own package first (a package cannot name
39
+ * itself), then `node_modules/<name>/package.json` at each ancestor directory —
40
+ * pnpm's symlinked layout, npm's hoisted global install and a plain checkout all
41
+ * put the manifest there. Anchors are tried in turn; null when none has it.
42
+ */
43
+ function resolvePackageVersion(name, anchors) {
44
+ for (const anchor of anchors) {
45
+ const own = nearestPackageVersion(anchor, name);
46
+ if (own !== null)
47
+ return own;
48
+ let dir = dirname(fileURLToPath(anchor));
49
+ for (;;) {
50
+ const pkg = readManifest(join(dir, "node_modules", name, "package.json"));
51
+ if (pkg?.name === name && typeof pkg.version === "string")
52
+ return pkg.version;
53
+ const parent = dirname(dir);
54
+ if (parent === dir)
55
+ break;
56
+ dir = parent;
57
+ }
58
+ }
59
+ return null;
60
+ }
61
+ /** The MCP SDK's version: from the module core itself imports, walking up to its manifest (its `package.json` is not exported). */
62
+ function mcpSdkVersion() {
63
+ try {
64
+ return nearestPackageVersion(import.meta.resolve(`${MCP_SDK}/types.js`), MCP_SDK);
65
+ }
66
+ catch {
67
+ return null;
68
+ }
69
+ }
70
+ /** The engine's own package version (`@omgbase/core`), from the manifest above this module. */
71
+ export function coreVersion() {
72
+ return nearestPackageVersion(import.meta.url, "@omgbase/core") ?? "0.0.0";
73
+ }
74
+ /**
75
+ * The `version` result. `store` supplies `schema`; `host` the binary's own
76
+ * version, build info and resolution anchor (see the module comment).
77
+ */
78
+ export function versionInfo(store, host) {
79
+ const anchors = [...(host?.resolveFrom ? [host.resolveFrom] : []), import.meta.url];
80
+ const components = {};
81
+ for (const name of COMPONENT_PACKAGES) {
82
+ // The host IS `omgbase`: its version is what it says it is (the resolver
83
+ // would find the same manifest above `resolveFrom`, but the host is explicit).
84
+ const v = name === "omgbase" && host ? host.version : resolvePackageVersion(name, anchors);
85
+ if (v !== null)
86
+ components[name] = v;
87
+ }
88
+ const sorted = Object.fromEntries(Object.entries(components).sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0)));
89
+ const version = host?.version ?? components["omgbase"] ?? coreVersion();
90
+ const schema = store ? store.db.pragma("user_version", { simple: true }) : null;
91
+ return {
92
+ engine: "typescript",
93
+ version,
94
+ components: sorted,
95
+ specs: SPEC_VERSIONS,
96
+ schema,
97
+ mcp: { protocol: LATEST_PROTOCOL_VERSION, sdk: mcpSdkVersion() ?? "unknown" },
98
+ runtime: `node ${process.versions.node}`,
99
+ commit: host?.commit ?? null,
100
+ built: host?.built ?? null,
101
+ };
102
+ }
103
+ //# sourceMappingURL=version.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@omgbase/core",
3
- "version": "0.4.1",
3
+ "version": "0.6.0",
4
4
  "description": "Open Markdown Graph Base — embedded engine",
5
5
  "license": "MIT",
6
6
  "repository": {