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