@omgbase/core 0.1.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.
Files changed (214) hide show
  1. package/LICENSE +21 -0
  2. package/dist/src/cli/eval-matcher.d.ts +13 -0
  3. package/dist/src/cli/eval-matcher.js +36 -0
  4. package/dist/src/core/attach.d.ts +5 -0
  5. package/dist/src/core/attach.js +38 -0
  6. package/dist/src/core/cursor.d.ts +11 -0
  7. package/dist/src/core/cursor.js +35 -0
  8. package/dist/src/core/hash.d.ts +23 -0
  9. package/dist/src/core/hash.js +82 -0
  10. package/dist/src/core/ids.d.ts +6 -0
  11. package/dist/src/core/ids.js +26 -0
  12. package/dist/src/core/index.d.ts +1 -0
  13. package/dist/src/core/index.js +2 -0
  14. package/dist/src/core/ingest.d.ts +43 -0
  15. package/dist/src/core/ingest.js +287 -0
  16. package/dist/src/core/order-key.d.ts +8 -0
  17. package/dist/src/core/order-key.js +72 -0
  18. package/dist/src/core/parse/parse.d.ts +3 -0
  19. package/dist/src/core/parse/parse.js +104 -0
  20. package/dist/src/core/parse/render.d.ts +3 -0
  21. package/dist/src/core/parse/render.js +20 -0
  22. package/dist/src/core/parse/tree.d.ts +8 -0
  23. package/dist/src/core/parse/tree.js +43 -0
  24. package/dist/src/core/parse/types.d.ts +52 -0
  25. package/dist/src/core/parse/types.js +9 -0
  26. package/dist/src/core/read/document.d.ts +107 -0
  27. package/dist/src/core/read/document.js +191 -0
  28. package/dist/src/core/read/nodes.d.ts +47 -0
  29. package/dist/src/core/read/nodes.js +102 -0
  30. package/dist/src/core/read/outline.d.ts +12 -0
  31. package/dist/src/core/read/outline.js +76 -0
  32. package/dist/src/core/read/reader.d.ts +125 -0
  33. package/dist/src/core/read/reader.js +206 -0
  34. package/dist/src/core/read/refs.d.ts +15 -0
  35. package/dist/src/core/read/refs.js +51 -0
  36. package/dist/src/core/store/edges.d.ts +26 -0
  37. package/dist/src/core/store/edges.js +101 -0
  38. package/dist/src/core/store/fts.d.ts +10 -0
  39. package/dist/src/core/store/fts.js +22 -0
  40. package/dist/src/core/store/gc.d.ts +18 -0
  41. package/dist/src/core/store/gc.js +69 -0
  42. package/dist/src/core/store/nodes.d.ts +5 -0
  43. package/dist/src/core/store/nodes.js +57 -0
  44. package/dist/src/core/store/properties.d.ts +38 -0
  45. package/dist/src/core/store/properties.js +177 -0
  46. package/dist/src/core/store/rebuild.d.ts +3 -0
  47. package/dist/src/core/store/rebuild.js +29 -0
  48. package/dist/src/core/store/schema.d.ts +9 -0
  49. package/dist/src/core/store/schema.js +386 -0
  50. package/dist/src/core/store/sections.d.ts +2 -0
  51. package/dist/src/core/store/sections.js +29 -0
  52. package/dist/src/core/store/store.d.ts +24 -0
  53. package/dist/src/core/store/store.js +164 -0
  54. package/dist/src/core/store/writers.d.ts +52 -0
  55. package/dist/src/core/store/writers.js +89 -0
  56. package/dist/src/core/vec.d.ts +10 -0
  57. package/dist/src/core/vec.js +33 -0
  58. package/dist/src/format/adapter.d.ts +97 -0
  59. package/dist/src/format/adapter.js +14 -0
  60. package/dist/src/format/index.d.ts +6 -0
  61. package/dist/src/format/index.js +15 -0
  62. package/dist/src/format/json.d.ts +3 -0
  63. package/dist/src/format/json.js +324 -0
  64. package/dist/src/format/markdown.d.ts +5 -0
  65. package/dist/src/format/markdown.js +217 -0
  66. package/dist/src/format/registry.d.ts +6 -0
  67. package/dist/src/format/registry.js +24 -0
  68. package/dist/src/format/yaml.d.ts +5 -0
  69. package/dist/src/format/yaml.js +413 -0
  70. package/dist/src/graph/extract.d.ts +38 -0
  71. package/dist/src/graph/extract.js +242 -0
  72. package/dist/src/graph/history.d.ts +103 -0
  73. package/dist/src/graph/history.js +210 -0
  74. package/dist/src/graph/inbound-links.d.ts +33 -0
  75. package/dist/src/graph/inbound-links.js +130 -0
  76. package/dist/src/graph/link-destinations.d.ts +22 -0
  77. package/dist/src/graph/link-destinations.js +84 -0
  78. package/dist/src/graph/link-health.d.ts +79 -0
  79. package/dist/src/graph/link-health.js +146 -0
  80. package/dist/src/graph/links.d.ts +23 -0
  81. package/dist/src/graph/links.js +42 -0
  82. package/dist/src/index.d.ts +60 -0
  83. package/dist/src/index.js +70 -0
  84. package/dist/src/mcp/errors.d.ts +17 -0
  85. package/dist/src/mcp/errors.js +18 -0
  86. package/dist/src/mcp/graph.d.ts +63 -0
  87. package/dist/src/mcp/graph.js +212 -0
  88. package/dist/src/mcp/reference.d.ts +1 -0
  89. package/dist/src/mcp/reference.js +227 -0
  90. package/dist/src/mcp/server.d.ts +30 -0
  91. package/dist/src/mcp/server.js +1076 -0
  92. package/dist/src/mcp/stdio.d.ts +13 -0
  93. package/dist/src/mcp/stdio.js +24 -0
  94. package/dist/src/migrate/mrplex.d.ts +27 -0
  95. package/dist/src/migrate/mrplex.js +29 -0
  96. package/dist/src/mutate/apply.d.ts +83 -0
  97. package/dist/src/mutate/apply.js +302 -0
  98. package/dist/src/mutate/doc-store.d.ts +49 -0
  99. package/dist/src/mutate/doc-store.js +71 -0
  100. package/dist/src/mutate/docs.d.ts +60 -0
  101. package/dist/src/mutate/docs.js +232 -0
  102. package/dist/src/mutate/known-ids.d.ts +12 -0
  103. package/dist/src/mutate/known-ids.js +154 -0
  104. package/dist/src/mutate/load.d.ts +3 -0
  105. package/dist/src/mutate/load.js +66 -0
  106. package/dist/src/mutate/lower.d.ts +32 -0
  107. package/dist/src/mutate/lower.js +232 -0
  108. package/dist/src/mutate/macros.d.ts +91 -0
  109. package/dist/src/mutate/macros.js +230 -0
  110. package/dist/src/mutate/ops.d.ts +40 -0
  111. package/dist/src/mutate/ops.js +513 -0
  112. package/dist/src/mutate/opset.d.ts +92 -0
  113. package/dist/src/mutate/opset.js +79 -0
  114. package/dist/src/mutate/plan-update.d.ts +51 -0
  115. package/dist/src/mutate/plan-update.js +187 -0
  116. package/dist/src/mutate/tree.d.ts +46 -0
  117. package/dist/src/mutate/tree.js +167 -0
  118. package/dist/src/oqx/run.d.ts +1 -0
  119. package/dist/src/oqx/run.js +6 -0
  120. package/dist/src/oqx-js/context.d.ts +23 -0
  121. package/dist/src/oqx-js/context.js +564 -0
  122. package/dist/src/oqx-js/planner.d.ts +10 -0
  123. package/dist/src/oqx-js/planner.js +91 -0
  124. package/dist/src/oqx-js/run.d.ts +41 -0
  125. package/dist/src/oqx-js/run.js +332 -0
  126. package/dist/src/oqx-js/sql/translate.d.ts +27 -0
  127. package/dist/src/oqx-js/sql/translate.js +290 -0
  128. package/dist/src/reconcile/crossdoc.d.ts +34 -0
  129. package/dist/src/reconcile/crossdoc.js +76 -0
  130. package/dist/src/reconcile/eval/generator.d.ts +21 -0
  131. package/dist/src/reconcile/eval/generator.js +129 -0
  132. package/dist/src/reconcile/eval/metrics.d.ts +32 -0
  133. package/dist/src/reconcile/eval/metrics.js +74 -0
  134. package/dist/src/reconcile/flatten.d.ts +12 -0
  135. package/dist/src/reconcile/flatten.js +37 -0
  136. package/dist/src/reconcile/phase5.d.ts +8 -0
  137. package/dist/src/reconcile/phase5.js +167 -0
  138. package/dist/src/reconcile/phase6a.d.ts +2 -0
  139. package/dist/src/reconcile/phase6a.js +159 -0
  140. package/dist/src/reconcile/phases.d.ts +28 -0
  141. package/dist/src/reconcile/phases.js +0 -0
  142. package/dist/src/reconcile/reconcile.d.ts +21 -0
  143. package/dist/src/reconcile/reconcile.js +115 -0
  144. package/dist/src/reconcile/similarity.d.ts +7 -0
  145. package/dist/src/reconcile/similarity.js +41 -0
  146. package/dist/src/reconcile/types.d.ts +46 -0
  147. package/dist/src/reconcile/types.js +19 -0
  148. package/dist/src/search/cel/ast.d.ts +74 -0
  149. package/dist/src/search/cel/ast.js +3 -0
  150. package/dist/src/search/cel/compile.d.ts +60 -0
  151. package/dist/src/search/cel/compile.js +836 -0
  152. package/dist/src/search/cel/lexer.d.ts +9 -0
  153. package/dist/src/search/cel/lexer.js +139 -0
  154. package/dist/src/search/cel/parser.d.ts +7 -0
  155. package/dist/src/search/cel/parser.js +219 -0
  156. package/dist/src/search/drain.d.ts +39 -0
  157. package/dist/src/search/drain.js +97 -0
  158. package/dist/src/search/embeddings.d.ts +131 -0
  159. package/dist/src/search/embeddings.js +263 -0
  160. package/dist/src/search/external.d.ts +13 -0
  161. package/dist/src/search/external.js +177 -0
  162. package/dist/src/search/fts-query.d.ts +1 -0
  163. package/dist/src/search/fts-query.js +64 -0
  164. package/dist/src/search/provider.d.ts +17 -0
  165. package/dist/src/search/provider.js +13 -0
  166. package/dist/src/search/resolve.d.ts +18 -0
  167. package/dist/src/search/resolve.js +23 -0
  168. package/dist/src/search/rrf.d.ts +36 -0
  169. package/dist/src/search/rrf.js +84 -0
  170. package/dist/src/search/tasks.d.ts +12 -0
  171. package/dist/src/search/tasks.js +131 -0
  172. package/dist/src/search/text.d.ts +16 -0
  173. package/dist/src/search/text.js +21 -0
  174. package/dist/src/search/vector.d.ts +18 -0
  175. package/dist/src/search/vector.js +49 -0
  176. package/dist/src/sync/admin.d.ts +59 -0
  177. package/dist/src/sync/admin.js +53 -0
  178. package/dist/src/sync/attach.d.ts +13 -0
  179. package/dist/src/sync/attach.js +29 -0
  180. package/dist/src/sync/checkpoint.d.ts +21 -0
  181. package/dist/src/sync/checkpoint.js +52 -0
  182. package/dist/src/sync/driver.d.ts +25 -0
  183. package/dist/src/sync/driver.js +80 -0
  184. package/dist/src/sync/external-source.d.ts +14 -0
  185. package/dist/src/sync/external-source.js +137 -0
  186. package/dist/src/sync/freshness.d.ts +42 -0
  187. package/dist/src/sync/freshness.js +152 -0
  188. package/dist/src/sync/fs-util.d.ts +12 -0
  189. package/dist/src/sync/fs-util.js +55 -0
  190. package/dist/src/sync/git-heuristics.d.ts +6 -0
  191. package/dist/src/sync/git-heuristics.js +16 -0
  192. package/dist/src/sync/observe.d.ts +63 -0
  193. package/dist/src/sync/observe.js +84 -0
  194. package/dist/src/sync/plugin.d.ts +54 -0
  195. package/dist/src/sync/plugin.js +12 -0
  196. package/dist/src/sync/reconciling-ingest.d.ts +12 -0
  197. package/dist/src/sync/reconciling-ingest.js +209 -0
  198. package/dist/src/sync/recovery.d.ts +6 -0
  199. package/dist/src/sync/recovery.js +34 -0
  200. package/dist/src/sync/settings.d.ts +18 -0
  201. package/dist/src/sync/settings.js +58 -0
  202. package/dist/src/sync/sources.d.ts +37 -0
  203. package/dist/src/sync/sources.js +75 -0
  204. package/dist/src/sync/tombstone.d.ts +9 -0
  205. package/dist/src/sync/tombstone.js +55 -0
  206. package/dist/src/sync/watch-lease.d.ts +14 -0
  207. package/dist/src/sync/watch-lease.js +91 -0
  208. package/dist/src/sync/watcher.d.ts +19 -0
  209. package/dist/src/sync/watcher.js +34 -0
  210. package/dist/src/sync/workspace.d.ts +34 -0
  211. package/dist/src/sync/workspace.js +120 -0
  212. package/dist/src/sync/writer-lock.d.ts +16 -0
  213. package/dist/src/sync/writer-lock.js +121 -0
  214. package/package.json +80 -0
