@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,101 @@
1
+ import { mintId } from "../ids.js";
2
+ // Stable key for diffing an edge set against currently-open rows.
3
+ function edgeKey(e) {
4
+ return `${e.srcBlock ?? ""}|${e.srcField ?? ""}|${e.predicate}|${e.dstNode}|${e.anchor ?? ""}`;
5
+ }
6
+ /**
7
+ * Reconcile the extracted edge set for a document against its currently-open
8
+ * rows: close rows no longer present (to_commit = this), open rows that are new
9
+ * (from_commit = this), leave unchanged rows untouched. Then recompute the
10
+ * doc_edges rollup for the doc. All within the caller's transaction.
11
+ */
12
+ export function maintainEdges(db, repoId, srcDoc, commitId, edges) {
13
+ const openRows = db
14
+ .prepare(`SELECT edge_id, src_block, src_field, predicate, dst_node, anchor FROM edges
15
+ WHERE src_doc = ? AND to_commit IS NULL`)
16
+ .all(srcDoc);
17
+ // One open row per key. Two spellings of one target in one block
18
+ // (`../old.md` and `./x/../../old.md`) extract as two descriptors but resolve
19
+ // to the SAME key, so dedupe the wanted set here; and any duplicate open rows
20
+ // already in the table (from earlier versions of this function) are closed
21
+ // below so they cannot leak past the link they came from.
22
+ const openKeys = new Set();
23
+ for (const r of openRows)
24
+ openKeys.add(edgeKey({ srcBlock: r.src_block, srcField: r.src_field, predicate: r.predicate, dstNode: r.dst_node, anchor: r.anchor }));
25
+ const wantKeys = new Set();
26
+ const insert = db.prepare(`INSERT INTO edges (edge_id, repo_id, src_doc, src_block, src_field, predicate, dst_kind, dst_node, anchor, provenance, from_commit, to_commit)
27
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NULL)`);
28
+ for (const e of edges) {
29
+ const key = edgeKey(e);
30
+ if (wantKeys.has(key))
31
+ continue;
32
+ wantKeys.add(key);
33
+ if (!openKeys.has(key)) {
34
+ insert.run(mintId("e"), repoId, srcDoc, e.srcBlock, e.srcField, e.predicate, e.dstKind, e.dstNode, e.anchor, e.provenance, commitId);
35
+ }
36
+ }
37
+ // Close every open row whose key is no longer wanted, and all but the first
38
+ // open row of a wanted key.
39
+ const close = db.prepare("UPDATE edges SET to_commit = ? WHERE edge_id = ?");
40
+ const kept = new Set();
41
+ for (const r of openRows) {
42
+ const key = edgeKey({ srcBlock: r.src_block, srcField: r.src_field, predicate: r.predicate, dstNode: r.dst_node, anchor: r.anchor });
43
+ if (wantKeys.has(key) && !kept.has(key)) {
44
+ kept.add(key);
45
+ continue;
46
+ }
47
+ close.run(commitId, r.edge_id);
48
+ }
49
+ rebuildDocEdges(db, srcDoc);
50
+ }
51
+ // When a document is (re)created at a path, adopt any open phantom edges that
52
+ // pointed at that path so backlinks re-point to the real node (05 §2). Rebuilds
53
+ // the affected source docs' rollups.
54
+ export function adoptPhantoms(db, path, realDocId) {
55
+ const canonical = path.replace(/^\//, "");
56
+ const phantom = `phantom:${canonical}`;
57
+ const affected = db.prepare("SELECT DISTINCT src_doc FROM edges WHERE dst_node = ? AND to_commit IS NULL").all(phantom);
58
+ if (affected.length === 0)
59
+ return;
60
+ db.prepare("UPDATE edges SET dst_node = ? WHERE dst_node = ? AND to_commit IS NULL").run(realDocId, phantom);
61
+ for (const a of affected)
62
+ rebuildDocEdges(db, a.src_doc);
63
+ }
64
+ /** Recompute the doc_edges rollup for one document from its open edges. */
65
+ export function rebuildDocEdges(db, srcDoc) {
66
+ db.prepare("DELETE FROM doc_edges WHERE src_doc = ?").run(srcDoc);
67
+ const rows = db
68
+ .prepare(`SELECT predicate, dst_node, dst_kind, count(*) AS cnt,
69
+ json_group_array(src_block) AS blocks
70
+ FROM edges WHERE src_doc = ? AND to_commit IS NULL
71
+ GROUP BY predicate, dst_node, dst_kind`)
72
+ .all(srcDoc);
73
+ const insert = db.prepare("INSERT INTO doc_edges (src_doc, predicate, dst_node, dst_kind, count, samples) VALUES (?, ?, ?, ?, ?, ?)");
74
+ for (const r of rows) {
75
+ const blocks = JSON.parse(r.blocks).filter((b) => b !== null).slice(0, 3);
76
+ insert.run(srcDoc, r.predicate, r.dst_node, r.dst_kind, r.cnt, JSON.stringify(blocks));
77
+ }
78
+ }
79
+ // Resolve/mint an external node id for a normalized URI.
80
+ export function resolveExternal(db, repoId, uri) {
81
+ const existing = db.prepare("SELECT node_id FROM external_nodes WHERE repo_id = ? AND uri = ?").get(repoId, uri);
82
+ if (existing)
83
+ return existing.node_id;
84
+ const id = mintId("x");
85
+ db.prepare("INSERT INTO external_nodes (node_id, repo_id, uri) VALUES (?, ?, ?)").run(id, repoId, uri);
86
+ return id;
87
+ }
88
+ // Resolve a repo-relative path to a document id, minting a phantom placeholder
89
+ // if absent (05 §2) so backlinks appear the moment the target is created. The
90
+ // phantom id is derived from the path so it is stable and re-resolves when the
91
+ // real doc is ingested (the doc keeps its own minted id; edges point at the
92
+ // path-keyed node, joined by path at query time).
93
+ export function resolveDocPath(db, repoId, path) {
94
+ const canonical = path.replace(/^\//, "");
95
+ const doc = db.prepare("SELECT doc_id FROM docs WHERE repo_id = ? AND path = ? AND deleted_commit IS NULL").get(repoId, canonical);
96
+ if (doc)
97
+ return { id: doc.doc_id, phantom: false };
98
+ // Phantom: a deterministic placeholder keyed by path.
99
+ return { id: `phantom:${canonical}`, phantom: true };
100
+ }
101
+ //# sourceMappingURL=edges.js.map
@@ -0,0 +1,10 @@
1
+ import type { Database } from "better-sqlite3";
2
+ /** Remove a document's live block rows from the FTS index (call before deleting
3
+ * or tombstoning blocks). Only live blocks are indexed (see ftsIndexDoc), so the
4
+ * delete must target the SAME set: an already-tombstoned block has no FTS row, and
5
+ * re-issuing a 'delete' for it corrupts the external-content index. Keeping this
6
+ * symmetric with ftsIndexDoc (deleted_commit IS NULL) makes a tombstone followed by
7
+ * a later re-ingest of the same doc id (observed delete → recreate) idempotent. */
8
+ export declare function ftsDeleteDoc(db: Database, docId: string): void;
9
+ /** Index a document's current block rows (call after inserting blocks). */
10
+ export declare function ftsIndexDoc(db: Database, docId: string): void;
@@ -0,0 +1,22 @@
1
+ /** Remove a document's live block rows from the FTS index (call before deleting
2
+ * or tombstoning blocks). Only live blocks are indexed (see ftsIndexDoc), so the
3
+ * delete must target the SAME set: an already-tombstoned block has no FTS row, and
4
+ * re-issuing a 'delete' for it corrupts the external-content index. Keeping this
5
+ * symmetric with ftsIndexDoc (deleted_commit IS NULL) makes a tombstone followed by
6
+ * a later re-ingest of the same doc id (observed delete → recreate) idempotent. */
7
+ export function ftsDeleteDoc(db, docId) {
8
+ const rows = db.prepare("SELECT rowid, text FROM blocks WHERE doc_id = ? AND deleted_commit IS NULL").all(docId);
9
+ const del = db.prepare("INSERT INTO blocks_fts(blocks_fts, rowid, text) VALUES('delete', ?, ?)");
10
+ for (const r of rows)
11
+ del.run(r.rowid, r.text);
12
+ }
13
+ /** Index a document's current block rows (call after inserting blocks). */
14
+ export function ftsIndexDoc(db, docId) {
15
+ const rows = db
16
+ .prepare("SELECT rowid, text FROM blocks WHERE doc_id = ? AND deleted_commit IS NULL")
17
+ .all(docId);
18
+ const ins = db.prepare("INSERT INTO blocks_fts(rowid, text) VALUES(?, ?)");
19
+ for (const r of rows)
20
+ ins.run(r.rowid, r.text);
21
+ }
22
+ //# sourceMappingURL=fts.js.map
@@ -0,0 +1,18 @@
1
+ import type { Store } from "./store.js";
2
+ export interface GcResult {
3
+ blobsSwept: number;
4
+ treeNodesSwept: number;
5
+ }
6
+ /**
7
+ * Mark-and-sweep GC (flag-gated; enabled must be true to run — v1.0 ships it
8
+ * off). Removes blobs/tree_nodes unreachable from any revision root. Because
9
+ * revisions are append-only and never pruned in v1, nothing is actually
10
+ * unreachable unless revision pruning ran first — so this is a no-op in a
11
+ * default v1 repo, exactly as specified. Rebuild-equivalence is preserved.
12
+ */
13
+ export declare function runGc(store: Store, opts?: {
14
+ enabled?: boolean;
15
+ }): GcResult;
16
+ /** Resurrection-pool expiry sweep (02 §7): drop rows past their expires_ts.
17
+ * Runs routinely (lazy, at checkpoint time). */
18
+ export declare function sweepResurrectionPool(store: Store, nowIso: string): number;
@@ -0,0 +1,69 @@
1
+ // Collect the set of tree-node hashes reachable from every revision root, and
2
+ // the blob hashes referenced by those nodes (raw + trivia) plus frontmatter.
3
+ function markReachable(store) {
4
+ const trees = new Set();
5
+ const blobs = new Set();
6
+ // Frontmatter blobs referenced by revisions.
7
+ const fmRows = store.db.prepare("SELECT DISTINCT frontmatter_blob FROM revisions WHERE frontmatter_blob IS NOT NULL").all();
8
+ for (const r of fmRows)
9
+ blobs.add(r.frontmatter_blob.toString("hex"));
10
+ const treeNode = store.db.prepare("SELECT entries FROM tree_nodes WHERE hash = ?");
11
+ const walk = (hashHex) => {
12
+ if (trees.has(hashHex))
13
+ return;
14
+ trees.add(hashHex);
15
+ const node = treeNode.get(Buffer.from(hashHex, "hex"));
16
+ if (!node)
17
+ return;
18
+ const entries = JSON.parse(node.entries);
19
+ for (const [, rawHashHex, childHashHex, , , triviaHashHex] of entries) {
20
+ blobs.add(rawHashHex);
21
+ if (triviaHashHex)
22
+ blobs.add(triviaHashHex);
23
+ if (childHashHex)
24
+ walk(childHashHex);
25
+ }
26
+ };
27
+ const roots = store.db.prepare("SELECT DISTINCT root_tree FROM revisions").all();
28
+ for (const r of roots)
29
+ walk(r.root_tree.toString("hex"));
30
+ return { trees, blobs };
31
+ }
32
+ /**
33
+ * Mark-and-sweep GC (flag-gated; enabled must be true to run — v1.0 ships it
34
+ * off). Removes blobs/tree_nodes unreachable from any revision root. Because
35
+ * revisions are append-only and never pruned in v1, nothing is actually
36
+ * unreachable unless revision pruning ran first — so this is a no-op in a
37
+ * default v1 repo, exactly as specified. Rebuild-equivalence is preserved.
38
+ */
39
+ export function runGc(store, opts = {}) {
40
+ if (!opts.enabled)
41
+ return { blobsSwept: 0, treeNodesSwept: 0 };
42
+ return store.write((db) => {
43
+ const { trees, blobs } = markReachable(store);
44
+ let treeNodesSwept = 0;
45
+ const allTrees = db.prepare("SELECT hash FROM tree_nodes").all();
46
+ const delTree = db.prepare("DELETE FROM tree_nodes WHERE hash = ?");
47
+ for (const t of allTrees)
48
+ if (!trees.has(t.hash.toString("hex"))) {
49
+ delTree.run(t.hash);
50
+ treeNodesSwept++;
51
+ }
52
+ let blobsSwept = 0;
53
+ const allBlobs = db.prepare("SELECT hash FROM blobs").all();
54
+ const delBlob = db.prepare("DELETE FROM blobs WHERE hash = ?");
55
+ for (const b of allBlobs)
56
+ if (!blobs.has(b.hash.toString("hex"))) {
57
+ delBlob.run(b.hash);
58
+ blobsSwept++;
59
+ }
60
+ return { blobsSwept, treeNodesSwept };
61
+ });
62
+ }
63
+ /** Resurrection-pool expiry sweep (02 §7): drop rows past their expires_ts.
64
+ * Runs routinely (lazy, at checkpoint time). */
65
+ export function sweepResurrectionPool(store, nowIso) {
66
+ const res = store.db.prepare("DELETE FROM resurrection_pool WHERE expires_ts <= ?").run(nowIso);
67
+ return res.changes;
68
+ }
69
+ //# sourceMappingURL=gc.js.map
@@ -0,0 +1,5 @@
1
+ import type { Database } from "better-sqlite3";
2
+ import type { ProjectedNode } from "../../format/adapter.js";
3
+ export declare function projectSectionNodes(db: Database, docId: string): ProjectedNode[];
4
+ export declare function deleteDocNodes(db: Database, docId: string): void;
5
+ export declare function writeDocNodes(db: Database, repoId: string, docId: string, projected: ProjectedNode[]): number;
@@ -0,0 +1,57 @@
1
+ import { hashHex } from "../hash.js";
2
+ function mintNodeId(docId, blockId, kind, ordinal) {
3
+ return "n_" + hashHex(`${docId}|${blockId}|${kind}|${ordinal}`).slice(0, 12);
4
+ }
5
+ // Project `md:section` nodes from the derived `sections` table (02 §4). A
6
+ // section node surfaces the outline latent in `sections`: it is anchored to the
7
+ // heading block (a real `b_` id — so unlike adapter-projected nodes it carries a
8
+ // valid block_id), names the heading text, and carries the section's TOP-LEVEL
9
+ // ordinal range + level in attrs. Range containment over these attrs is what
10
+ // OQX's `section.blocks` / `section.subsections` / `block.section` relations
11
+ // correlate on. Derived/rebuildable; not a new source of truth. Non-markdown
12
+ // formats have no heading blocks, so `sections` is empty and this returns [].
13
+ export function projectSectionNodes(db, docId) {
14
+ const rows = db
15
+ .prepare(`SELECT s.heading_block AS heading_block, s.level AS level,
16
+ s.first_ordinal AS first_ordinal, s.last_ordinal AS last_ordinal,
17
+ hb.text AS text
18
+ FROM sections s
19
+ JOIN blocks hb ON hb.block_id = s.heading_block AND hb.doc_id = s.doc_id
20
+ WHERE s.doc_id = ? AND hb.deleted_commit IS NULL
21
+ ORDER BY s.first_ordinal`)
22
+ .all(docId);
23
+ return rows.map((r) => ({
24
+ kind: "md:section",
25
+ name: r.text,
26
+ blockId: r.heading_block,
27
+ attrs: { level: r.level, first_ordinal: r.first_ordinal, last_ordinal: r.last_ordinal },
28
+ }));
29
+ }
30
+ export function deleteDocNodes(db, docId) {
31
+ const rows = db.prepare("SELECT rowid, name, value FROM nodes WHERE doc_id = ?").all(docId);
32
+ for (const r of rows) {
33
+ db.prepare("INSERT INTO nodes_fts(nodes_fts, rowid, name, value) VALUES('delete', ?, ?, ?)").run(r.rowid, r.name ?? "", r.value ?? "");
34
+ }
35
+ db.prepare("DELETE FROM nodes WHERE doc_id = ?").run(docId);
36
+ }
37
+ export function writeDocNodes(db, repoId, docId, projected) {
38
+ deleteDocNodes(db, docId);
39
+ const insert = db.prepare(`INSERT INTO nodes (node_id, repo_id, doc_id, block_id, kind, name, value, span_start, span_end, attrs)
40
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
41
+ const ftsInsert = db.prepare("INSERT INTO nodes_fts(rowid, name, value) VALUES(?, ?, ?)");
42
+ const kindCounters = new Map();
43
+ for (const p of projected) {
44
+ const key = p.kind + "|" + (p.blockId ?? "");
45
+ const ordinal = kindCounters.get(key) ?? 0;
46
+ kindCounters.set(key, ordinal + 1);
47
+ const validBlockId = p.blockId?.startsWith("b_") ? p.blockId : null;
48
+ const nodeId = mintNodeId(docId, validBlockId ?? "", p.kind, ordinal);
49
+ const name = p.name ?? null;
50
+ const value = p.value ?? null;
51
+ insert.run(nodeId, repoId, docId, validBlockId, p.kind, name, value, p.spanStart ?? null, p.spanEnd ?? null, JSON.stringify(p.attrs ?? {}));
52
+ const lastRowid = db.prepare("SELECT last_insert_rowid() AS r").get();
53
+ ftsInsert.run(lastRowid.r, name ?? "", value ?? "");
54
+ }
55
+ return projected.length;
56
+ }
57
+ //# sourceMappingURL=nodes.js.map
@@ -0,0 +1,38 @@
1
+ import type { Database } from "better-sqlite3";
2
+ export type PropertySource = "frontmatter" | "inline" | "computed";
3
+ export type PropertyCard = "scalar" | "list";
4
+ export type PropertyType = "string" | "number" | "bool" | "null" | "json";
5
+ export interface PropertyRow {
6
+ source: PropertySource;
7
+ blockId: string | null;
8
+ key: string;
9
+ card: PropertyCard;
10
+ ord: number;
11
+ valText: string | null;
12
+ valNum: number | null;
13
+ valBool: number | null;
14
+ valJson: string | null;
15
+ type: PropertyType;
16
+ }
17
+ export interface RangeValue {
18
+ lo: number | string | null;
19
+ hi: number | string | null;
20
+ exclusiveEnd: boolean;
21
+ }
22
+ /** Parse a string as a range value, or null if it is not a well-formed range.
23
+ * Strict on purpose (both bounds must share a scalar domain — numeric or ISO
24
+ * date) so ordinary strings that merely contain dots are never mis-promoted. */
25
+ export declare function detectRange(s: string): RangeValue | null;
26
+ export declare function flattenFrontmatter(obj: Record<string, unknown>): Omit<PropertyRow, "source" | "blockId">[];
27
+ export declare function flattenComputed(obj: Record<string, unknown>): Omit<PropertyRow, "source" | "blockId">[];
28
+ export declare function deleteDocProperties(db: Database, docId: string): void;
29
+ /**
30
+ * Effective (merged) property bag for a document: each key mapped to its
31
+ * authored-shaped value (scalar or array), unioned across authored sources
32
+ * (frontmatter + inline). Computed `$`-keys are included under their `$` name.
33
+ * This is the projection/hydration view that replaces docs.metadata.
34
+ */
35
+ export declare function docPropertiesMerged(db: Database, docId: string): Record<string, unknown>;
36
+ /** Properties grouped by source (docs_read shape): {frontmatter, inline, computed}. */
37
+ export declare function docPropertiesGrouped(db: Database, docId: string): Record<PropertySource, Record<string, unknown>>;
38
+ export declare function writeDocProperties(db: Database, repoId: string, docId: string, createdCommit: string, rows: PropertyRow[]): number;
@@ -0,0 +1,177 @@
1
+ import { hashHex } from "../hash.js";
2
+ // Deterministic id: a property row is identified by its document, source, key,
3
+ // and ordinal position within that (source,key). Same bytes ⇒ same id.
4
+ function propId(docId, source, key, ord) {
5
+ return "p_" + hashHex(`${docId}|${source}|${key}|${ord}`).slice(0, 12);
6
+ }
7
+ const RANGE_NUM = /^-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?$/;
8
+ const RANGE_ISO = /^\d{4}-\d{2}-\d{2}(?:[T ]\d{2}:\d{2}(?::\d{2})?(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})?)?$/;
9
+ /** Parse a string as a range value, or null if it is not a well-formed range.
10
+ * Strict on purpose (both bounds must share a scalar domain — numeric or ISO
11
+ * date) so ordinary strings that merely contain dots are never mis-promoted. */
12
+ export function detectRange(s) {
13
+ // The operator is a maximal run of exactly 2 (`..`) or 3 (`...`) dots. A single
14
+ // dot is a decimal point and stays part of a bound; a run of 4+ is not a range.
15
+ const m = /^(.*?)(\.\.\.?)(.*)$/.exec(s);
16
+ if (!m)
17
+ return null;
18
+ const loRaw = m[1], dots = m[2], hiRaw = m[3];
19
+ if (loRaw.endsWith(".") || hiRaw.startsWith("."))
20
+ return null; // adjacent dot ⇒ non-maximal run
21
+ const lo = loRaw.length ? loRaw : null;
22
+ const hi = hiRaw.length ? hiRaw : null;
23
+ if (lo === null && hi === null)
24
+ return null; // `..` with no bounds is meaningless
25
+ const exclusiveEnd = dots.length === 3;
26
+ const present = [lo, hi].filter((b) => b !== null);
27
+ if (present.every((b) => RANGE_NUM.test(b))) {
28
+ return { lo: lo === null ? null : Number(lo), hi: hi === null ? null : Number(hi), exclusiveEnd };
29
+ }
30
+ if (present.every((b) => RANGE_ISO.test(b))) {
31
+ return { lo, hi, exclusiveEnd }; // keep ISO strings; lexical ordering is chronological
32
+ }
33
+ return null; // mixed domains or non-scalar bounds ⇒ an ordinary string
34
+ }
35
+ // Classify a single JSON scalar into a typed column. Objects/arrays are not
36
+ // scalars — callers flatten those before reaching here.
37
+ function typedValue(v) {
38
+ if (v === null || v === undefined)
39
+ return { valText: null, valNum: null, valBool: null, valJson: null, type: "null" };
40
+ if (typeof v === "boolean")
41
+ return { valText: null, valNum: null, valBool: v ? 1 : 0, valJson: null, type: "bool" };
42
+ if (typeof v === "number")
43
+ return { valText: null, valNum: v, valBool: null, valJson: null, type: "number" };
44
+ if (typeof v === "string") {
45
+ const range = detectRange(v);
46
+ // A range stays type='string' (verbatim in val_text so hydration, equality,
47
+ // and FTS are unchanged); the parsed bounds ride in val_json for querying.
48
+ if (range)
49
+ return { valText: v, valNum: null, valBool: null, valJson: JSON.stringify({ __range: true, ...range }), type: "string" };
50
+ return { valText: v, valNum: null, valBool: null, valJson: null, type: "string" };
51
+ }
52
+ // object / array that resisted flattening: escape hatch.
53
+ return { valText: null, valNum: null, valBool: null, valJson: JSON.stringify(v), type: "json" };
54
+ }
55
+ function isPlainObject(v) {
56
+ return typeof v === "object" && v !== null && !Array.isArray(v);
57
+ }
58
+ // Flatten a parsed frontmatter object into property rows.
59
+ // - nested maps → dotted keys (meta.owner)
60
+ // - arrays → one card='list' row per element, ord 0..n (scalars only; an array
61
+ // of objects goes to a single val_json row rather than exploding structure)
62
+ // - scalars → one card='scalar' row
63
+ export function flattenFrontmatter(obj) {
64
+ const rows = [];
65
+ const walk = (value, key) => {
66
+ if (isPlainObject(value)) {
67
+ for (const [k, v] of Object.entries(value))
68
+ walk(v, key ? `${key}.${k}` : k);
69
+ return;
70
+ }
71
+ if (Array.isArray(value)) {
72
+ const allScalar = value.every((e) => !isPlainObject(e) && !Array.isArray(e));
73
+ if (allScalar) {
74
+ value.forEach((e, ord) => rows.push({ key, card: "list", ord, ...typedValue(e) }));
75
+ }
76
+ else {
77
+ // array of objects / nested arrays: keep as one json row (escape hatch).
78
+ rows.push({ key, card: "list", ord: 0, ...typedValue(value) });
79
+ }
80
+ return;
81
+ }
82
+ rows.push({ key, card: "scalar", ord: 0, ...typedValue(value) });
83
+ };
84
+ for (const [k, v] of Object.entries(obj))
85
+ walk(v, k);
86
+ return rows;
87
+ }
88
+ // Flatten computed properties (adapter computeProperties output) into rows.
89
+ // Keys are already $-prefixed intrinsics ($title, $tags); a value is a scalar
90
+ // (one scalar row) or an array of scalars (ord-indexed list rows).
91
+ export function flattenComputed(obj) {
92
+ const rows = [];
93
+ for (const [key, value] of Object.entries(obj)) {
94
+ if (Array.isArray(value)) {
95
+ value.forEach((e, ord) => rows.push({ key, card: "list", ord, ...typedValue(e) }));
96
+ }
97
+ else {
98
+ rows.push({ key, card: "scalar", ord: 0, ...typedValue(value) });
99
+ }
100
+ }
101
+ return rows;
102
+ }
103
+ export function deleteDocProperties(db, docId) {
104
+ db.prepare("DELETE FROM properties WHERE doc_id = ?").run(docId);
105
+ }
106
+ function decode(r) {
107
+ switch (r.type) {
108
+ case "string": return r.val_text;
109
+ case "number": return r.val_num;
110
+ case "bool": return r.val_bool === 1;
111
+ case "null": return null;
112
+ case "json": return r.val_json ? JSON.parse(r.val_json) : null;
113
+ }
114
+ }
115
+ // Collapse a key's rows to the authored shape: a lone scalar row → the scalar;
116
+ // anything else (list rows, or multiple values) → an array in (source-rank,
117
+ // ord) order. Deterministic — see docs/properties-table.md §9.
118
+ const SOURCE_RANK = { frontmatter: 0, inline: 1, computed: 2 };
119
+ function shapeValues(rows) {
120
+ if (rows.length === 1 && rows[0].card === "scalar")
121
+ return decode(rows[0]);
122
+ const ordered = [...rows].sort((a, b) => SOURCE_RANK[a.source] - SOURCE_RANK[b.source] || a.ord - b.ord);
123
+ return ordered.map(decode);
124
+ }
125
+ /**
126
+ * Effective (merged) property bag for a document: each key mapped to its
127
+ * authored-shaped value (scalar or array), unioned across authored sources
128
+ * (frontmatter + inline). Computed `$`-keys are included under their `$` name.
129
+ * This is the projection/hydration view that replaces docs.metadata.
130
+ */
131
+ export function docPropertiesMerged(db, docId) {
132
+ const rows = db.prepare("SELECT source, key, card, ord, val_text, val_num, val_bool, val_json, type FROM properties WHERE doc_id = ? AND deleted_commit IS NULL").all(docId);
133
+ const byKey = new Map();
134
+ for (const r of rows) {
135
+ const g = byKey.get(r.key) ?? [];
136
+ g.push(r);
137
+ byKey.set(r.key, g);
138
+ }
139
+ const out = {};
140
+ for (const [key, group] of byKey)
141
+ out[key] = shapeValues(group);
142
+ return out;
143
+ }
144
+ /** Properties grouped by source (docs_read shape): {frontmatter, inline, computed}. */
145
+ export function docPropertiesGrouped(db, docId) {
146
+ const rows = db.prepare("SELECT source, key, card, ord, val_text, val_num, val_bool, val_json, type FROM properties WHERE doc_id = ? AND deleted_commit IS NULL").all(docId);
147
+ const grouped = {
148
+ frontmatter: new Map(), inline: new Map(), computed: new Map(),
149
+ };
150
+ for (const r of rows) {
151
+ const m = grouped[r.source];
152
+ const g = m.get(r.key) ?? [];
153
+ g.push(r);
154
+ m.set(r.key, g);
155
+ }
156
+ const out = { frontmatter: {}, inline: {}, computed: {} };
157
+ for (const src of ["frontmatter", "inline", "computed"]) {
158
+ for (const [key, group] of grouped[src])
159
+ out[src][key] = shapeValues(group);
160
+ }
161
+ return out;
162
+ }
163
+ // Replace a document's property rows. Deterministic prop_id per (source,key,ord)
164
+ // gives dedup/stable identity; a genuine duplicate (same source+key+ord) is
165
+ // collapsed by the primary key (INSERT OR REPLACE).
166
+ export function writeDocProperties(db, repoId, docId, createdCommit, rows) {
167
+ deleteDocProperties(db, docId);
168
+ const insert = db.prepare(`INSERT OR REPLACE INTO properties
169
+ (prop_id, repo_id, doc_id, block_id, source, key, card, ord,
170
+ val_text, val_num, val_bool, val_json, type, created_commit)
171
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
172
+ for (const r of rows) {
173
+ insert.run(propId(docId, r.source, r.key, r.ord), repoId, docId, r.blockId, r.source, r.key, r.card, r.ord, r.valText, r.valNum, r.valBool, r.valJson, r.type, createdCommit);
174
+ }
175
+ return rows.length;
176
+ }
177
+ //# sourceMappingURL=properties.js.map
@@ -0,0 +1,3 @@
1
+ import type { Store } from "./store.js";
2
+ export type RebuildTarget = "sections" | "edges" | "fts" | "block_changes" | "all";
3
+ export declare function rebuildIndex(store: Store, target?: RebuildTarget): void;
@@ -0,0 +1,29 @@
1
+ import { rebuildSections } from "./sections.js";
2
+ import { rebuildDocEdges } from "./edges.js";
3
+ import { ftsIndexDoc } from "./fts.js";
4
+ export function rebuildIndex(store, target = "all") {
5
+ store.write((db) => {
6
+ const docIds = db.prepare("SELECT doc_id FROM docs WHERE deleted_commit IS NULL").all().map((r) => r.doc_id);
7
+ if (target === "sections" || target === "all") {
8
+ for (const docId of docIds)
9
+ rebuildSections(db, docId);
10
+ }
11
+ if (target === "edges" || target === "all") {
12
+ // doc_edges is a pure rollup of the open `edges` rows; rebuild per doc.
13
+ for (const docId of docIds)
14
+ rebuildDocEdges(db, docId);
15
+ }
16
+ if (target === "fts" || target === "all") {
17
+ // Rebuild the external-content FTS index from current block text.
18
+ db.exec("INSERT INTO blocks_fts(blocks_fts) VALUES('rebuild')");
19
+ }
20
+ if (target === "block_changes" || target === "all") {
21
+ // block_changes is a projection of dispositions across commits: rebuild
22
+ // it verbatim from the dispositions table (which is durable).
23
+ db.prepare("DELETE FROM block_changes").run();
24
+ db.exec("INSERT OR IGNORE INTO block_changes (block_id, commit_id, kind) SELECT block_id, commit_id, kind FROM dispositions");
25
+ }
26
+ });
27
+ void ftsIndexDoc; // fts rebuild uses the FTS5 'rebuild' command directly
28
+ }
29
+ //# sourceMappingURL=rebuild.js.map
@@ -0,0 +1,9 @@
1
+ export declare const SCHEMA_VERSION = 13;
2
+ export declare const FILE_STATS_DDL = "\nCREATE TABLE IF NOT EXISTS file_stats (\n repo_id TEXT NOT NULL,\n path TEXT NOT NULL,\n mtime_ns INTEGER NOT NULL,\n size INTEGER NOT NULL,\n hash BLOB NOT NULL,\n PRIMARY KEY (repo_id, path)\n);\n";
3
+ export declare const PROPERTIES_DDL = "\nCREATE TABLE IF NOT EXISTS properties (\n prop_id TEXT PRIMARY KEY,\n repo_id TEXT NOT NULL,\n doc_id TEXT NOT NULL,\n block_id TEXT, -- NULL = document-scoped (frontmatter, computed)\n source TEXT NOT NULL CHECK (source IN ('frontmatter','inline','computed')),\n key TEXT NOT NULL, -- dotted, flattened: \"layer\",\"meta.owner\"; computed carry $: \"$title\"\n card TEXT NOT NULL CHECK (card IN ('scalar','list')),\n ord INTEGER NOT NULL DEFAULT 0,\n val_text TEXT,\n val_num REAL,\n val_bool INTEGER,\n val_json TEXT,\n type TEXT NOT NULL CHECK (type IN ('string','number','bool','null','json')),\n created_commit TEXT NOT NULL,\n deleted_commit TEXT\n);\nCREATE INDEX IF NOT EXISTS idx_props_doc ON properties(doc_id) WHERE deleted_commit IS NULL;\nCREATE INDEX IF NOT EXISTS idx_props_key_text ON properties(repo_id, key, val_text) WHERE deleted_commit IS NULL;\nCREATE INDEX IF NOT EXISTS idx_props_key_num ON properties(repo_id, key, val_num) WHERE deleted_commit IS NULL;\nCREATE INDEX IF NOT EXISTS idx_props_src_key ON properties(repo_id, source, key) WHERE deleted_commit IS NULL;\n";
4
+ export declare const SYNC_DDL = "\nCREATE TABLE IF NOT EXISTS adapters (\n name TEXT PRIMARY KEY, -- workspace-unique adapter name (e.g. \"fs\")\n command TEXT NOT NULL, -- argv[0] to spawn (e.g. \"omgbase-fs-adapter\")\n args TEXT NOT NULL DEFAULT '[]' -- fixed leading args (JSON string[])\n);\n\nCREATE TABLE IF NOT EXISTS sources (\n source_id TEXT PRIMARY KEY,\n name TEXT NOT NULL UNIQUE, -- workspace-unique source name\n adapter TEXT NOT NULL REFERENCES adapters(name),\n config TEXT NOT NULL DEFAULT '{}', -- JSON object \u2192 rendered to flags (13 \u00A73.1)\n env TEXT NOT NULL DEFAULT '{}' -- JSON object \u2192 spawn env (secrets; 13 \u00A73.2)\n);\n\nCREATE TABLE IF NOT EXISTS attachments (\n repo_id TEXT NOT NULL REFERENCES repos(repo_id),\n source_id TEXT NOT NULL REFERENCES sources(source_id),\n PRIMARY KEY (repo_id, source_id)\n);\n\nCREATE TABLE IF NOT EXISTS sync_state (\n repo_id TEXT NOT NULL,\n source_id TEXT NOT NULL,\n path TEXT NOT NULL, -- '' reserved for the attachment-level cursor row\n revision TEXT, -- last-observed source revision for this path\n cursor TEXT, -- last poll/webhook cursor (attachment-level row)\n PRIMARY KEY (repo_id, source_id, path)\n);\n";
5
+ export declare const WORKSPACE_SETTINGS_DDL = "\nCREATE TABLE IF NOT EXISTS workspace_settings (\n id INTEGER PRIMARY KEY CHECK (id = 0),\n settings TEXT NOT NULL DEFAULT '{}'\n);\nINSERT OR IGNORE INTO workspace_settings (id, settings) VALUES (0, '{}');\n";
6
+ export declare const NODES_DDL = "\nCREATE TABLE IF NOT EXISTS nodes (\n node_id TEXT PRIMARY KEY,\n repo_id TEXT NOT NULL,\n doc_id TEXT NOT NULL,\n block_id TEXT,\n kind TEXT NOT NULL,\n name TEXT,\n value TEXT,\n span_start INTEGER,\n span_end INTEGER,\n attrs TEXT NOT NULL DEFAULT '{}'\n);\nCREATE INDEX IF NOT EXISTS idx_nodes_doc ON nodes(doc_id);\nCREATE INDEX IF NOT EXISTS idx_nodes_kind ON nodes(kind);\nCREATE INDEX IF NOT EXISTS idx_nodes_name ON nodes(name) WHERE name IS NOT NULL;\nCREATE VIRTUAL TABLE IF NOT EXISTS nodes_fts USING fts5(\n name, value, content='nodes', content_rowid='rowid', tokenize='porter unicode61'\n);\n";
7
+ export declare const DOC_EMBEDDINGS_DDL = "\nCREATE TABLE IF NOT EXISTS doc_embeddings (\n doc_id TEXT NOT NULL,\n model TEXT NOT NULL,\n input_hash BLOB NOT NULL,\n method TEXT NOT NULL CHECK (method IN ('whole','pooled')),\n dim INTEGER NOT NULL,\n vec BLOB NOT NULL,\n PRIMARY KEY (doc_id, model)\n);\n";
8
+ export declare const MIGRATIONS: Record<number, string>;
9
+ export declare const DDL = "\n-- ---- durable tables (02 \u00A73) --------------------------------------------------\nCREATE TABLE IF NOT EXISTS repos (\n repo_id TEXT PRIMARY KEY,\n slug TEXT NOT NULL UNIQUE,\n settings TEXT NOT NULL DEFAULT '{}'\n);\n-- A repo owns identity + history, NOT a filesystem (ADR-014). Where its bytes\n-- come from is a sources row (adapter=fs, config.root) joined via attachments;\n-- the former repos.root_path column was removed in schema v13.\n\nCREATE TABLE IF NOT EXISTS docs (\n doc_id TEXT PRIMARY KEY,\n repo_id TEXT NOT NULL REFERENCES repos(repo_id),\n path TEXT NOT NULL,\n format TEXT NOT NULL DEFAULT 'markdown',\n current_rev TEXT,\n file_hash BLOB,\n conflicted INTEGER NOT NULL DEFAULT 0,\n leading_trivia TEXT NOT NULL DEFAULT '',\n frontmatter_trivia TEXT, -- separator between frontmatter and first body block (NULL = no frontmatter)\n deleted_commit TEXT,\n UNIQUE (repo_id, path)\n);\n\nCREATE TABLE IF NOT EXISTS blocks (\n block_id TEXT PRIMARY KEY,\n repo_id TEXT NOT NULL REFERENCES repos(repo_id),\n doc_id TEXT NOT NULL REFERENCES docs(doc_id),\n parent_block TEXT,\n order_key TEXT NOT NULL,\n ordinal INTEGER NOT NULL,\n depth INTEGER NOT NULL,\n ancestor_path TEXT NOT NULL,\n type TEXT NOT NULL,\n attrs TEXT NOT NULL DEFAULT '{}',\n text TEXT NOT NULL,\n raw_hash BLOB NOT NULL,\n norm_hash BLOB NOT NULL,\n trivia_hash BLOB, -- trailing trivia blob hash (NULL = no trivia)\n created_commit TEXT NOT NULL,\n deleted_commit TEXT\n);\nCREATE INDEX IF NOT EXISTS idx_blocks_doc ON blocks(doc_id, parent_block, order_key) WHERE deleted_commit IS NULL;\nCREATE INDEX IF NOT EXISTS idx_blocks_type ON blocks(repo_id, type) WHERE deleted_commit IS NULL;\nCREATE INDEX IF NOT EXISTS idx_blocks_rawhash ON blocks(raw_hash);\nCREATE INDEX IF NOT EXISTS idx_blocks_ancestor ON blocks(doc_id, ancestor_path);\n\nCREATE TABLE IF NOT EXISTS blobs (\n hash BLOB PRIMARY KEY,\n bytes BLOB NOT NULL,\n size INTEGER NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS tree_nodes (\n hash BLOB PRIMARY KEY,\n entries TEXT NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS revisions (\n rev_id TEXT PRIMARY KEY,\n doc_id TEXT NOT NULL REFERENCES docs(doc_id),\n seq INTEGER NOT NULL,\n root_tree BLOB NOT NULL REFERENCES tree_nodes(hash),\n frontmatter_blob BLOB REFERENCES blobs(hash),\n rendered_hash BLOB NOT NULL,\n path TEXT NOT NULL,\n commit_id TEXT NOT NULL REFERENCES commits(commit_id),\n UNIQUE (doc_id, seq)\n);\n\nCREATE TABLE IF NOT EXISTS commits (\n commit_id TEXT PRIMARY KEY,\n repo_id TEXT NOT NULL REFERENCES repos(repo_id),\n seq INTEGER NOT NULL,\n ts TEXT NOT NULL,\n origin TEXT NOT NULL CHECK (origin IN ('api','observed','import','projection')),\n actor TEXT,\n reason TEXT,\n checkpoint_id TEXT,\n ops TEXT,\n UNIQUE (repo_id, seq)\n);\n\nCREATE TABLE IF NOT EXISTS dispositions (\n commit_id TEXT NOT NULL REFERENCES commits(commit_id),\n block_id TEXT NOT NULL,\n kind TEXT NOT NULL CHECK (kind IN\n ('same','edited','moved','edited_moved','inserted','deleted',\n 'split_from','merged_into','copied_from','resurrected','bulk_rewrite')),\n confidence REAL,\n reason TEXT,\n matcher_v TEXT,\n detail TEXT NOT NULL DEFAULT '{}',\n PRIMARY KEY (commit_id, block_id, kind)\n);\n\nCREATE TABLE IF NOT EXISTS edges (\n edge_id TEXT PRIMARY KEY,\n repo_id TEXT NOT NULL,\n src_doc TEXT NOT NULL,\n src_block TEXT,\n src_field TEXT,\n predicate TEXT NOT NULL,\n dst_kind TEXT NOT NULL CHECK (dst_kind IN ('document','block','external','collection')),\n dst_node TEXT NOT NULL,\n anchor TEXT,\n provenance TEXT NOT NULL CHECK (provenance IN ('link','frontmatter','inline_field','projected',\n 'yaml_ref','yaml_schema','yaml_extends','json_ref','json_schema')),\n via_node TEXT,\n from_commit TEXT NOT NULL,\n to_commit TEXT\n);\nCREATE INDEX IF NOT EXISTS idx_edges_src ON edges(src_doc, predicate) WHERE to_commit IS NULL;\nCREATE INDEX IF NOT EXISTS idx_edges_dst ON edges(dst_node, predicate) WHERE to_commit IS NULL;\nCREATE INDEX IF NOT EXISTS idx_edges_temporal ON edges(from_commit, to_commit);\n\nCREATE TABLE IF NOT EXISTS external_nodes (\n node_id TEXT PRIMARY KEY,\n repo_id TEXT NOT NULL,\n uri TEXT NOT NULL,\n title TEXT,\n UNIQUE (repo_id, uri)\n);\n\nCREATE TABLE IF NOT EXISTS collections (\n node_id TEXT PRIMARY KEY,\n repo_id TEXT NOT NULL,\n name TEXT NOT NULL,\n spec TEXT NOT NULL DEFAULT '{}'\n);\n\nCREATE TABLE IF NOT EXISTS checkpoints (\n id TEXT PRIMARY KEY,\n repo_id TEXT NOT NULL,\n ts TEXT NOT NULL,\n files TEXT NOT NULL,\n git_head TEXT\n);\n\nCREATE TABLE IF NOT EXISTS resurrection_pool (\n block_id TEXT PRIMARY KEY,\n repo_id TEXT NOT NULL,\n doc_id TEXT NOT NULL,\n raw_hash BLOB NOT NULL,\n norm_hash BLOB NOT NULL,\n type TEXT NOT NULL,\n deleted_commit TEXT NOT NULL,\n expires_ts TEXT NOT NULL\n);\n\n-- ---- derived tables (02 \u00A74; rebuildable) ------------------------------------\nCREATE TABLE IF NOT EXISTS sections (\n heading_block TEXT NOT NULL,\n doc_id TEXT NOT NULL,\n level INTEGER NOT NULL,\n first_ordinal INTEGER NOT NULL,\n last_ordinal INTEGER NOT NULL,\n PRIMARY KEY (doc_id, heading_block)\n);\n\nCREATE TABLE IF NOT EXISTS doc_edges (\n src_doc TEXT NOT NULL,\n predicate TEXT NOT NULL,\n dst_node TEXT NOT NULL,\n dst_kind TEXT NOT NULL,\n count INTEGER NOT NULL,\n samples TEXT NOT NULL,\n PRIMARY KEY (src_doc, predicate, dst_node)\n);\n\nCREATE TABLE IF NOT EXISTS block_changes (\n block_id TEXT NOT NULL,\n commit_id TEXT NOT NULL,\n kind TEXT NOT NULL,\n PRIMARY KEY (block_id, commit_id, kind)\n);\n\nCREATE TABLE IF NOT EXISTS inferred_edges (\n src_node TEXT NOT NULL,\n dst_node TEXT NOT NULL,\n predicate TEXT NOT NULL,\n method TEXT NOT NULL,\n score REAL NOT NULL,\n model_v TEXT NOT NULL,\n computed_at TEXT NOT NULL,\n PRIMARY KEY (src_node, dst_node, predicate, method)\n);\n\nCREATE TABLE IF NOT EXISTS embeddings (\n content_hash BLOB NOT NULL,\n ctx_hash BLOB NOT NULL,\n model TEXT NOT NULL,\n dim INTEGER NOT NULL,\n vec BLOB NOT NULL,\n PRIMARY KEY (content_hash, ctx_hash, model)\n);\n\n\nCREATE TABLE IF NOT EXISTS doc_embeddings (\n doc_id TEXT NOT NULL,\n model TEXT NOT NULL,\n input_hash BLOB NOT NULL,\n method TEXT NOT NULL CHECK (method IN ('whole','pooled')),\n dim INTEGER NOT NULL,\n vec BLOB NOT NULL,\n PRIMARY KEY (doc_id, model)\n);\n\n\nCREATE VIRTUAL TABLE IF NOT EXISTS blocks_fts USING fts5(\n text, content='blocks', content_rowid='rowid', tokenize='porter unicode61'\n);\n\nCREATE TABLE IF NOT EXISTS nodes (\n node_id TEXT PRIMARY KEY,\n repo_id TEXT NOT NULL,\n doc_id TEXT NOT NULL,\n block_id TEXT,\n kind TEXT NOT NULL,\n name TEXT,\n value TEXT,\n span_start INTEGER,\n span_end INTEGER,\n attrs TEXT NOT NULL DEFAULT '{}'\n);\nCREATE INDEX IF NOT EXISTS idx_nodes_doc ON nodes(doc_id);\nCREATE INDEX IF NOT EXISTS idx_nodes_kind ON nodes(kind);\nCREATE INDEX IF NOT EXISTS idx_nodes_name ON nodes(name) WHERE name IS NOT NULL;\n\nCREATE VIRTUAL TABLE IF NOT EXISTS nodes_fts USING fts5(\n name, value, content='nodes', content_rowid='rowid', tokenize='porter unicode61'\n);\n\n\nCREATE TABLE IF NOT EXISTS file_stats (\n repo_id TEXT NOT NULL,\n path TEXT NOT NULL,\n mtime_ns INTEGER NOT NULL,\n size INTEGER NOT NULL,\n hash BLOB NOT NULL,\n PRIMARY KEY (repo_id, path)\n);\n\n\nCREATE TABLE IF NOT EXISTS properties (\n prop_id TEXT PRIMARY KEY,\n repo_id TEXT NOT NULL,\n doc_id TEXT NOT NULL,\n block_id TEXT, -- NULL = document-scoped (frontmatter, computed)\n source TEXT NOT NULL CHECK (source IN ('frontmatter','inline','computed')),\n key TEXT NOT NULL, -- dotted, flattened: \"layer\",\"meta.owner\"; computed carry $: \"$title\"\n card TEXT NOT NULL CHECK (card IN ('scalar','list')),\n ord INTEGER NOT NULL DEFAULT 0,\n val_text TEXT,\n val_num REAL,\n val_bool INTEGER,\n val_json TEXT,\n type TEXT NOT NULL CHECK (type IN ('string','number','bool','null','json')),\n created_commit TEXT NOT NULL,\n deleted_commit TEXT\n);\nCREATE INDEX IF NOT EXISTS idx_props_doc ON properties(doc_id) WHERE deleted_commit IS NULL;\nCREATE INDEX IF NOT EXISTS idx_props_key_text ON properties(repo_id, key, val_text) WHERE deleted_commit IS NULL;\nCREATE INDEX IF NOT EXISTS idx_props_key_num ON properties(repo_id, key, val_num) WHERE deleted_commit IS NULL;\nCREATE INDEX IF NOT EXISTS idx_props_src_key ON properties(repo_id, source, key) WHERE deleted_commit IS NULL;\n\n\nCREATE TABLE IF NOT EXISTS adapters (\n name TEXT PRIMARY KEY, -- workspace-unique adapter name (e.g. \"fs\")\n command TEXT NOT NULL, -- argv[0] to spawn (e.g. \"omgbase-fs-adapter\")\n args TEXT NOT NULL DEFAULT '[]' -- fixed leading args (JSON string[])\n);\n\nCREATE TABLE IF NOT EXISTS sources (\n source_id TEXT PRIMARY KEY,\n name TEXT NOT NULL UNIQUE, -- workspace-unique source name\n adapter TEXT NOT NULL REFERENCES adapters(name),\n config TEXT NOT NULL DEFAULT '{}', -- JSON object \u2192 rendered to flags (13 \u00A73.1)\n env TEXT NOT NULL DEFAULT '{}' -- JSON object \u2192 spawn env (secrets; 13 \u00A73.2)\n);\n\nCREATE TABLE IF NOT EXISTS attachments (\n repo_id TEXT NOT NULL REFERENCES repos(repo_id),\n source_id TEXT NOT NULL REFERENCES sources(source_id),\n PRIMARY KEY (repo_id, source_id)\n);\n\nCREATE TABLE IF NOT EXISTS sync_state (\n repo_id TEXT NOT NULL,\n source_id TEXT NOT NULL,\n path TEXT NOT NULL, -- '' reserved for the attachment-level cursor row\n revision TEXT, -- last-observed source revision for this path\n cursor TEXT, -- last poll/webhook cursor (attachment-level row)\n PRIMARY KEY (repo_id, source_id, path)\n);\n\n\nCREATE TABLE IF NOT EXISTS workspace_settings (\n id INTEGER PRIMARY KEY CHECK (id = 0),\n settings TEXT NOT NULL DEFAULT '{}'\n);\nINSERT OR IGNORE INTO workspace_settings (id, settings) VALUES (0, '{}');\n\n";