@omgbase/core 0.1.0 → 0.3.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.
- package/README.md +172 -0
- package/dist/src/cli/eval-matcher.d.ts +6 -1
- package/dist/src/cli/eval-matcher.js +14 -2
- package/dist/src/core/clock.d.ts +9 -0
- package/dist/src/core/clock.js +46 -0
- package/dist/src/core/hash.d.ts +25 -1
- package/dist/src/core/hash.js +110 -10
- package/dist/src/core/ids.d.ts +12 -0
- package/dist/src/core/ids.js +43 -1
- package/dist/src/core/ingest.d.ts +22 -2
- package/dist/src/core/ingest.js +92 -18
- package/dist/src/core/parse/parse.d.ts +1 -1
- package/dist/src/core/parse/parse.js +31 -9
- package/dist/src/core/store/nodes.d.ts +13 -0
- package/dist/src/core/store/nodes.js +24 -0
- package/dist/src/core/store/writers.d.ts +6 -0
- package/dist/src/core/store/writers.js +1 -0
- package/dist/src/core/utf8.d.ts +29 -0
- package/dist/src/core/utf8.js +96 -0
- package/dist/src/format/adapter.d.ts +7 -1
- package/dist/src/format/markdown.js +36 -41
- package/dist/src/graph/extract.d.ts +41 -1
- package/dist/src/graph/extract.js +59 -1
- package/dist/src/graph/history.d.ts +39 -1
- package/dist/src/graph/history.js +154 -20
- package/dist/src/index.d.ts +10 -6
- package/dist/src/index.js +11 -6
- package/dist/src/mcp/graph.js +8 -4
- package/dist/src/mcp/reference.d.ts +1 -1
- package/dist/src/mcp/reference.js +17 -6
- package/dist/src/mcp/server.js +15 -10
- package/dist/src/mutate/apply.d.ts +9 -0
- package/dist/src/mutate/apply.js +24 -11
- package/dist/src/mutate/docs.d.ts +2 -0
- package/dist/src/mutate/docs.js +10 -6
- package/dist/src/mutate/known-ids.js +11 -3
- package/dist/src/mutate/macros.js +9 -2
- package/dist/src/mutate/ops.d.ts +5 -4
- package/dist/src/mutate/ops.js +62 -15
- package/dist/src/mutate/plan-update.d.ts +7 -1
- package/dist/src/mutate/plan-update.js +3 -1
- package/dist/src/oqx-js/context.js +57 -7
- package/dist/src/reconcile/eval/generator.d.ts +24 -1
- package/dist/src/reconcile/eval/generator.js +248 -0
- package/dist/src/reconcile/eval/metrics.d.ts +33 -9
- package/dist/src/reconcile/eval/metrics.js +55 -32
- package/dist/src/reconcile/flatten.js +7 -5
- package/dist/src/reconcile/phase5.d.ts +2 -1
- package/dist/src/reconcile/phase5.js +27 -10
- package/dist/src/reconcile/phase6a.js +17 -5
- package/dist/src/reconcile/phases.d.ts +44 -6
- package/dist/src/reconcile/phases.js +0 -0
- package/dist/src/reconcile/reconcile.js +16 -9
- package/dist/src/reconcile/types.d.ts +4 -1
- package/dist/src/reconcile/types.js +11 -2
- package/dist/src/search/external.d.ts +10 -0
- package/dist/src/search/external.js +18 -1
- package/dist/src/search/provider.d.ts +7 -3
- package/dist/src/search/provider.js +8 -7
- package/dist/src/search/rrf.js +4 -0
- package/dist/src/search/tasks.d.ts +28 -0
- package/dist/src/search/tasks.js +36 -23
- package/dist/src/search/vector.js +30 -14
- package/dist/src/sync/attach.d.ts +7 -2
- package/dist/src/sync/attach.js +10 -8
- package/dist/src/sync/checkpoint.d.ts +18 -1
- package/dist/src/sync/checkpoint.js +31 -29
- package/dist/src/sync/driver.d.ts +15 -4
- package/dist/src/sync/driver.js +18 -43
- package/dist/src/sync/external-source.js +7 -1
- package/dist/src/sync/freshness.d.ts +73 -15
- package/dist/src/sync/freshness.js +128 -97
- package/dist/src/sync/fs-util.d.ts +19 -0
- package/dist/src/sync/fs-util.js +24 -1
- package/dist/src/sync/observe.d.ts +53 -5
- package/dist/src/sync/observe.js +162 -21
- package/dist/src/sync/reconciling-ingest.d.ts +42 -2
- package/dist/src/sync/reconciling-ingest.js +87 -13
- package/dist/src/sync/recovery.d.ts +5 -1
- package/dist/src/sync/recovery.js +8 -8
- package/dist/src/sync/workspace.d.ts +11 -3
- package/dist/src/sync/workspace.js +39 -30
- package/dist/src/sync/writer-lock.js +16 -26
- package/package.json +3 -3
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
import { AdapterCapability } from "./adapter.js";
|
|
3
3
|
import { parseTree, assertFullCoverage } from "../core/parse/tree.js";
|
|
4
4
|
import { render } from "../core/parse/render.js";
|
|
5
|
-
import { extractFromBlock, extractFromFrontmatter,
|
|
6
|
-
import { parse as parseYaml } from "yaml";
|
|
5
|
+
import { extractFromBlock, extractFromFrontmatter, ownText, scanText, INLINE_FIELD_BRACKETED, INLINE_FIELD_LINE } from "../graph/extract.js";
|
|
7
6
|
export const MARKDOWN_FORMAT = "markdown";
|
|
8
7
|
function toAdapterEdge(e) {
|
|
9
8
|
return {
|
|
@@ -23,7 +22,6 @@ export const markdownAdapter = {
|
|
|
23
22
|
AdapterCapability.Parse,
|
|
24
23
|
AdapterCapability.Render,
|
|
25
24
|
AdapterCapability.ExtractEdges,
|
|
26
|
-
AdapterCapability.ExtractMetadata,
|
|
27
25
|
AdapterCapability.ComputeProperties,
|
|
28
26
|
AdapterCapability.StructuralMutation,
|
|
29
27
|
AdapterCapability.ProjectNodes,
|
|
@@ -37,14 +35,15 @@ export const markdownAdapter = {
|
|
|
37
35
|
extractEdges(blocks, metadata) {
|
|
38
36
|
const edges = [];
|
|
39
37
|
// Walk every block (recursing into list/blockquote/table nesting) and scan
|
|
40
|
-
// its
|
|
41
|
-
//
|
|
42
|
-
//
|
|
43
|
-
//
|
|
44
|
-
//
|
|
38
|
+
// its OWN text for links (spec/graph §1: the raw with the children blanked,
|
|
39
|
+
// so a link in an item is the item's edge only). srcBlock is the block's
|
|
40
|
+
// OWN assigned id — the caller passes id-assigned blocks (blockId zipped
|
|
41
|
+
// on), mirroring projectNodes; a block-grain edge without it is useless, so
|
|
42
|
+
// fall back to "" only for the pre-identity callers. extractFromBlock
|
|
43
|
+
// returns [] for link-free blocks, so no per-block gate is needed.
|
|
45
44
|
const walk = (list) => {
|
|
46
45
|
for (const block of list) {
|
|
47
|
-
for (const e of extractFromBlock(block.blockId ?? "", block.type, block
|
|
46
|
+
for (const e of extractFromBlock(block.blockId ?? "", block.type, ownText(block))) {
|
|
48
47
|
edges.push(toAdapterEdge(e));
|
|
49
48
|
}
|
|
50
49
|
if (block.children.length > 0)
|
|
@@ -58,24 +57,17 @@ export const markdownAdapter = {
|
|
|
58
57
|
}
|
|
59
58
|
return edges;
|
|
60
59
|
},
|
|
61
|
-
extractMetadata
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
return parsed && typeof parsed === "object" && !Array.isArray(parsed)
|
|
68
|
-
? parsed
|
|
69
|
-
: null;
|
|
70
|
-
}
|
|
71
|
-
catch {
|
|
72
|
-
return null;
|
|
73
|
-
}
|
|
74
|
-
},
|
|
60
|
+
// No `extractMetadata`: Markdown frontmatter is the `frontmatter` block of the
|
|
61
|
+
// parsed tree and nothing else (spec/properties §3.1), so ingest parses that
|
|
62
|
+
// block's YAML itself. The regex-over-the-source form this adapter used to
|
|
63
|
+
// carry disagreed with the block model — `---\nfoo: 1\n---bar\n` has no
|
|
64
|
+
// frontmatter block yet matched, and a fence containing a line that starts
|
|
65
|
+
// with `---` was cut short (spec/properties §8 "Fixed").
|
|
75
66
|
projectNodes(blocks) {
|
|
76
67
|
const nodes = [];
|
|
77
|
-
//
|
|
78
|
-
//
|
|
68
|
+
// Span of a regex match within the block's raw, as string indices (ingest
|
|
69
|
+
// converts to bytes at the store boundary — spec/graph §2.3). It locates the
|
|
70
|
+
// feature inside its block and disambiguates same-kind nodes (13/nodes).
|
|
79
71
|
const span = (m) => ({
|
|
80
72
|
spanStart: m.index ?? 0,
|
|
81
73
|
spanEnd: (m.index ?? 0) + m[0].length,
|
|
@@ -85,12 +77,13 @@ export const markdownAdapter = {
|
|
|
85
77
|
// Anchor to the block's OWN assigned id (zipped on by ingest); fall back
|
|
86
78
|
// to the parent's id, then "root", for pre-identity/parse-time callers.
|
|
87
79
|
const id = b.blockId || parentId || "root";
|
|
88
|
-
//
|
|
89
|
-
//
|
|
90
|
-
//
|
|
91
|
-
//
|
|
92
|
-
//
|
|
93
|
-
|
|
80
|
+
// The block's own text (spec/graph §1): children blanked, so a feature
|
|
81
|
+
// belongs to the innermost block only; then code masked — a code_fence
|
|
82
|
+
// block projects no link/anchor/field nodes, and inline code spans are
|
|
83
|
+
// blanked (extraction_version x2). Same rule as graph/extract.ts so the
|
|
84
|
+
// md:link / md:wikilink nodes and the `references` edges agree. Both
|
|
85
|
+
// masks are length-preserving, so spans still index the ORIGINAL raw.
|
|
86
|
+
const scan = scanText(b);
|
|
94
87
|
// Links: [text](target)
|
|
95
88
|
for (const m of scan.matchAll(/\[([^\]]*)\]\(([^)\s]+)(?:\s+"[^"]*")?\)/g)) {
|
|
96
89
|
nodes.push({ kind: "md:link", name: m[1], value: m[2], blockId: id, ...span(m) });
|
|
@@ -109,20 +102,22 @@ export const markdownAdapter = {
|
|
|
109
102
|
for (const m of scan.matchAll(anchorRe)) {
|
|
110
103
|
nodes.push({ kind: "md:anchor", name: m[1], blockId: id, ...span(m) });
|
|
111
104
|
}
|
|
112
|
-
// Inline fields (dataview forms
|
|
113
|
-
//
|
|
114
|
-
//
|
|
105
|
+
// Inline fields (dataview forms; the patterns are spec/properties §3.2's,
|
|
106
|
+
// shared with the properties scanner). Two shapes, so a multi-word value
|
|
107
|
+
// is captured whole instead of truncated at the first space:
|
|
108
|
+
// line form `key:: value` — key at line start (after blanks and, since
|
|
109
|
+
// 1.1, an optional list marker + task checkbox), value to
|
|
110
|
+
// end of line; span from the key to the trimmed value's end
|
|
115
111
|
// bracketed `[key:: value]` / `(key:: value)` — value ends at closer
|
|
116
|
-
// Bracketed matches first;
|
|
117
|
-
//
|
|
118
|
-
|
|
119
|
-
for (const m of scan.matchAll(/[[(]([a-z][a-z0-9_]*)::[ \t]*([^\]\n)]*?)[ \t]*[\])]/gi)) {
|
|
112
|
+
// Bracketed matches first; a `[key:: …]`/`(key:: …)` sitting on its own
|
|
113
|
+
// line is not double-counted (a bracket is not `[a-z]`).
|
|
114
|
+
for (const m of scan.matchAll(INLINE_FIELD_BRACKETED)) {
|
|
120
115
|
nodes.push({ kind: "md:inline_field", name: m[1], value: m[2], blockId: id, ...span(m) });
|
|
121
116
|
}
|
|
122
|
-
for (const m of scan.matchAll(
|
|
123
|
-
const start = (m.index ?? 0) +
|
|
117
|
+
for (const m of scan.matchAll(INLINE_FIELD_LINE)) {
|
|
118
|
+
const start = (m.index ?? 0) + m[1].length;
|
|
124
119
|
const end = (m.index ?? 0) + m[0].replace(/[ \t]+$/, "").length;
|
|
125
|
-
nodes.push({ kind: "md:inline_field", name: m[
|
|
120
|
+
nodes.push({ kind: "md:inline_field", name: m[2], value: m[3], blockId: id, spanStart: start, spanEnd: end });
|
|
126
121
|
}
|
|
127
122
|
if (b.children.length > 0)
|
|
128
123
|
walk(b.children, id);
|
|
@@ -11,6 +11,42 @@ export interface ExtractedEdge {
|
|
|
11
11
|
anchor: string | null;
|
|
12
12
|
provenance: Provenance;
|
|
13
13
|
}
|
|
14
|
+
/** Bracketed form: `[key:: value]` / `(key:: value)` anywhere in prose; the value
|
|
15
|
+
* runs to the first closer (either bracket), trimmed of spaces and tabs. */
|
|
16
|
+
export declare const INLINE_FIELD_BRACKETED: RegExp;
|
|
17
|
+
/** Line form (1.1): a key at the start of a line — after blanks and optionally
|
|
18
|
+
* a list marker (`-`/`*`/`+`/`1.`/`1)`) and a task checkbox — with the value
|
|
19
|
+
* to the end of the line, trimmed. Group 1 is the prefix (so a span can start
|
|
20
|
+
* at the key), 2 the key as written, 3 the trimmed value. A bracketed field on
|
|
21
|
+
* its own line is not also a line-form match: `[`/`(` is not `[a-z]`. */
|
|
22
|
+
export declare const INLINE_FIELD_LINE: RegExp;
|
|
23
|
+
interface SpannedBlock {
|
|
24
|
+
raw: string;
|
|
25
|
+
span: {
|
|
26
|
+
start: number;
|
|
27
|
+
end: number;
|
|
28
|
+
};
|
|
29
|
+
children: SpannedBlock[];
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* A block's own text: its `raw` with the span of every DIRECT child blanked to
|
|
33
|
+
* spaces (newlines kept, like `maskCode`, so line structure survives). A child
|
|
34
|
+
* sits at `child.span.start - block.span.start` in the parent's raw (spans are
|
|
35
|
+
* source offsets; spec/format §1 inv. 3 puts a child inside its parent). That
|
|
36
|
+
* offset is trusted only when the parent's raw actually reads `child.raw`
|
|
37
|
+
* there; otherwise — callers that hand in blocks without real spans (a tree
|
|
38
|
+
* rebuilt from store rows carries `{0,0}`), which the invariant makes impossible
|
|
39
|
+
* for a parsed tree — the child is located as the first occurrence of its raw
|
|
40
|
+
* at or after the previous child's end, and skipped when not found. Length-
|
|
41
|
+
* preserving: offsets into the result are offsets into `raw`.
|
|
42
|
+
*/
|
|
43
|
+
export declare function ownText(block: SpannedBlock): string;
|
|
44
|
+
/** What the scanners see for a block: nothing for a `code_fence`, else
|
|
45
|
+
* `maskCode(ownText(block))` — own text first (a child's fence is blanked
|
|
46
|
+
* either way), code second. Same length as `raw`. */
|
|
47
|
+
export declare function scanText(block: SpannedBlock & {
|
|
48
|
+
type: string;
|
|
49
|
+
}): string;
|
|
14
50
|
/**
|
|
15
51
|
* Resolve a `./foo` / `../bar` destination against the source document's
|
|
16
52
|
* directory (`docDir` = path with the filename stripped, e.g. "a/b/" or "").
|
|
@@ -33,6 +69,10 @@ export declare function normalizeUri(uri: string): string;
|
|
|
33
69
|
*/
|
|
34
70
|
export declare function maskCode(raw: string): string;
|
|
35
71
|
/** Extract edges from a single block's raw markdown. Code is skipped: a
|
|
36
|
-
* `code_fence` block yields nothing, and inline code spans are masked.
|
|
72
|
+
* `code_fence` block yields nothing, and inline code spans are masked. Pass the
|
|
73
|
+
* block's own text (`ownText`) for a container so its children's features are
|
|
74
|
+
* not counted again here — the adapter does; a caller with only a stored raw
|
|
75
|
+
* (no children) passes it as is. */
|
|
37
76
|
export declare function extractFromBlock(blockId: string, blockType: string, rawInput: string): ExtractedEdge[];
|
|
38
77
|
export declare function extractFromFrontmatter(fm: Record<string, unknown>): ExtractedEdge[];
|
|
78
|
+
export {};
|
|
@@ -10,6 +10,12 @@
|
|
|
10
10
|
// (and links_stale no longer reports them as dangling). There is no persisted
|
|
11
11
|
// extraction version — an existing repo re-extracts a document the next time
|
|
12
12
|
// that document is ingested (checkpoint/observe/apply), not via rebuild-index.
|
|
13
|
+
//
|
|
14
|
+
// spec/properties 1.1 + spec/graph 1.1: every scanner runs over the block's
|
|
15
|
+
// OWN text — `maskCode(ownText(block))`, the raw with each direct child's span
|
|
16
|
+
// blanked — so a feature inside a list item belongs to the item (the innermost
|
|
17
|
+
// block that contains it), never also to the list; and the inline-field line
|
|
18
|
+
// form admits a list marker and a task checkbox (`- key:: v`, `- [ ] due:: x`).
|
|
13
19
|
export const EXTRACTION_VERSION = "x2";
|
|
14
20
|
// --- link/inline scanners (operate on a block's raw markdown) ----------------
|
|
15
21
|
const MD_LINK = /(!?)\[[^\]]*\]\(([^)\s]+)(?:\s+"[^"]*")?\)/g; // [t](dest) / 
|
|
@@ -17,6 +23,55 @@ const WIKILINK = /(!?)\[\[([^\]]+)\]\]/g; // [[note]] / [[note#H]] / [[note^ref]
|
|
|
17
23
|
const BARE_URL = /(?<![("[])\bhttps?:\/\/[^\s)>\]]+/g;
|
|
18
24
|
const AUTOLINK = /<(https?:\/\/[^>]+)>/g;
|
|
19
25
|
const INLINE_FIELD = /(?:^|\s)([a-z][a-z0-9_]*)::\s*(\[\[[^\]]+\]\]|\/[^\s]+|https?:\/\/[^\s]+)/gi;
|
|
26
|
+
// --- inline-field forms (spec/properties §3.2; shared with md:inline_field) ---
|
|
27
|
+
/** Bracketed form: `[key:: value]` / `(key:: value)` anywhere in prose; the value
|
|
28
|
+
* runs to the first closer (either bracket), trimmed of spaces and tabs. */
|
|
29
|
+
export const INLINE_FIELD_BRACKETED = /[[(]([a-z][a-z0-9_]*)::[ \t]*([^\]\n)]*?)[ \t]*[\])]/gi;
|
|
30
|
+
/** Line form (1.1): a key at the start of a line — after blanks and optionally
|
|
31
|
+
* a list marker (`-`/`*`/`+`/`1.`/`1)`) and a task checkbox — with the value
|
|
32
|
+
* to the end of the line, trimmed. Group 1 is the prefix (so a span can start
|
|
33
|
+
* at the key), 2 the key as written, 3 the trimmed value. A bracketed field on
|
|
34
|
+
* its own line is not also a line-form match: `[`/`(` is not `[a-z]`. */
|
|
35
|
+
export const INLINE_FIELD_LINE = /^([ \t]*(?:(?:[-*+]|\d{1,9}[.)])[ \t]+(?:\[[ xX]\][ \t]+)?)?)([a-z][a-z0-9_]*)::[ \t]*([^\n]*?)[ \t]*$/gim;
|
|
36
|
+
/**
|
|
37
|
+
* A block's own text: its `raw` with the span of every DIRECT child blanked to
|
|
38
|
+
* spaces (newlines kept, like `maskCode`, so line structure survives). A child
|
|
39
|
+
* sits at `child.span.start - block.span.start` in the parent's raw (spans are
|
|
40
|
+
* source offsets; spec/format §1 inv. 3 puts a child inside its parent). That
|
|
41
|
+
* offset is trusted only when the parent's raw actually reads `child.raw`
|
|
42
|
+
* there; otherwise — callers that hand in blocks without real spans (a tree
|
|
43
|
+
* rebuilt from store rows carries `{0,0}`), which the invariant makes impossible
|
|
44
|
+
* for a parsed tree — the child is located as the first occurrence of its raw
|
|
45
|
+
* at or after the previous child's end, and skipped when not found. Length-
|
|
46
|
+
* preserving: offsets into the result are offsets into `raw`.
|
|
47
|
+
*/
|
|
48
|
+
export function ownText(block) {
|
|
49
|
+
if (block.children.length === 0)
|
|
50
|
+
return block.raw;
|
|
51
|
+
const raw = block.raw;
|
|
52
|
+
let out = raw;
|
|
53
|
+
let cursor = 0;
|
|
54
|
+
for (const child of block.children) {
|
|
55
|
+
const len = child.raw.length;
|
|
56
|
+
if (len === 0)
|
|
57
|
+
continue;
|
|
58
|
+
let at = child.span.start - block.span.start;
|
|
59
|
+
if (!(at >= cursor && at + len <= raw.length && raw.startsWith(child.raw, at))) {
|
|
60
|
+
at = raw.indexOf(child.raw, cursor);
|
|
61
|
+
if (at < 0)
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
out = out.slice(0, at) + blank(child.raw) + out.slice(at + len);
|
|
65
|
+
cursor = at + len;
|
|
66
|
+
}
|
|
67
|
+
return out;
|
|
68
|
+
}
|
|
69
|
+
/** What the scanners see for a block: nothing for a `code_fence`, else
|
|
70
|
+
* `maskCode(ownText(block))` — own text first (a child's fence is blanked
|
|
71
|
+
* either way), code second. Same length as `raw`. */
|
|
72
|
+
export function scanText(block) {
|
|
73
|
+
return block.type === "code_fence" ? "" : maskCode(ownText(block));
|
|
74
|
+
}
|
|
20
75
|
function splitAnchor(target) {
|
|
21
76
|
const hashIdx = target.indexOf("#");
|
|
22
77
|
const caretIdx = target.indexOf("^");
|
|
@@ -161,7 +216,10 @@ export function maskCode(raw) {
|
|
|
161
216
|
return out;
|
|
162
217
|
}
|
|
163
218
|
/** Extract edges from a single block's raw markdown. Code is skipped: a
|
|
164
|
-
* `code_fence` block yields nothing, and inline code spans are masked.
|
|
219
|
+
* `code_fence` block yields nothing, and inline code spans are masked. Pass the
|
|
220
|
+
* block's own text (`ownText`) for a container so its children's features are
|
|
221
|
+
* not counted again here — the adapter does; a caller with only a stored raw
|
|
222
|
+
* (no children) passes it as is. */
|
|
165
223
|
export function extractFromBlock(blockId, blockType, rawInput) {
|
|
166
224
|
const edges = [];
|
|
167
225
|
if (blockType === "code_fence")
|
|
@@ -12,6 +12,12 @@ export interface NodeChange {
|
|
|
12
12
|
export declare function historyNode(store: Store, blockId: string, opts?: {
|
|
13
13
|
limit?: number;
|
|
14
14
|
}): NodeChange[];
|
|
15
|
+
/** A `rev` that names no revision of the document (spec/surface §3: both sides of a diff must exist). The MCP layer maps it to `target_missing` with `{ doc, rev }`. */
|
|
16
|
+
export declare class RevisionNotFound extends Error {
|
|
17
|
+
readonly docId: string;
|
|
18
|
+
readonly rev: string;
|
|
19
|
+
constructor(docId: string, rev: string);
|
|
20
|
+
}
|
|
15
21
|
export interface DiffEntry {
|
|
16
22
|
kind: "added" | "removed" | "changed" | "unchanged";
|
|
17
23
|
blockId: string;
|
|
@@ -19,8 +25,40 @@ export interface DiffEntry {
|
|
|
19
25
|
after?: string;
|
|
20
26
|
}
|
|
21
27
|
export declare function diffBlocks(store: Store, docId: string, fromRev: string, toRev: string): DiffEntry[];
|
|
22
|
-
/**
|
|
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}. */
|
|
23
30
|
export declare function diffUnified(store: Store, docId: string, fromRev: string, toRev: string): string;
|
|
31
|
+
/** How each text becomes lines: `split("\n")` exactly — no trimming, no
|
|
32
|
+
* dropping of a trailing empty element (a raw ending in `\n` yields one) — with
|
|
33
|
+
* a single special case: the empty text has *no* lines (an empty file is zero
|
|
34
|
+
* lines, not one empty line), so a diff from/to nothing is `@@ -0,0 +1,n @@`. */
|
|
35
|
+
export declare function diffLines(text: string): string[];
|
|
36
|
+
/** One step of the edit script: `keep` consumes a line from both sides,
|
|
37
|
+
* `delete` one from the old text, `insert` one from the new text. */
|
|
38
|
+
export interface EditOp {
|
|
39
|
+
kind: "keep" | "delete" | "insert";
|
|
40
|
+
line: string;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Myers' O(ND) shortest edit script (forward, with a per-`d` trace for the
|
|
44
|
+
* backtrack). `V[k]` is the furthest x on diagonal `k = x - y` reachable with
|
|
45
|
+
* `d` edits. The canonical tie rule, identical in both engines: at each step
|
|
46
|
+
* take the diagonal from `k+1` (moving down — an insertion of `b[y]`) when
|
|
47
|
+
* `k == -d || (k != d && V[k-1] < V[k+1])`, else from `k-1` (moving right —
|
|
48
|
+
* a deletion of `a[x]`). On a tie (`V[k-1] == V[k+1]`) that is the deletion.
|
|
49
|
+
*/
|
|
50
|
+
export declare function myersScript(a: string[], b: string[]): EditOp[];
|
|
51
|
+
/**
|
|
52
|
+
* The unified diff of two texts (spec/surface §3): hunks of `DIFF_CONTEXT` (3)
|
|
53
|
+
* lines of context; a change group extends to include the next change when
|
|
54
|
+
* fewer than `2 * DIFF_CONTEXT + 1` unchanged lines separate them (the two
|
|
55
|
+
* contexts touch or overlap). Each hunk is `@@ -a,b +c,d @@` (1-based start
|
|
56
|
+
* and length; a length of 1 is written as the start alone; a length of 0 as
|
|
57
|
+
* `a,0` with `a` the line before the insertion point, `0` at the very top)
|
|
58
|
+
* followed by its lines prefixed `-`, `+` or a space with nothing after the
|
|
59
|
+
* sign; hunks joined by `\n`; no file header; identical texts → `""`.
|
|
60
|
+
*/
|
|
61
|
+
export declare function unifiedDiff(oldText: string, newText: string): string;
|
|
24
62
|
export interface CommitDigest {
|
|
25
63
|
commit: string;
|
|
26
64
|
seq: number;
|
|
@@ -14,6 +14,17 @@ export function historyNode(store, blockId, opts = {}) {
|
|
|
14
14
|
LIMIT ?`)
|
|
15
15
|
.all(blockId, limit);
|
|
16
16
|
}
|
|
17
|
+
/** A `rev` that names no revision of the document (spec/surface §3: both sides of a diff must exist). The MCP layer maps it to `target_missing` with `{ doc, rev }`. */
|
|
18
|
+
export class RevisionNotFound extends Error {
|
|
19
|
+
docId;
|
|
20
|
+
rev;
|
|
21
|
+
constructor(docId, rev) {
|
|
22
|
+
super(`no revision ${JSON.stringify(rev)} for document ${docId}`);
|
|
23
|
+
this.docId = docId;
|
|
24
|
+
this.rev = rev;
|
|
25
|
+
this.name = "RevisionNotFound";
|
|
26
|
+
}
|
|
27
|
+
}
|
|
17
28
|
// Block-grain diff between two revisions of a document: compare block id → raw.
|
|
18
29
|
export function diffBlocks(store, docId, fromRev, toRev) {
|
|
19
30
|
const at = (rev) => blocksAtRevision(store, docId, rev);
|
|
@@ -41,9 +52,9 @@ export function diffBlocks(store, docId, fromRev, toRev) {
|
|
|
41
52
|
// in core/read/document.ts readDocumentAtRevision.
|
|
42
53
|
function blocksAtRevision(store, docId, revId) {
|
|
43
54
|
const rev = store.db.prepare("SELECT root_tree FROM revisions WHERE rev_id = ? AND doc_id = ?").get(revId, docId);
|
|
44
|
-
const out = new Map();
|
|
45
55
|
if (!rev)
|
|
46
|
-
|
|
56
|
+
throw new RevisionNotFound(docId, revId);
|
|
57
|
+
const out = new Map();
|
|
47
58
|
const blob = store.db.prepare("SELECT bytes FROM blobs WHERE hash = ?");
|
|
48
59
|
const treeNode = store.db.prepare("SELECT entries FROM tree_nodes WHERE hash = ?");
|
|
49
60
|
const walk = (treeHashHex) => {
|
|
@@ -61,26 +72,149 @@ function blocksAtRevision(store, docId, revId) {
|
|
|
61
72
|
walk(rev.root_tree.toString("hex"));
|
|
62
73
|
return out;
|
|
63
74
|
}
|
|
64
|
-
/**
|
|
75
|
+
/** `diff_unified` (spec/surface §3): a unified diff of two revisions' rendered
|
|
76
|
+
* texts — each revision's live raws joined by `\n` — via {@link unifiedDiff}. */
|
|
65
77
|
export function diffUnified(store, docId, fromRev, toRev) {
|
|
66
|
-
const rendered = (rev) =>
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
78
|
+
const rendered = (rev) => [...blocksAtRevision(store, docId, rev).values()].join("\n");
|
|
79
|
+
return unifiedDiff(rendered(fromRev), rendered(toRev));
|
|
80
|
+
}
|
|
81
|
+
// ---- unified diff (spec/surface §3) ------------------------------------------
|
|
82
|
+
// A line-grain unified diff with a deterministic Myers script, so both engines
|
|
83
|
+
// (this reference and crates/omgbase-surface/src/history.rs) produce the same
|
|
84
|
+
// bytes for the same two texts. Kept pure so it can be pinned by unit tests.
|
|
85
|
+
/** How each text becomes lines: `split("\n")` exactly — no trimming, no
|
|
86
|
+
* dropping of a trailing empty element (a raw ending in `\n` yields one) — with
|
|
87
|
+
* a single special case: the empty text has *no* lines (an empty file is zero
|
|
88
|
+
* lines, not one empty line), so a diff from/to nothing is `@@ -0,0 +1,n @@`. */
|
|
89
|
+
export function diffLines(text) {
|
|
90
|
+
return text === "" ? [] : text.split("\n");
|
|
91
|
+
}
|
|
92
|
+
const DIFF_CONTEXT = 3;
|
|
93
|
+
/**
|
|
94
|
+
* Myers' O(ND) shortest edit script (forward, with a per-`d` trace for the
|
|
95
|
+
* backtrack). `V[k]` is the furthest x on diagonal `k = x - y` reachable with
|
|
96
|
+
* `d` edits. The canonical tie rule, identical in both engines: at each step
|
|
97
|
+
* take the diagonal from `k+1` (moving down — an insertion of `b[y]`) when
|
|
98
|
+
* `k == -d || (k != d && V[k-1] < V[k+1])`, else from `k-1` (moving right —
|
|
99
|
+
* a deletion of `a[x]`). On a tie (`V[k-1] == V[k+1]`) that is the deletion.
|
|
100
|
+
*/
|
|
101
|
+
export function myersScript(a, b) {
|
|
102
|
+
const n = a.length;
|
|
103
|
+
const m = b.length;
|
|
104
|
+
const max = n + m;
|
|
105
|
+
// V is indexed by k ∈ [-max-1, max+1]; `off` maps it onto a plain array.
|
|
106
|
+
const off = max + 1;
|
|
107
|
+
const v = new Array(2 * max + 3).fill(0);
|
|
108
|
+
const trace = [];
|
|
109
|
+
let found = false;
|
|
110
|
+
for (let d = 0; d <= max && !found; d++) {
|
|
111
|
+
trace.push(v.slice());
|
|
112
|
+
for (let k = -d; k <= d; k += 2) {
|
|
113
|
+
let x;
|
|
114
|
+
if (k === -d || (k !== d && v[off + k - 1] < v[off + k + 1]))
|
|
115
|
+
x = v[off + k + 1];
|
|
116
|
+
else
|
|
117
|
+
x = v[off + k - 1] + 1;
|
|
118
|
+
let y = x - k;
|
|
119
|
+
while (x < n && y < m && a[x] === b[y]) {
|
|
120
|
+
x++;
|
|
121
|
+
y++;
|
|
122
|
+
}
|
|
123
|
+
v[off + k] = x;
|
|
124
|
+
if (x >= n && y >= m) {
|
|
125
|
+
found = true;
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
// Backtrack from (n, m) through the trace, emitting ops newest-first.
|
|
131
|
+
const ops = [];
|
|
132
|
+
let x = n;
|
|
133
|
+
let y = m;
|
|
134
|
+
for (let d = trace.length - 1; d >= 0; d--) {
|
|
135
|
+
const vd = trace[d];
|
|
136
|
+
const k = x - y;
|
|
137
|
+
const prevK = k === -d || (k !== d && vd[off + k - 1] < vd[off + k + 1]) ? k + 1 : k - 1;
|
|
138
|
+
const prevX = vd[off + prevK];
|
|
139
|
+
const prevY = prevX - prevK;
|
|
140
|
+
while (x > prevX && y > prevY) {
|
|
141
|
+
x--;
|
|
142
|
+
y--;
|
|
143
|
+
ops.push({ kind: "keep", line: a[x] });
|
|
144
|
+
}
|
|
145
|
+
if (d > 0) {
|
|
146
|
+
if (x === prevX)
|
|
147
|
+
ops.push({ kind: "insert", line: b[prevY] });
|
|
148
|
+
else
|
|
149
|
+
ops.push({ kind: "delete", line: a[prevX] });
|
|
150
|
+
}
|
|
151
|
+
x = prevX;
|
|
152
|
+
y = prevY;
|
|
153
|
+
}
|
|
154
|
+
ops.reverse();
|
|
155
|
+
return ops;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* The unified diff of two texts (spec/surface §3): hunks of `DIFF_CONTEXT` (3)
|
|
159
|
+
* lines of context; a change group extends to include the next change when
|
|
160
|
+
* fewer than `2 * DIFF_CONTEXT + 1` unchanged lines separate them (the two
|
|
161
|
+
* contexts touch or overlap). Each hunk is `@@ -a,b +c,d @@` (1-based start
|
|
162
|
+
* and length; a length of 1 is written as the start alone; a length of 0 as
|
|
163
|
+
* `a,0` with `a` the line before the insertion point, `0` at the very top)
|
|
164
|
+
* followed by its lines prefixed `-`, `+` or a space with nothing after the
|
|
165
|
+
* sign; hunks joined by `\n`; no file header; identical texts → `""`.
|
|
166
|
+
*/
|
|
167
|
+
export function unifiedDiff(oldText, newText) {
|
|
168
|
+
const ops = myersScript(diffLines(oldText), diffLines(newText));
|
|
169
|
+
// Old/new line counts consumed before each op (0-based positions).
|
|
170
|
+
const oldPos = new Array(ops.length + 1);
|
|
171
|
+
const newPos = new Array(ops.length + 1);
|
|
172
|
+
let o = 0;
|
|
173
|
+
let nn = 0;
|
|
174
|
+
for (let i = 0; i < ops.length; i++) {
|
|
175
|
+
oldPos[i] = o;
|
|
176
|
+
newPos[i] = nn;
|
|
177
|
+
if (ops[i].kind !== "insert")
|
|
178
|
+
o++;
|
|
179
|
+
if (ops[i].kind !== "delete")
|
|
180
|
+
nn++;
|
|
181
|
+
}
|
|
182
|
+
oldPos[ops.length] = o;
|
|
183
|
+
newPos[ops.length] = nn;
|
|
184
|
+
const changes = [];
|
|
185
|
+
for (let i = 0; i < ops.length; i++)
|
|
186
|
+
if (ops[i].kind !== "keep")
|
|
187
|
+
changes.push(i);
|
|
188
|
+
if (changes.length === 0)
|
|
189
|
+
return "";
|
|
190
|
+
const hunks = [];
|
|
191
|
+
let g = 0;
|
|
192
|
+
while (g < changes.length) {
|
|
193
|
+
const first = changes[g];
|
|
194
|
+
let last = first;
|
|
195
|
+
// Merge rule: the next change joins this hunk iff the unchanged lines
|
|
196
|
+
// between them number at most 2 * DIFF_CONTEXT.
|
|
197
|
+
while (g + 1 < changes.length && changes[g + 1] - last - 1 <= 2 * DIFF_CONTEXT) {
|
|
198
|
+
g++;
|
|
199
|
+
last = changes[g];
|
|
200
|
+
}
|
|
201
|
+
g++;
|
|
202
|
+
const start = Math.max(0, first - DIFF_CONTEXT);
|
|
203
|
+
const end = Math.min(ops.length - 1, last + DIFF_CONTEXT);
|
|
204
|
+
const oldLen = oldPos[end + 1] - oldPos[start];
|
|
205
|
+
const newLen = newPos[end + 1] - newPos[start];
|
|
206
|
+
const range = (pos, len) => {
|
|
207
|
+
const startLine = len === 0 ? pos : pos + 1;
|
|
208
|
+
return len === 1 ? `${startLine}` : `${startLine},${len}`;
|
|
209
|
+
};
|
|
210
|
+
const lines = [`@@ -${range(oldPos[start], oldLen)} +${range(newPos[start], newLen)} @@`];
|
|
211
|
+
for (let i = start; i <= end; i++) {
|
|
212
|
+
const op = ops[i];
|
|
213
|
+
lines.push((op.kind === "keep" ? " " : op.kind === "delete" ? "-" : "+") + op.line);
|
|
214
|
+
}
|
|
215
|
+
hunks.push(lines.join("\n"));
|
|
82
216
|
}
|
|
83
|
-
return
|
|
217
|
+
return hunks.join("\n");
|
|
84
218
|
}
|
|
85
219
|
/** changes_since: commit digests after a cursor (repo commit seq) — the change
|
|
86
220
|
* feed (06 §3). `seq` is a dense per-REPO total order, so a cursor is only
|
package/dist/src/index.d.ts
CHANGED
|
@@ -9,20 +9,21 @@ export { writeDocNodes, deleteDocNodes } from "./core/store/nodes.js";
|
|
|
9
9
|
export { docPropertiesMerged, docPropertiesGrouped, writeDocProperties, flattenFrontmatter } from "./core/store/properties.js";
|
|
10
10
|
import "./format/index.js";
|
|
11
11
|
export { Store, type StoreOptions } from "./core/store/store.js";
|
|
12
|
-
export { Workspace, RepoSelectionError, type RepoRow } from "./sync/workspace.js";
|
|
12
|
+
export { Workspace, RepoSelectionError, selectRepo, type RepoRow } from "./sync/workspace.js";
|
|
13
13
|
export { resolveSettings, workspaceSettings, repoOwnSettings, writeWorkspaceSettings, writeRepoSettings, deepMerge, type Settings, } from "./sync/settings.js";
|
|
14
|
-
export { freshnessSweep, rebuildFileStats, recordFileStat, detectDiskDrift, type SweepResult, type DiskDrift } from "./sync/freshness.js";
|
|
14
|
+
export { freshnessSweep, rebuildFileStats, recordFileStat, recordStat, detectDiskDrift, sweepPlan, sweepCandidates, loadStatCache, snapshotOf, type SweepResult, type DiskDrift, type SweepPlan, type StatCacheRow, type SnapshotEntry, } from "./sync/freshness.js";
|
|
15
|
+
export { recoverRepo, type RecoveryResult } from "./sync/recovery.js";
|
|
15
16
|
export { withWriterLock, writerLockFree, WriterLockTimeout } from "./sync/writer-lock.js";
|
|
16
17
|
export { WatchLease, watchLeaseLive } from "./sync/watch-lease.js";
|
|
17
18
|
export { ingestDirectory, type IngestDirResult } from "./sync/attach.js";
|
|
18
19
|
export { ensureRepo } from "./core/attach.js";
|
|
19
|
-
export { walkMarkdown, walkMarkdownAsync } from "./sync/fs-util.js";
|
|
20
|
+
export { walkMarkdown, walkMarkdownAsync, nodeFs, type SyncFs, type FileStat } from "./sync/fs-util.js";
|
|
20
21
|
export { Watcher, type WatcherOptions } from "./sync/watcher.js";
|
|
21
22
|
export type { SyncSource, SourceCapabilities, SourceIdentity, SourceEntry, SourceItem, SourceWatch, WatchListener, } from "./sync/plugin.js";
|
|
22
23
|
export { createExternalSource, type ExternalSourceSpec } from "./sync/external-source.js";
|
|
23
24
|
export { ensureAdapter, listAdapters, createSource, deleteSource, listSources, getSourceByName, attachSourceToRepo, detachSourceFromRepo, sourcesForRepo, renderConfigFlags, type AdapterRow, type SourceRow, } from "./sync/sources.js";
|
|
24
25
|
export { reconcileChanges, attachSource } from "./sync/driver.js";
|
|
25
|
-
export { observeFile, observeMany, observeOne, observeDelete, type ObserveResult, type ObserveOneResult, type ObserveDeleteResult } from "./sync/observe.js";
|
|
26
|
+
export { observeFile, observeMany, observeOne, observeDelete, observeBatch, type ObserveResult, type ObserveOneResult, type ObserveDeleteResult, type BatchItem, type BatchOutcome } from "./sync/observe.js";
|
|
26
27
|
export { buildServer, type ServerContext } from "./mcp/server.js";
|
|
27
28
|
export { serveStdio, type ServeStdioHandle } from "./mcp/stdio.js";
|
|
28
29
|
export { processCheckpoint, type CheckpointResult } from "./sync/checkpoint.js";
|
|
@@ -42,12 +43,15 @@ export { embeddingSettings, type EmbeddingSettings } from "./search/provider.js"
|
|
|
42
43
|
export { createExternalProvider, type ExternalProvider } from "./search/external.js";
|
|
43
44
|
export { hybridSearch, type HybridHit, type HybridInput } from "./search/rrf.js";
|
|
44
45
|
export { vectorSearch, docVectorSearch, type VectorHit, type DocVectorHit } from "./search/vector.js";
|
|
45
|
-
export { historyNode, diffUnified, changesSince, type NodeChange, type CommitDigest } from "./graph/history.js";
|
|
46
|
+
export { historyNode, diffUnified, unifiedDiff, changesSince, type NodeChange, type CommitDigest } from "./graph/history.js";
|
|
46
47
|
export { docLinks, type LinksResult, type LinkEdge, type LinksOptions } from "./graph/links.js";
|
|
47
48
|
export { FilterInvalid } from "./search/cel/parser.js";
|
|
48
49
|
export { EngineError, type ErrorCode } from "./mcp/errors.js";
|
|
49
50
|
export { MutationError } from "./mutate/tree.js";
|
|
50
|
-
export { isValidId, prefixOf } from "./core/ids.js";
|
|
51
|
+
export { isValidId, prefixOf, mintId, setIdMinter, withIdMinter, sequentialMinter, type IdMinter, type IdPrefix } from "./core/ids.js";
|
|
52
|
+
export { pinClock, withClock } from "./core/clock.js";
|
|
53
|
+
export { sha256 } from "./core/hash.js";
|
|
54
|
+
export { ingestFile, type IngestResult } from "./core/ingest.js";
|
|
51
55
|
export { apply, type ApplyRequest, type ApplyResult, type Op } from "./mutate/apply.js";
|
|
52
56
|
export { type DocStore, FsDocStore, NullDocStore, resolveDocStore } from "./mutate/doc-store.js";
|
|
53
57
|
export { type To, type At, type Expect } from "./mutate/ops.js";
|
package/dist/src/index.js
CHANGED
|
@@ -10,19 +10,20 @@ export { docPropertiesMerged, docPropertiesGrouped, writeDocProperties, flattenF
|
|
|
10
10
|
import "./format/index.js";
|
|
11
11
|
// Workspace + sync surface used by the CLI (second client, 11 §1).
|
|
12
12
|
export { Store } from "./core/store/store.js";
|
|
13
|
-
export { Workspace, RepoSelectionError } from "./sync/workspace.js";
|
|
13
|
+
export { Workspace, RepoSelectionError, selectRepo } from "./sync/workspace.js";
|
|
14
14
|
export { resolveSettings, workspaceSettings, repoOwnSettings, writeWorkspaceSettings, writeRepoSettings, deepMerge, } from "./sync/settings.js";
|
|
15
|
-
export { freshnessSweep, rebuildFileStats, recordFileStat, detectDiskDrift } from "./sync/freshness.js";
|
|
15
|
+
export { freshnessSweep, rebuildFileStats, recordFileStat, recordStat, detectDiskDrift, sweepPlan, sweepCandidates, loadStatCache, snapshotOf, } from "./sync/freshness.js";
|
|
16
|
+
export { recoverRepo } from "./sync/recovery.js";
|
|
16
17
|
export { withWriterLock, writerLockFree, WriterLockTimeout } from "./sync/writer-lock.js";
|
|
17
18
|
export { WatchLease, watchLeaseLive } from "./sync/watch-lease.js";
|
|
18
19
|
export { ingestDirectory } from "./sync/attach.js";
|
|
19
20
|
export { ensureRepo } from "./core/attach.js";
|
|
20
|
-
export { walkMarkdown, walkMarkdownAsync } from "./sync/fs-util.js";
|
|
21
|
+
export { walkMarkdown, walkMarkdownAsync, nodeFs } from "./sync/fs-util.js";
|
|
21
22
|
export { Watcher } from "./sync/watcher.js";
|
|
22
23
|
export { createExternalSource } from "./sync/external-source.js";
|
|
23
24
|
export { ensureAdapter, listAdapters, createSource, deleteSource, listSources, getSourceByName, attachSourceToRepo, detachSourceFromRepo, sourcesForRepo, renderConfigFlags, } from "./sync/sources.js";
|
|
24
25
|
export { reconcileChanges, attachSource } from "./sync/driver.js";
|
|
25
|
-
export { observeFile, observeMany, observeOne, observeDelete } from "./sync/observe.js";
|
|
26
|
+
export { observeFile, observeMany, observeOne, observeDelete, observeBatch } from "./sync/observe.js";
|
|
26
27
|
export { buildServer } from "./mcp/server.js";
|
|
27
28
|
export { serveStdio } from "./mcp/stdio.js";
|
|
28
29
|
export { processCheckpoint } from "./sync/checkpoint.js";
|
|
@@ -47,12 +48,16 @@ export { embeddingSettings } from "./search/provider.js";
|
|
|
47
48
|
export { createExternalProvider } from "./search/external.js";
|
|
48
49
|
export { hybridSearch } from "./search/rrf.js";
|
|
49
50
|
export { vectorSearch, docVectorSearch } from "./search/vector.js";
|
|
50
|
-
export { historyNode, diffUnified, changesSince } from "./graph/history.js";
|
|
51
|
+
export { historyNode, diffUnified, unifiedDiff, changesSince } from "./graph/history.js";
|
|
51
52
|
export { docLinks } from "./graph/links.js";
|
|
52
53
|
export { FilterInvalid } from "./search/cel/parser.js";
|
|
53
54
|
export { EngineError } from "./mcp/errors.js";
|
|
54
55
|
export { MutationError } from "./mutate/tree.js";
|
|
55
|
-
export { isValidId, prefixOf } from "./core/ids.js";
|
|
56
|
+
export { isValidId, prefixOf, mintId, setIdMinter, withIdMinter, sequentialMinter } from "./core/ids.js";
|
|
57
|
+
// The clock seam (spec/surface §7.1): `omg mcp` pins the process clock under OMGBASE_SPEC_CLOCK.
|
|
58
|
+
export { pinClock, withClock } from "./core/clock.js";
|
|
59
|
+
export { sha256 } from "./core/hash.js";
|
|
60
|
+
export { ingestFile } from "./core/ingest.js";
|
|
56
61
|
// Mutation surface consumed by CLI write commands (11 §5.6).
|
|
57
62
|
export { apply } from "./mutate/apply.js";
|
|
58
63
|
export { FsDocStore, NullDocStore, resolveDocStore } from "./mutate/doc-store.js";
|