@@ -0,0 +1,102 @@
1
+ import { loadDocBlocks, blockRaw } from "./reader.js";
2
+ function firstWords(text, n) {
3
+ const w = text.split(/\s+/).filter(Boolean);
4
+ return w.length <= n ? w.join(" ") : w.slice(0, n).join(" ") + "…";
5
+ }
6
+ function project(store, node, resolution, includeChildren) {
7
+ const out = { id: node.blockId, type: node.type };
8
+ switch (resolution) {
9
+ case "skeleton":
10
+ out.label = node.type === "heading" ? node.text : node.type;
11
+ break;
12
+ case "outline":
13
+ out.label = firstWords(node.text, 10);
14
+ break;
15
+ case "text":
16
+ out.text = node.text;
17
+ break;
18
+ case "raw":
19
+ out.raw = blockRaw(store, node.rawHashHex);
20
+ out.content_hash = node.rawHashHex;
21
+ break;
22
+ case "full":
23
+ out.raw = blockRaw(store, node.rawHashHex);
24
+ out.content_hash = node.rawHashHex;
25
+ out.text = node.text;
26
+ out.attrs = node.attrs;
27
+ out.placement = { parent: node.parentBlock, ordinal: node.ordinal, depth: node.depth };
28
+ break;
29
+ }
30
+ if (includeChildren && node.children.length > 0) {
31
+ out.children = node.children.map((c) => project(store, c, resolution, true));
32
+ }
33
+ return out;
34
+ }
35
+ function findBlock(roots, blockId) {
36
+ for (const n of roots) {
37
+ if (n.blockId === blockId)
38
+ return n;
39
+ const found = findBlock(n.children, blockId);
40
+ if (found)
41
+ return found;
42
+ }
43
+ return null;
44
+ }
45
+ /** Fetch a single block subtree at a resolution. Returns null if missing. */
46
+ export function nodesGet(store, docId, blockId, opts = {}) {
47
+ const roots = loadDocBlocks(store, docId);
48
+ const node = findBlock(roots, blockId);
49
+ if (!node)
50
+ return null;
51
+ const includeChildren = opts.include?.includes("children") ?? true;
52
+ return project(store, node, opts.resolution ?? "full", includeChildren);
53
+ }
54
+ /**
55
+ * Fetch up to 100 blocks by id, with budget truncation (06 §3). Block ids are
56
+ * globally unique, so `docId` is an optional SCOPE, not a requirement: with
57
+ * `null` each id is resolved to its owning document (`blocks.doc_id`), the ids
58
+ * are grouped by doc, each doc's forest is loaded once, and the nodes come back
59
+ * in request order. With a `docId`, ids from other documents count as
60
+ * `unresolved` (the caller asked for that doc's blocks).
61
+ */
62
+ export function nodesGetMany(store, docId, blockIds, opts = {}) {
63
+ const capped = blockIds.slice(0, 100);
64
+ const budget = opts.budgetTokens ?? Infinity;
65
+ const resolution = opts.resolution ?? "text";
66
+ // id → owning doc for every requested id that is a live block (one query).
67
+ const owner = new Map();
68
+ if (capped.length > 0) {
69
+ const rows = store.db
70
+ .prepare(`SELECT block_id, doc_id FROM blocks WHERE deleted_commit IS NULL AND block_id IN (${capped.map(() => "?").join(",")})`)
71
+ .all(...capped);
72
+ for (const r of rows)
73
+ if (docId === null || r.doc_id === docId)
74
+ owner.set(r.block_id, r.doc_id);
75
+ }
76
+ // Load each involved document's forest once.
77
+ const forests = new Map();
78
+ for (const d of new Set(owner.values()))
79
+ forests.set(d, loadDocBlocks(store, d));
80
+ const nodes = [];
81
+ const unresolved = [];
82
+ let tokens = 0;
83
+ let truncated = blockIds.length > 100;
84
+ for (const id of capped) {
85
+ const d = owner.get(id);
86
+ const node = d ? findBlock(forests.get(d), id) : null;
87
+ if (!node) {
88
+ unresolved.push(id);
89
+ continue;
90
+ }
91
+ const projected = project(store, node, resolution, false);
92
+ const cost = Math.ceil(JSON.stringify(projected).length / 4);
93
+ if (tokens + cost > budget) {
94
+ truncated = true;
95
+ break;
96
+ }
97
+ tokens += cost;
98
+ nodes.push(projected);
99
+ }
100
+ return { nodes, truncated, unresolved };
101
+ }
102
+ //# sourceMappingURL=nodes.js.map
@@ -0,0 +1,12 @@
1
+ import type { Store } from "../store/store.js";
2
+ export interface OutlineResult {
3
+ text: string;
4
+ truncated: boolean;
5
+ }
6
+ export interface OutlineOptions {
7
+ resolution?: "skeleton" | "outline";
8
+ depth?: number;
9
+ budgetTokens?: number;
10
+ }
11
+ /** Render a document outline in the frozen wire format. */
12
+ export declare function docsOutline(store: Store, docId: string, opts?: OutlineOptions): OutlineResult;
@@ -0,0 +1,76 @@
1
+ import { loadDocBlocks } from "./reader.js";
2
+ // Outline wire format (06 §6). Each line:
3
+ // <block-id> <type-abbr> <label> [§ for section-owning headings]
4
+ // The full block id (b_…) is emitted inline so it can be used directly in
5
+ // follow-up ops without a separate alias→id lookup table.
6
+ const TYPE_ABBR = {
7
+ heading: "h",
8
+ paragraph: "p",
9
+ list: "ul",
10
+ list_item: "li",
11
+ task: "li",
12
+ blockquote: "bq",
13
+ code_fence: "code",
14
+ table: "tbl",
15
+ table_row: "tr",
16
+ thematic_break: "hr",
17
+ html_block: "html",
18
+ opaque: "raw",
19
+ };
20
+ function typeLabel(node) {
21
+ if (node.type === "heading")
22
+ return `h${node.attrs.level ?? ""}`;
23
+ return TYPE_ABBR[node.type] ?? node.type;
24
+ }
25
+ // Structural containers carry no label of their own; their content is in
26
+ // children (matches the frozen format example, 06 §6: `b03 ul` has no label).
27
+ const CONTAINER_TYPES = new Set(["list", "blockquote", "table"]);
28
+ function labelFor(node) {
29
+ if (CONTAINER_TYPES.has(node.type))
30
+ return "";
31
+ if (node.type === "task") {
32
+ const glyph = node.attrs.checked ? "☑" : "☐";
33
+ return `${glyph} ${truncateWords(node.text, 10)}`;
34
+ }
35
+ return truncateWords(node.text, 10);
36
+ }
37
+ function truncateWords(text, n) {
38
+ const words = text.split(/\s+/).filter(Boolean);
39
+ if (words.length <= n)
40
+ return words.join(" ");
41
+ return words.slice(0, n).join(" ") + "…";
42
+ }
43
+ /** Render a document outline in the frozen wire format. */
44
+ export function docsOutline(store, docId, opts = {}) {
45
+ const roots = loadDocBlocks(store, docId);
46
+ const resolution = opts.resolution ?? "outline";
47
+ const maxDepth = opts.depth ?? Infinity;
48
+ const budget = opts.budgetTokens ?? Infinity;
49
+ const lines = [];
50
+ let tokens = 0;
51
+ let truncated = false;
52
+ const walk = (nodes, indent) => {
53
+ for (const node of nodes) {
54
+ if (truncated)
55
+ return;
56
+ if (indent > maxDepth)
57
+ continue;
58
+ const pad = " ".repeat(indent);
59
+ const sectionMark = node.type === "heading" ? " §" : "";
60
+ const label = resolution === "skeleton" ? "" : labelFor(node);
61
+ const line = `${pad}${node.blockId} ${typeLabel(node).padEnd(4)} ${label}${sectionMark}`.trimEnd();
62
+ const lineTokens = Math.ceil(line.length / 4);
63
+ if (tokens + lineTokens > budget) {
64
+ truncated = true;
65
+ return;
66
+ }
67
+ tokens += lineTokens;
68
+ lines.push(line);
69
+ if (node.children.length > 0)
70
+ walk(node.children, indent + 1);
71
+ }
72
+ };
73
+ walk(roots, 0);
74
+ return { text: lines.join("\n"), truncated };
75
+ }
76
+ //# sourceMappingURL=outline.js.map
@@ -0,0 +1,125 @@
1
+ import type { Store } from "../store/store.js";
2
+ /** One row of the `ls` / docs_list surface: a live document with its block
3
+ * count and last-commit timestamp. */
4
+ export interface DocListRow {
5
+ path: string;
6
+ blocks: number;
7
+ ts: string | null;
8
+ }
9
+ /** A page of the docs_list surface. Honors the uniform list contract (mcp-api
10
+ * §1): `truncated` is honest, and when true `cursor` resumes after the last
11
+ * item returned. */
12
+ export interface DocListPage {
13
+ items: DocListRow[];
14
+ truncated: boolean;
15
+ cursor: string | null;
16
+ }
17
+ export interface DocListOptions {
18
+ /** Simple LIKE match on the path (`*` → any run, ACROSS `/`). */
19
+ pathGlob?: string;
20
+ /** Max rows per page (default DOCS_LIST_DEFAULT_LIMIT). */
21
+ limit?: number;
22
+ /** Resume after a previous page's `cursor`. */
23
+ cursor?: string | null;
24
+ /** Cap the page's estimated size (~4 chars/token); at least one row is always
25
+ * returned so a paging client makes progress. */
26
+ budgetTokens?: number;
27
+ }
28
+ export declare const DOCS_LIST_DEFAULT_LIMIT = 200;
29
+ /** List a repo's live documents (path, block count, last-commit ts), ordered by
30
+ * path, as a page: `{ items, truncated, cursor }`. `pathGlob` is a simple LIKE
31
+ * match (`*` → `%`). Backs `omg ls` and the `docs_list` MCP tool (one
32
+ * implementation for both surfaces). For a directory-aware summary use
33
+ * `docsTree`. */
34
+ export declare function docsList(store: Store, repoId: string, opts?: DocListOptions): DocListPage;
35
+ /** One entry of the docs_tree surface: a live document (`kind: "doc"`) or a
36
+ * collapsed directory (`kind: "dir"`, path ends in `/`) at the requested
37
+ * depth. `docs`/`blocks` are totals under the entry (a doc counts 1 doc);
38
+ * `ts` is the latest last-commit timestamp under it. */
39
+ export interface DocTreeEntry {
40
+ path: string;
41
+ kind: "dir" | "doc";
42
+ docs: number;
43
+ blocks: number;
44
+ ts: string | null;
45
+ }
46
+ export interface DocTreePage {
47
+ /** The normalized directory prefix the tree was taken under (`""` = root;
48
+ * otherwise ends in `/`). */
49
+ prefix: string;
50
+ depth: number;
51
+ /** Totals over EVERYTHING under `prefix`, regardless of paging. */
52
+ total: {
53
+ docs: number;
54
+ blocks: number;
55
+ };
56
+ entries: DocTreeEntry[];
57
+ truncated: boolean;
58
+ cursor: string | null;
59
+ }
60
+ export interface DocTreeOptions {
61
+ /** Directory to take the tree under (`projects` or `projects/`; leading `/`
62
+ * tolerated). Omit for the repo root. */
63
+ path?: string;
64
+ /** How many path segments below `prefix` to expand before collapsing
65
+ * (default 1 = immediate children, like `tree -L 1`). */
66
+ depth?: number;
67
+ limit?: number;
68
+ cursor?: string | null;
69
+ budgetTokens?: number;
70
+ }
71
+ export declare const DOCS_TREE_DEFAULT_LIMIT = 200;
72
+ /** Normalize a tree prefix: no leading `/`, and either empty or ending in `/`. */
73
+ export declare function normalizeTreePrefix(path: string | undefined): string;
74
+ /** The path-separator-aware orientation read (`tree -L depth` + `du`): every
75
+ * live doc under `path` is collapsed at `depth` segments into one entry per
76
+ * directory (with doc/block totals + latest ts) or listed as a doc when it is
77
+ * shallow enough. Entries are ordered by path; dirs carry a trailing `/`. The
78
+ * result is paged under the same limit/cursor/budget contract as docsList,
79
+ * and always carries `total` for the whole prefix so an agent sees the size of
80
+ * what it is looking at even when the page is cut. Backs the `docs_tree` MCP
81
+ * tool. */
82
+ export declare function docsTree(store: Store, repoId: string, opts?: DocTreeOptions): DocTreePage;
83
+ export interface BlockNode {
84
+ blockId: string;
85
+ docId: string;
86
+ parentBlock: string | null;
87
+ ordinal: number;
88
+ depth: number;
89
+ type: string;
90
+ attrs: Record<string, unknown>;
91
+ text: string;
92
+ rawHashHex: string;
93
+ children: BlockNode[];
94
+ }
95
+ /** Load a document's live blocks as a containment forest, ordered. */
96
+ export declare function loadDocBlocks(store: Store, docId: string): BlockNode[];
97
+ /** Retrieve the raw source bytes for a block by its raw_hash. */
98
+ export declare function blockRaw(store: Store, rawHashHex: string): string;
99
+ export interface DocInfo {
100
+ docId: string;
101
+ repoId: string;
102
+ path: string;
103
+ format: string;
104
+ currentRev: string | null;
105
+ }
106
+ export declare function findDoc(store: Store, ref: {
107
+ docId?: string;
108
+ repoId?: string;
109
+ path?: string;
110
+ }): DocInfo | null;
111
+ /**
112
+ * Resolve a single `doc` ref that may be EITHER a minted document id (`d_…`) OR
113
+ * a repo-relative path — the id-or-path symmetry every doc-ref surface expects
114
+ * (docs_read/outline/read_at, diff, apply's insert.doc, graph seeds, docHistory).
115
+ * This is the ONE dispatch those call sites route through so the "path in the
116
+ * doc field" trap can't recur per-tool.
117
+ *
118
+ * A `d_`-shaped id is looked up by id ONLY — if it doesn't exist we return null
119
+ * (the caller fails loud) rather than falling through to a path lookup that
120
+ * would also miss and confusingly report a path-not-found for a d_ value.
121
+ * Anything else is treated as a path. Returns null when unresolvable; callers
122
+ * throw a clear doc_missing/target_missing so an unresolvable ref is never a
123
+ * silent empty result.
124
+ */
125
+ export declare function findDocByRef(store: Store, repoId: string, ref: string): DocInfo | null;
@@ -0,0 +1,206 @@
1
+ import { isValidId } from "../ids.js";
2
+ import { encodeCursor, decodeCursor } from "../cursor.js";
3
+ export const DOCS_LIST_DEFAULT_LIMIT = 200;
4
+ /** Escape a user glob into a LIKE pattern (`*` → `%`; literal `%`/`_` escaped). */
5
+ function globToLike(glob) {
6
+ return glob.replace(/[%_\\]/g, "\\$&").replace(/\*/g, "%");
7
+ }
8
+ /** Live docs of a repo (path, live block count, last-commit ts) whose path
9
+ * matches `like`, ordered by path, optionally resuming strictly after `after`
10
+ * and capped at `limit`. The one query behind docs_list and docs_tree. */
11
+ function liveDocRows(store, repoId, like, after, limit) {
12
+ const params = [repoId, like];
13
+ let where = "";
14
+ if (after !== null) {
15
+ where += " AND d.path > ?";
16
+ params.push(after);
17
+ }
18
+ let tail = "";
19
+ if (limit !== null) {
20
+ tail = " LIMIT ?";
21
+ params.push(limit);
22
+ }
23
+ return store.db
24
+ .prepare(`SELECT d.path AS path,
25
+ (SELECT count(*) FROM blocks b WHERE b.doc_id = d.doc_id AND b.deleted_commit IS NULL) AS blocks,
26
+ (SELECT c.ts FROM revisions r JOIN commits c ON c.commit_id = r.commit_id WHERE r.rev_id = d.current_rev) AS ts
27
+ FROM docs d
28
+ WHERE d.repo_id = ? AND d.deleted_commit IS NULL AND d.path LIKE ? ESCAPE '\\'${where}
29
+ ORDER BY d.path${tail}`)
30
+ .all(...params);
31
+ }
32
+ // Path-keyset cursors for the path-ordered list surfaces: the shared kernel
33
+ // encoding (core/cursor.ts) with a one-part tuple.
34
+ function encodePathCursor(path) {
35
+ return encodeCursor([path]);
36
+ }
37
+ function decodePathCursor(cursor) {
38
+ return decodeCursor(cursor, "docs_list/docs_tree", 1)[0];
39
+ }
40
+ /** Page an already path-ordered row set under a limit + token budget, issuing
41
+ * a keyset cursor when it stops early. Shared by docs_list and docs_tree. */
42
+ function pagePathOrdered(rows, limit, budgetTokens, moreBeyond) {
43
+ const budget = budgetTokens ?? Infinity;
44
+ const items = [];
45
+ let tokens = 0;
46
+ let truncated = moreBeyond;
47
+ for (const row of rows) {
48
+ if (items.length >= limit) {
49
+ truncated = true;
50
+ break;
51
+ }
52
+ const cost = Math.ceil(JSON.stringify(row).length / 4);
53
+ if (items.length > 0 && tokens + cost > budget) {
54
+ truncated = true;
55
+ break;
56
+ }
57
+ tokens += cost;
58
+ items.push(row);
59
+ }
60
+ const last = items[items.length - 1];
61
+ return { items, truncated, cursor: truncated && last ? encodePathCursor(last.path) : null };
62
+ }
63
+ /** List a repo's live documents (path, block count, last-commit ts), ordered by
64
+ * path, as a page: `{ items, truncated, cursor }`. `pathGlob` is a simple LIKE
65
+ * match (`*` → `%`). Backs `omg ls` and the `docs_list` MCP tool (one
66
+ * implementation for both surfaces). For a directory-aware summary use
67
+ * `docsTree`. */
68
+ export function docsList(store, repoId, opts = {}) {
69
+ const like = opts.pathGlob ? globToLike(opts.pathGlob) : "%";
70
+ const limit = Math.max(1, Math.floor(opts.limit ?? DOCS_LIST_DEFAULT_LIMIT));
71
+ const after = opts.cursor ? decodePathCursor(opts.cursor) : null;
72
+ // Fetch one past the limit so `truncated` is a fact, not a guess.
73
+ const rows = liveDocRows(store, repoId, like, after, limit + 1);
74
+ const moreBeyond = rows.length > limit;
75
+ return pagePathOrdered(rows.slice(0, limit), limit, opts.budgetTokens, moreBeyond);
76
+ }
77
+ export const DOCS_TREE_DEFAULT_LIMIT = 200;
78
+ /** Normalize a tree prefix: no leading `/`, and either empty or ending in `/`. */
79
+ export function normalizeTreePrefix(path) {
80
+ const trimmed = (path ?? "").replace(/^\/+/, "").replace(/\/+$/, "");
81
+ return trimmed === "" ? "" : trimmed + "/";
82
+ }
83
+ /** The path-separator-aware orientation read (`tree -L depth` + `du`): every
84
+ * live doc under `path` is collapsed at `depth` segments into one entry per
85
+ * directory (with doc/block totals + latest ts) or listed as a doc when it is
86
+ * shallow enough. Entries are ordered by path; dirs carry a trailing `/`. The
87
+ * result is paged under the same limit/cursor/budget contract as docsList,
88
+ * and always carries `total` for the whole prefix so an agent sees the size of
89
+ * what it is looking at even when the page is cut. Backs the `docs_tree` MCP
90
+ * tool. */
91
+ export function docsTree(store, repoId, opts = {}) {
92
+ const prefix = normalizeTreePrefix(opts.path);
93
+ const depth = Math.max(1, Math.floor(opts.depth ?? 1));
94
+ const limit = Math.max(1, Math.floor(opts.limit ?? DOCS_TREE_DEFAULT_LIMIT));
95
+ const like = prefix === "" ? "%" : globToLike(prefix) + "%";
96
+ const rows = liveDocRows(store, repoId, like, null, null);
97
+ const byPath = new Map();
98
+ const total = { docs: 0, blocks: 0 };
99
+ for (const row of rows) {
100
+ total.docs += 1;
101
+ total.blocks += row.blocks;
102
+ const segs = row.path.slice(prefix.length).split("/");
103
+ if (segs.length <= depth) {
104
+ byPath.set(row.path, { path: row.path, kind: "doc", docs: 1, blocks: row.blocks, ts: row.ts });
105
+ continue;
106
+ }
107
+ const dir = prefix + segs.slice(0, depth).join("/") + "/";
108
+ const cur = byPath.get(dir);
109
+ if (cur) {
110
+ cur.docs += 1;
111
+ cur.blocks += row.blocks;
112
+ if (row.ts !== null && (cur.ts === null || row.ts > cur.ts))
113
+ cur.ts = row.ts;
114
+ }
115
+ else {
116
+ byPath.set(dir, { path: dir, kind: "dir", docs: 1, blocks: row.blocks, ts: row.ts });
117
+ }
118
+ }
119
+ let entries = [...byPath.values()].sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));
120
+ if (opts.cursor) {
121
+ const after = decodePathCursor(opts.cursor);
122
+ entries = entries.filter((e) => e.path > after);
123
+ }
124
+ const { items, truncated, cursor } = pagePathOrdered(entries, limit, opts.budgetTokens, false);
125
+ return { prefix, depth, total, entries: items, truncated, cursor };
126
+ }
127
+ function toNode(r) {
128
+ return {
129
+ blockId: r.block_id,
130
+ docId: r.doc_id,
131
+ parentBlock: r.parent_block,
132
+ ordinal: r.ordinal,
133
+ depth: r.depth,
134
+ type: r.type,
135
+ attrs: JSON.parse(r.attrs),
136
+ text: r.text,
137
+ rawHashHex: r.raw_hash.toString("hex"),
138
+ children: [],
139
+ };
140
+ }
141
+ /** Load a document's live blocks as a containment forest, ordered. */
142
+ export function loadDocBlocks(store, docId) {
143
+ const rows = store.db
144
+ .prepare(`SELECT block_id, doc_id, parent_block, order_key, ordinal, depth, type, attrs, text, raw_hash
145
+ FROM blocks WHERE doc_id = ? AND deleted_commit IS NULL
146
+ ORDER BY parent_block, order_key`)
147
+ .all(docId);
148
+ const byId = new Map();
149
+ for (const r of rows)
150
+ byId.set(r.block_id, toNode(r));
151
+ const roots = [];
152
+ // Preserve order_key ordering by iterating rows (already sorted).
153
+ for (const r of rows) {
154
+ const node = byId.get(r.block_id);
155
+ if (r.parent_block && byId.has(r.parent_block)) {
156
+ byId.get(r.parent_block).children.push(node);
157
+ }
158
+ else {
159
+ roots.push(node);
160
+ }
161
+ }
162
+ return roots;
163
+ }
164
+ /** Retrieve the raw source bytes for a block by its raw_hash. */
165
+ export function blockRaw(store, rawHashHex) {
166
+ const row = store.db.prepare("SELECT bytes FROM blobs WHERE hash = ?").get(Buffer.from(rawHashHex, "hex"));
167
+ return row ? row.bytes.toString("utf8") : "";
168
+ }
169
+ export function findDoc(store, ref) {
170
+ let row;
171
+ if (ref.docId) {
172
+ row = store.db.prepare("SELECT doc_id, repo_id, path, format, current_rev FROM docs WHERE doc_id = ? AND deleted_commit IS NULL").get(ref.docId);
173
+ }
174
+ else if (ref.repoId && ref.path) {
175
+ row = store.db.prepare("SELECT doc_id, repo_id, path, format, current_rev FROM docs WHERE repo_id = ? AND path = ? AND deleted_commit IS NULL").get(ref.repoId, ref.path);
176
+ }
177
+ if (!row)
178
+ return null;
179
+ return {
180
+ docId: row.doc_id,
181
+ repoId: row.repo_id,
182
+ path: row.path,
183
+ format: row.format ?? "markdown",
184
+ currentRev: row.current_rev ?? null,
185
+ };
186
+ }
187
+ /**
188
+ * Resolve a single `doc` ref that may be EITHER a minted document id (`d_…`) OR
189
+ * a repo-relative path — the id-or-path symmetry every doc-ref surface expects
190
+ * (docs_read/outline/read_at, diff, apply's insert.doc, graph seeds, docHistory).
191
+ * This is the ONE dispatch those call sites route through so the "path in the
192
+ * doc field" trap can't recur per-tool.
193
+ *
194
+ * A `d_`-shaped id is looked up by id ONLY — if it doesn't exist we return null
195
+ * (the caller fails loud) rather than falling through to a path lookup that
196
+ * would also miss and confusingly report a path-not-found for a d_ value.
197
+ * Anything else is treated as a path. Returns null when unresolvable; callers
198
+ * throw a clear doc_missing/target_missing so an unresolvable ref is never a
199
+ * silent empty result.
200
+ */
201
+ export function findDocByRef(store, repoId, ref) {
202
+ if (isValidId(ref, "d"))
203
+ return findDoc(store, { docId: ref });
204
+ return findDoc(store, { repoId, path: ref });
205
+ }
206
+ //# sourceMappingURL=reader.js.map
@@ -0,0 +1,15 @@
1
+ import type { Store } from "../store/store.js";
2
+ export interface ResolvedRef {
3
+ kind: "block" | "document";
4
+ docId: string;
5
+ blockId?: string;
6
+ }
7
+ /**
8
+ * Resolve a node reference within a repo. Accepts:
9
+ * - a block id (prefix "b") → { block, docId, blockId }
10
+ * - a document id (prefix "d") → { document, docId }
11
+ * - a projected node id ("n_…") → its block, or its doc when block-less
12
+ * - a repo-relative document path → { document, docId }
13
+ * Returns null when nothing matches.
14
+ */
15
+ export declare function resolveRef(store: Store, repoId: string, ref: string): ResolvedRef | null;
@@ -0,0 +1,51 @@
1
+ import { prefixOf } from "../ids.js";
2
+ import { findDoc } from "./reader.js";
3
+ // Node ids are content-hash derived (`n_` + 12 hex chars, see core/store/nodes),
4
+ // not the 7-char minted scheme prefixOf recognizes — match them explicitly.
5
+ const NODE_ID_RE = /^n_[0-9a-f]{12}$/;
6
+ /**
7
+ * Resolve a node reference within a repo. Accepts:
8
+ * - a block id (prefix "b") → { block, docId, blockId }
9
+ * - a document id (prefix "d") → { document, docId }
10
+ * - a projected node id ("n_…") → its block, or its doc when block-less
11
+ * - a repo-relative document path → { document, docId }
12
+ * Returns null when nothing matches.
13
+ */
14
+ export function resolveRef(store, repoId, ref) {
15
+ // A projected-node id (e.g. an OQX `from nodes` hit) dereferences to the block
16
+ // it was projected from — so a node hit is a first-class argument to the same
17
+ // read/write commands a block id is. Block-less nodes (or nodes whose block is
18
+ // no longer live) fall back to their document.
19
+ if (NODE_ID_RE.test(ref)) {
20
+ const node = store.db
21
+ .prepare("SELECT doc_id, block_id FROM nodes WHERE node_id = ?")
22
+ .get(ref);
23
+ if (!node)
24
+ return null;
25
+ if (node.block_id) {
26
+ const live = store.db
27
+ .prepare("SELECT 1 FROM blocks WHERE block_id = ? AND deleted_commit IS NULL")
28
+ .get(node.block_id);
29
+ if (live)
30
+ return { kind: "block", docId: node.doc_id, blockId: node.block_id };
31
+ }
32
+ return { kind: "document", docId: node.doc_id };
33
+ }
34
+ const prefix = prefixOf(ref);
35
+ if (prefix === "b") {
36
+ const row = store.db
37
+ .prepare("SELECT doc_id FROM blocks WHERE block_id = ? AND deleted_commit IS NULL")
38
+ .get(ref);
39
+ if (!row)
40
+ return null;
41
+ return { kind: "block", docId: row.doc_id, blockId: ref };
42
+ }
43
+ if (prefix === "d") {
44
+ const info = findDoc(store, { docId: ref });
45
+ return info ? { kind: "document", docId: info.docId } : null;
46
+ }
47
+ // Otherwise treat it as a document path.
48
+ const info = findDoc(store, { repoId, path: ref });
49
+ return info ? { kind: "document", docId: info.docId } : null;
50
+ }
51
+ //# sourceMappingURL=refs.js.map
@@ -0,0 +1,26 @@
1
+ import type { Database } from "better-sqlite3";
2
+ export interface ResolvedEdge {
3
+ srcDoc: string;
4
+ srcBlock: string | null;
5
+ srcField: string | null;
6
+ predicate: string;
7
+ dstKind: "document" | "block" | "external" | "collection";
8
+ dstNode: string;
9
+ anchor: string | null;
10
+ provenance: string;
11
+ }
12
+ /**
13
+ * Reconcile the extracted edge set for a document against its currently-open
14
+ * rows: close rows no longer present (to_commit = this), open rows that are new
15
+ * (from_commit = this), leave unchanged rows untouched. Then recompute the
16
+ * doc_edges rollup for the doc. All within the caller's transaction.
17
+ */
18
+ export declare function maintainEdges(db: Database, repoId: string, srcDoc: string, commitId: string, edges: ResolvedEdge[]): void;
19
+ export declare function adoptPhantoms(db: Database, path: string, realDocId: string): void;
20
+ /** Recompute the doc_edges rollup for one document from its open edges. */
21
+ export declare function rebuildDocEdges(db: Database, srcDoc: string): void;
22
+ export declare function resolveExternal(db: Database, repoId: string, uri: string): string;
23
+ export declare function resolveDocPath(db: Database, repoId: string, path: string): {
24
+ id: string;
25
+ phantom: boolean;
26
+ };