@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,54 @@
1
+ /** How a source's identity relates to omgbase block/doc identity (13 §5.2). */
2
+ export type SourceIdentity =
3
+ /** Anonymous bytes; block continuity must be inferred by the reconciler. */
4
+ "inferred"
5
+ /** Stable per-member ids from upstream; the matcher is skipped (v1: unused). */
6
+ | "borne";
7
+ export interface SourceCapabilities {
8
+ identity: SourceIdentity;
9
+ /** Can engine-authored mutations be pushed back to the source? */
10
+ writeThrough: boolean;
11
+ /** Can the source stream a change feed, or is it poll-only (engine re-enumerates)? */
12
+ watch: boolean;
13
+ }
14
+ /** A member of the source scope: its storage key + cheap change-token (13 §4.2). */
15
+ export interface SourceEntry {
16
+ /** Storage key — repo-relative canonical path (docs.path). */
17
+ path: string;
18
+ /** The source's cheap change-token; equal ⇒ unchanged ⇒ engine no-op. */
19
+ revision: string;
20
+ /** Source locator when it differs from `path`. Defaults to `path` (13 §5.2). */
21
+ sourceId?: string;
22
+ }
23
+ /** A hydrated member: its entry plus the bytes handed to ingestFile. */
24
+ export interface SourceItem extends SourceEntry {
25
+ /** The engine hashes this itself for the authoritative echo/convergence gate. */
26
+ content: string;
27
+ }
28
+ /** A batch of changed storage-keys delivered by a live watch (13 §4.3). */
29
+ export type WatchListener = (paths: string[]) => void;
30
+ /** A live subscription created by SyncSource.watch. */
31
+ export interface SourceWatch {
32
+ /** Stop delivering batches and release resources. */
33
+ stop(): Promise<void>;
34
+ }
35
+ /**
36
+ * The in-engine handle to an external source process. Only capabilities/
37
+ * enumerate/fetch are always meaningful; watch exists when capabilities().watch,
38
+ * write/remove when capabilities().writeThrough. close() terminates the process.
39
+ */
40
+ export interface SyncSource {
41
+ capabilities(): SourceCapabilities;
42
+ /** The full current scope as (path, revision) pairs. */
43
+ enumerate(): Promise<SourceEntry[]>;
44
+ /** Current state of one member, or null if it left the scope (a delete). */
45
+ fetch(path: string): Promise<SourceItem | null>;
46
+ /** Subscribe to debounced change batches (push sources). */
47
+ watch?(onBatch: WatchListener): Promise<SourceWatch>;
48
+ /** Persist engine-authored bytes back to the source (writeThrough only). */
49
+ write?(path: string, content: string): Promise<void>;
50
+ /** Remove a member from the source (writeThrough only). */
51
+ remove?(path: string): Promise<void>;
52
+ /** Terminate the underlying process / release resources. */
53
+ close(): Promise<void>;
54
+ }
@@ -0,0 +1,12 @@
1
+ // Sync source contract (sync-plugins). A SyncSource abstracts an external
2
+ // source scope (filesystem, git, GitHub, Linear, …) behind enumerate/fetch/watch
3
+ // keyed on a cheap `revision` change-token. Sources are EXTERNAL PROCESSES spoken
4
+ // to over stdio (see external-source.ts); this interface is the in-engine view of
5
+ // one. Because every call crosses a pipe, the contract is async.
6
+ //
7
+ // The engine owns everything below the bytes — content-hash echo suppression,
8
+ // reconciliation, commits, convergence, and durable revision/cursor state — so a
9
+ // source only reports membership and transports bytes; it can never write a
10
+ // commit, mint identity, or bypass the convergence check.
11
+ export {};
12
+ //# sourceMappingURL=plugin.js.map
@@ -0,0 +1,12 @@
1
+ import type { Database } from "better-sqlite3";
2
+ import type { Store } from "../core/store/store.js";
3
+ import type { IdResolver } from "../core/ingest.js";
4
+ import { type MatchBlock, type ReconcileConfig } from "../reconcile/types.js";
5
+ export declare function loadOldMatchBlocks(db: Database, docId: string): MatchBlock[];
6
+ /** Build an IdResolver bound to this store + repo, reconciling against the
7
+ * document's current revision. */
8
+ export declare function makeReconcilingResolver(store: Store, repoId: string, opts?: {
9
+ ts?: string;
10
+ config?: ReconcileConfig;
11
+ path?: string;
12
+ }): IdResolver;
@@ -0,0 +1,209 @@
1
+ import { mintId } from "../core/ids.js";
2
+ import { extractFromBlock, extractFromFrontmatter, resolveRelativePath } from "../graph/extract.js";
3
+ import { resolveExternal, resolveDocPath } from "../core/store/edges.js";
4
+ import { sha256, normalizeVisibleText } from "../core/hash.js";
5
+ import { reconcileDocument } from "../reconcile/reconcile.js";
6
+ import { flatten } from "../reconcile/flatten.js";
7
+ import { DEFAULT_CONFIG } from "../reconcile/types.js";
8
+ import { adapterForPath } from "../format/index.js";
9
+ // Rebuild the old tree as positional MatchBlocks straight from stored blocks +
10
+ // blob bytes (so text/hashes match what reconcile expects). Exported so the
11
+ // whole-document update planner (mutate/plan-update.ts) reconciles against the
12
+ // same old-tree representation the observation path uses.
13
+ export function loadOldMatchBlocks(db, docId) {
14
+ const rows = db
15
+ .prepare(`SELECT block_id, parent_block, ordinal, type, raw_hash
16
+ FROM blocks WHERE doc_id = ? AND deleted_commit IS NULL
17
+ ORDER BY parent_block, ordinal`)
18
+ .all(docId);
19
+ // Build positional keys mirroring the flatten() convention. We need parent's
20
+ // key, so process in a way that resolves parents first (top-level then down).
21
+ const byId = new Map();
22
+ for (const r of rows)
23
+ byId.set(r.block_id, r);
24
+ // Compute a positional key per block: parentKey + '/' + ordinal.
25
+ const keyOf = new Map();
26
+ const parentKeyOf = new Map();
27
+ const resolveKey = (r) => {
28
+ if (keyOf.has(r.block_id))
29
+ return keyOf.get(r.block_id);
30
+ let parentKey = null;
31
+ if (r.parent_block && byId.has(r.parent_block))
32
+ parentKey = resolveKey(byId.get(r.parent_block));
33
+ const key = `${parentKey ?? ""}/${r.ordinal}`;
34
+ keyOf.set(r.block_id, key);
35
+ parentKeyOf.set(r.block_id, parentKey);
36
+ return key;
37
+ };
38
+ const blobBytes = db.prepare("SELECT bytes FROM blobs WHERE hash = ?");
39
+ return rows.map((r) => {
40
+ const key = resolveKey(r);
41
+ const raw = blobBytes.get(r.raw_hash)?.bytes.toString("utf8") ?? "";
42
+ const text = normalizeVisibleText(raw, r.type);
43
+ return {
44
+ blockId: r.block_id,
45
+ type: r.type,
46
+ rawHashHex: r.raw_hash.toString("hex"),
47
+ normHashHex: sha256(text).toString("hex"),
48
+ text,
49
+ anchors: [],
50
+ parentKey: parentKeyOf.get(r.block_id) ?? null,
51
+ index: r.ordinal,
52
+ key,
53
+ };
54
+ });
55
+ }
56
+ function loadPool(db, repoId, ts) {
57
+ const rows = db
58
+ .prepare("SELECT block_id, raw_hash, norm_hash, type FROM resurrection_pool WHERE repo_id = ? AND expires_ts > ?")
59
+ .all(repoId, ts);
60
+ return rows.map((r) => ({
61
+ blockId: r.block_id,
62
+ rawHashHex: r.raw_hash.toString("hex"),
63
+ normHashHex: r.norm_hash.toString("hex"),
64
+ type: r.type,
65
+ }));
66
+ }
67
+ // Assign ids from a reconcile assignment (new key → id) onto a fresh
68
+ // TreeInputBlock tree, minting for keys the assignment missed.
69
+ function assignFromMap(blocks, assignment) {
70
+ const walk = (list, parentKey) => list.map((b, index) => {
71
+ const key = `${parentKey ?? ""}/${index}`;
72
+ const blockId = assignment.get(key) ?? mintId("b");
73
+ return { blockId, type: b.type, raw: b.raw, trivia: b.trivia, attrs: b.attrs, children: walk(b.children, key) };
74
+ });
75
+ return walk(blocks, null);
76
+ }
77
+ /** Build an IdResolver bound to this store + repo, reconciling against the
78
+ * document's current revision. */
79
+ export function makeReconcilingResolver(store, repoId, opts = {}) {
80
+ const ts = opts.ts ?? new Date().toISOString();
81
+ const config = opts.config ?? DEFAULT_CONFIG;
82
+ const adapter = opts.path ? adapterForPath(opts.path) : undefined;
83
+ const docDir = opts.path ? opts.path.replace(/[^/]*$/, "") : "";
84
+ return (rest, docId) => {
85
+ const db = store.db;
86
+ const oldBlocks = docId ? loadOldMatchBlocks(db, docId) : [];
87
+ const newBlocks = flatten(rest.map(toFlatSource));
88
+ const pool = docId ? loadPool(db, repoId, ts) : [];
89
+ const result = reconcileDocument(oldBlocks, newBlocks, { config, pool });
90
+ const assigned = assignFromMap(rest, result.assignment);
91
+ const dispositions = result.dispositions.map((d) => ({
92
+ blockId: d.blockId,
93
+ kind: d.kind,
94
+ confidence: d.confidence,
95
+ reason: d.reason,
96
+ matcherV: d.matcherV,
97
+ detail: d.detail,
98
+ }));
99
+ // Edge extraction: adapter-aware. For formats with extractEdges(), use the
100
+ // adapter; otherwise fall back to markdown's per-block link scanner.
101
+ const extractEdges = (thisDocId, metadata) => {
102
+ const out = [];
103
+ if (adapter?.extractEdges) {
104
+ const rawBlocks = collectRawBlocks(assigned);
105
+ const adapterEdges = adapter.extractEdges(rawBlocks, metadata);
106
+ for (const e of adapterEdges) {
107
+ out.push(resolveAdapterEdge(db, repoId, thisDocId, e, docDir));
108
+ }
109
+ }
110
+ else {
111
+ // Markdown fallback: per-block link scanning + frontmatter relations.
112
+ const walk = (blocks) => {
113
+ for (const b of blocks) {
114
+ for (const e of extractFromBlock(b.blockId, b.type, b.raw)) {
115
+ out.push(resolveEdge(db, repoId, thisDocId, e));
116
+ }
117
+ if (b.children.length > 0)
118
+ walk(b.children);
119
+ }
120
+ };
121
+ walk(assigned);
122
+ for (const e of extractFromFrontmatter(metadata))
123
+ out.push(resolveEdge(db, repoId, thisDocId, e));
124
+ }
125
+ return out;
126
+ };
127
+ return { assigned, dispositions, deleted: result.deleted, consumedPool: result.consumedPool, extractEdges };
128
+ };
129
+ }
130
+ // Resolve an extracted edge's target to a node id (external mint, doc path or
131
+ // phantom). Block-anchor targets resolve to a document node in v1 (block-ref
132
+ // resolution requires the anchor index; the anchor is preserved on the edge).
133
+ function resolveEdge(db, repoId, srcDoc, e) {
134
+ let dstNode;
135
+ let dstKind = e.dstKind;
136
+ if (e.dstKind === "external") {
137
+ dstNode = resolveExternal(db, repoId, e.target);
138
+ }
139
+ else if (e.target === "") {
140
+ // pure fragment (#H / ^ref) → self document
141
+ dstNode = srcDoc;
142
+ dstKind = "document";
143
+ }
144
+ else {
145
+ const resolved = resolveDocPath(db, repoId, e.target);
146
+ dstNode = resolved.id;
147
+ dstKind = "document";
148
+ }
149
+ return {
150
+ srcDoc,
151
+ srcBlock: e.srcBlock,
152
+ srcField: e.srcField,
153
+ predicate: e.predicate,
154
+ dstKind,
155
+ dstNode,
156
+ anchor: e.anchor,
157
+ provenance: e.provenance,
158
+ };
159
+ }
160
+ // resolveRelativePath (./foo, ../bar against the source doc's directory) lives
161
+ // in graph/extract.ts so docs_move's inbound-link scan resolves identically.
162
+ // Resolve an adapter-produced edge to a ResolvedEdgeRow. Same resolution
163
+ // logic as resolveEdge but accepts the AdapterEdge shape, with relative path
164
+ // resolution against the source document's directory.
165
+ function resolveAdapterEdge(db, repoId, srcDoc, e, docDir) {
166
+ let dstNode;
167
+ let dstKind = e.dstKind;
168
+ if (e.dstKind === "external") {
169
+ dstNode = resolveExternal(db, repoId, e.target);
170
+ }
171
+ else if (e.target === "") {
172
+ dstNode = srcDoc;
173
+ dstKind = "document";
174
+ }
175
+ else {
176
+ const resolved = resolveDocPath(db, repoId, resolveRelativePath(e.target, docDir));
177
+ dstNode = resolved.id;
178
+ dstKind = "document";
179
+ }
180
+ return {
181
+ srcDoc,
182
+ srcBlock: e.srcBlock,
183
+ srcField: e.srcField,
184
+ predicate: e.predicate,
185
+ dstKind,
186
+ dstNode,
187
+ anchor: e.anchor,
188
+ provenance: e.provenance,
189
+ };
190
+ }
191
+ // Collect RawBlock-shaped objects from the id-assigned tree for adapter edge extraction.
192
+ function collectRawBlocks(blocks) {
193
+ return blocks.map((b) => ({
194
+ type: b.type,
195
+ span: { start: 0, end: 0 },
196
+ raw: b.raw,
197
+ text: "",
198
+ attrs: b.attrs,
199
+ children: collectRawBlocks(b.children),
200
+ trivia: b.trivia,
201
+ anchors: [],
202
+ outLinks: [],
203
+ blockId: b.blockId, // thread the assigned id so extractEdges can set src_block
204
+ }));
205
+ }
206
+ function toFlatSource(b) {
207
+ return { type: b.type, raw: b.raw, children: b.children.map(toFlatSource) };
208
+ }
209
+ //# sourceMappingURL=reconciling-ingest.js.map
@@ -0,0 +1,6 @@
1
+ import type { Store } from "../core/store/store.js";
2
+ export interface RecoveryResult {
3
+ healed: string[];
4
+ missing: string[];
5
+ }
6
+ export declare function recoverRepo(store: Store, repoId: string, rootPath: string): RecoveryResult;
@@ -0,0 +1,34 @@
1
+ import { readFileSync, existsSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { sha256 } from "../core/hash.js";
4
+ import { ingestFile } from "../core/ingest.js";
5
+ import { makeReconcilingResolver } from "./reconciling-ingest.js";
6
+ export function recoverRepo(store, repoId, rootPath) {
7
+ const docs = store.db
8
+ .prepare(`SELECT d.doc_id, d.path, d.file_hash, r.rendered_hash
9
+ FROM docs d LEFT JOIN revisions r ON r.rev_id = d.current_rev
10
+ WHERE d.repo_id = ? AND d.deleted_commit IS NULL`)
11
+ .all(repoId);
12
+ const healed = [];
13
+ const missing = [];
14
+ const ts = new Date().toISOString();
15
+ for (const doc of docs) {
16
+ const abs = join(rootPath, doc.path);
17
+ if (!existsSync(abs)) {
18
+ missing.push(doc.path);
19
+ continue;
20
+ }
21
+ const onDisk = sha256(readFileSync(abs, "utf8"));
22
+ const recorded = doc.rendered_hash ?? doc.file_hash;
23
+ if (!recorded || !onDisk.equals(recorded)) {
24
+ // Divergence: the file moved ahead of (or behind) the store. Re-ingest.
25
+ ingestFile(store, repoId, doc.path, readFileSync(abs, "utf8"), {
26
+ ts,
27
+ resolveIds: makeReconcilingResolver(store, repoId, { ts, path: doc.path }),
28
+ });
29
+ healed.push(doc.path);
30
+ }
31
+ }
32
+ return { healed, missing };
33
+ }
34
+ //# sourceMappingURL=recovery.js.map
@@ -0,0 +1,18 @@
1
+ import type { Store } from "../core/store/store.js";
2
+ export type Settings = Record<string, unknown>;
3
+ /** The workspace default settings blob (the layer repos inherit from). */
4
+ export declare function workspaceSettings(store: Store): Settings;
5
+ /** A single repo's own (override) settings blob — NOT merged with defaults. */
6
+ export declare function repoOwnSettings(store: Store, repoId: string): Settings;
7
+ export declare function writeWorkspaceSettings(store: Store, settings: Settings): void;
8
+ export declare function writeRepoSettings(store: Store, repoId: string, settings: Settings): void;
9
+ /** Deep-merge: `over` wins at the leaf; nested plain objects merge recursively,
10
+ * everything else (scalars, arrays) replaces wholesale. */
11
+ export declare function deepMerge(base: Settings, over: Settings): Settings;
12
+ /**
13
+ * The effective settings a repo sees: workspace defaults with the repo's own
14
+ * settings deep-merged on top. Every settings consumer should read through this
15
+ * so the workspace-default/repo-override rule holds uniformly. Pass repoId=null
16
+ * (e.g. a sourceless workspace-scoped read) to get the defaults alone.
17
+ */
18
+ export declare function resolveSettings(store: Store, repoId: string | null): Settings;
@@ -0,0 +1,58 @@
1
+ function parse(json) {
2
+ if (!json)
3
+ return {};
4
+ try {
5
+ const v = JSON.parse(json);
6
+ return v && typeof v === "object" ? v : {};
7
+ }
8
+ catch {
9
+ return {};
10
+ }
11
+ }
12
+ /** The workspace default settings blob (the layer repos inherit from). */
13
+ export function workspaceSettings(store) {
14
+ const row = store.db.prepare("SELECT settings FROM workspace_settings WHERE id = 0").get();
15
+ return parse(row?.settings);
16
+ }
17
+ /** A single repo's own (override) settings blob — NOT merged with defaults. */
18
+ export function repoOwnSettings(store, repoId) {
19
+ const row = store.db.prepare("SELECT settings FROM repos WHERE repo_id = ?").get(repoId);
20
+ return parse(row?.settings);
21
+ }
22
+ export function writeWorkspaceSettings(store, settings) {
23
+ store.db
24
+ .prepare("INSERT INTO workspace_settings (id, settings) VALUES (0, ?) ON CONFLICT(id) DO UPDATE SET settings = excluded.settings")
25
+ .run(JSON.stringify(settings));
26
+ }
27
+ export function writeRepoSettings(store, repoId, settings) {
28
+ store.db.prepare("UPDATE repos SET settings = ? WHERE repo_id = ?").run(JSON.stringify(settings), repoId);
29
+ }
30
+ /** Deep-merge: `over` wins at the leaf; nested plain objects merge recursively,
31
+ * everything else (scalars, arrays) replaces wholesale. */
32
+ export function deepMerge(base, over) {
33
+ const out = { ...base };
34
+ for (const [k, v] of Object.entries(over)) {
35
+ const b = out[k];
36
+ if (isPlainObject(b) && isPlainObject(v))
37
+ out[k] = deepMerge(b, v);
38
+ else
39
+ out[k] = v;
40
+ }
41
+ return out;
42
+ }
43
+ function isPlainObject(v) {
44
+ return typeof v === "object" && v !== null && !Array.isArray(v);
45
+ }
46
+ /**
47
+ * The effective settings a repo sees: workspace defaults with the repo's own
48
+ * settings deep-merged on top. Every settings consumer should read through this
49
+ * so the workspace-default/repo-override rule holds uniformly. Pass repoId=null
50
+ * (e.g. a sourceless workspace-scoped read) to get the defaults alone.
51
+ */
52
+ export function resolveSettings(store, repoId) {
53
+ const defaults = workspaceSettings(store);
54
+ if (!repoId)
55
+ return defaults;
56
+ return deepMerge(defaults, repoOwnSettings(store, repoId));
57
+ }
58
+ //# sourceMappingURL=settings.js.map
@@ -0,0 +1,37 @@
1
+ import type { Store } from "../core/store/store.js";
2
+ export interface AdapterRow {
3
+ name: string;
4
+ command: string;
5
+ args: string[];
6
+ }
7
+ export interface SourceRow {
8
+ sourceId: string;
9
+ name: string;
10
+ adapter: string;
11
+ config: Record<string, unknown>;
12
+ env: Record<string, string>;
13
+ }
14
+ /** Register (or update) a named adapter → external command. Idempotent. */
15
+ export declare function ensureAdapter(store: Store, name: string, command: string, args?: string[]): void;
16
+ export declare function listAdapters(store: Store): AdapterRow[];
17
+ /** Create a named source over an adapter. Throws if `name` is taken or the
18
+ * adapter does not exist (FK enforced). Returns the minted source id. */
19
+ export declare function createSource(store: Store, spec: {
20
+ name: string;
21
+ adapter: string;
22
+ config?: Record<string, unknown>;
23
+ env?: Record<string, string>;
24
+ }): string;
25
+ export declare function deleteSource(store: Store, sourceId: string): void;
26
+ export declare function listSources(store: Store): SourceRow[];
27
+ export declare function getSourceByName(store: Store, name: string): SourceRow | null;
28
+ /** Attach a source to a repo (m:n). Idempotent. */
29
+ export declare function attachSourceToRepo(store: Store, repoId: string, sourceId: string): void;
30
+ export declare function detachSourceFromRepo(store: Store, repoId: string, sourceId: string): void;
31
+ /** The sources attached to a repo (ordered by source name). */
32
+ export declare function sourcesForRepo(store: Store, repoId: string): SourceRow[];
33
+ /** Render a source's structured config into adapter argv flags: each own key
34
+ * becomes `--key value` (booleans render as a bare `--key` when true, omitted
35
+ * when false). The declarative config→argv mapping reserved in sync-plugins
36
+ * §3.1; the fs adapter consumes `{ root }` → `--root <root>`. */
37
+ export declare function renderConfigFlags(config: Record<string, unknown>): string[];
@@ -0,0 +1,75 @@
1
+ import { mintId } from "../core/ids.js";
2
+ /** Register (or update) a named adapter → external command. Idempotent. */
3
+ export function ensureAdapter(store, name, command, args = []) {
4
+ store.db
5
+ .prepare(`INSERT INTO adapters (name, command, args) VALUES (?, ?, ?)
6
+ ON CONFLICT(name) DO UPDATE SET command = excluded.command, args = excluded.args`)
7
+ .run(name, command, JSON.stringify(args));
8
+ }
9
+ export function listAdapters(store) {
10
+ return store.db.prepare("SELECT name, command, args FROM adapters ORDER BY name").all().map((r) => ({ name: r.name, command: r.command, args: JSON.parse(r.args) }));
11
+ }
12
+ /** Create a named source over an adapter. Throws if `name` is taken or the
13
+ * adapter does not exist (FK enforced). Returns the minted source id. */
14
+ export function createSource(store, spec) {
15
+ const sourceId = mintId("src");
16
+ store.db
17
+ .prepare("INSERT INTO sources (source_id, name, adapter, config, env) VALUES (?, ?, ?, ?, ?)")
18
+ .run(sourceId, spec.name, spec.adapter, JSON.stringify(spec.config ?? {}), JSON.stringify(spec.env ?? {}));
19
+ return sourceId;
20
+ }
21
+ export function deleteSource(store, sourceId) {
22
+ store.db.prepare("DELETE FROM attachments WHERE source_id = ?").run(sourceId);
23
+ store.db.prepare("DELETE FROM sync_state WHERE source_id = ?").run(sourceId);
24
+ store.db.prepare("DELETE FROM sources WHERE source_id = ?").run(sourceId);
25
+ }
26
+ function rowToSource(r) {
27
+ return {
28
+ sourceId: r.source_id,
29
+ name: r.name,
30
+ adapter: r.adapter,
31
+ config: JSON.parse(r.config),
32
+ env: JSON.parse(r.env),
33
+ };
34
+ }
35
+ export function listSources(store) {
36
+ return store.db.prepare("SELECT source_id, name, adapter, config, env FROM sources ORDER BY name").all().map(rowToSource);
37
+ }
38
+ export function getSourceByName(store, name) {
39
+ const r = store.db.prepare("SELECT source_id, name, adapter, config, env FROM sources WHERE name = ?").get(name);
40
+ return r ? rowToSource(r) : null;
41
+ }
42
+ /** Attach a source to a repo (m:n). Idempotent. */
43
+ export function attachSourceToRepo(store, repoId, sourceId) {
44
+ store.db.prepare("INSERT OR IGNORE INTO attachments (repo_id, source_id) VALUES (?, ?)").run(repoId, sourceId);
45
+ }
46
+ export function detachSourceFromRepo(store, repoId, sourceId) {
47
+ store.db.prepare("DELETE FROM attachments WHERE repo_id = ? AND source_id = ?").run(repoId, sourceId);
48
+ }
49
+ /** The sources attached to a repo (ordered by source name). */
50
+ export function sourcesForRepo(store, repoId) {
51
+ return store.db
52
+ .prepare(`SELECT s.source_id, s.name, s.adapter, s.config, s.env
53
+ FROM sources s JOIN attachments a ON a.source_id = s.source_id
54
+ WHERE a.repo_id = ? ORDER BY s.name`)
55
+ .all(repoId).map(rowToSource);
56
+ }
57
+ /** Render a source's structured config into adapter argv flags: each own key
58
+ * becomes `--key value` (booleans render as a bare `--key` when true, omitted
59
+ * when false). The declarative config→argv mapping reserved in sync-plugins
60
+ * §3.1; the fs adapter consumes `{ root }` → `--root <root>`. */
61
+ export function renderConfigFlags(config) {
62
+ const out = [];
63
+ for (const [key, value] of Object.entries(config)) {
64
+ if (value === undefined || value === null)
65
+ continue;
66
+ if (typeof value === "boolean") {
67
+ if (value)
68
+ out.push(`--${key}`);
69
+ continue;
70
+ }
71
+ out.push(`--${key}`, String(value));
72
+ }
73
+ return out;
74
+ }
75
+ //# sourceMappingURL=sources.js.map
@@ -0,0 +1,9 @@
1
+ import type { Store } from "../core/store/store.js";
2
+ /**
3
+ * Tombstone a doc the engine observed as gone from its source (deleted on disk /
4
+ * left the scope). Pools the doc's live blocks for later resurrection, then drops
5
+ * FTS rows and tombstones the blocks + the doc with an "observed"-origin commit.
6
+ * Returns the minted commit id. Idempotent-safe: if the doc has no live blocks the
7
+ * UPDATEs simply affect zero rows.
8
+ */
9
+ export declare function tombstoneObservedDeletion(store: Store, repoId: string, docId: string, ts: string): string;
@@ -0,0 +1,55 @@
1
+ import { newCommit } from "../core/store/writers.js";
2
+ import { ftsDeleteDoc } from "../core/store/fts.js";
3
+ // Observed-deletion tombstone (01 §6, 02 §3/§7). Shared by BOTH observed-change
4
+ // paths — the synchronous filesystem fast-path (checkpoint.ts::processCheckpoint,
5
+ // used by the freshness sweep / one-shot / recovery) and the async SyncSource
6
+ // path (driver.ts::reconcileChanges, used by the live watcher). Factored into ONE
7
+ // helper so the two loops cannot drift apart: a file that leaves the source scope
8
+ // (existsSync false / source.fetch → null) MUST tombstone the doc, not linger as a
9
+ // live, queryable, FTS-indexed ghost.
10
+ //
11
+ // This mirrors the DB effects of the API delete path (mutate/docs.ts::docsDelete)
12
+ // — mint a commit, drop FTS rows, tombstone the doc's live blocks + the doc row —
13
+ // with two deliberate differences:
14
+ // 1. origin is "observed", not "api": the engine witnessed the disk change; it
15
+ // did not author it (writers.ts NewCommitInput.origin; ingestFile defaults to
16
+ // "observed"). No actor.
17
+ // 2. the live blocks are snapshotted into the resurrection_pool BEFORE they are
18
+ // tombstoned. An API delete is an intentional, permanent removal, so
19
+ // docsDelete pools nothing. An observed disk deletion is the opposite: a file
20
+ // vanishing is overwhelmingly a move/rename/transient (editor swap, git
21
+ // checkout), which is exactly the delete→recreate case the pool exists for
22
+ // (core/ingest.ts snapshots deleted blocks the same way; makeReconcilingResolver
23
+ // consumes the pool to resurrect ids when the file reappears). Pooling with the
24
+ // standard ~30-day expiry keeps sweepResurrectionPool semantics intact.
25
+ //
26
+ // Convention: like processCheckpoint/reconcileChanges, this runs bare db.prepare
27
+ // statements on store.db (each auto-commits) rather than opening its own
28
+ // store.write transaction, so it composes with either caller's loop. The unlink of
29
+ // the on-disk file that docsDelete performs is intentionally absent here — the file
30
+ // is ALREADY gone (that is what triggered the tombstone).
31
+ /**
32
+ * Tombstone a doc the engine observed as gone from its source (deleted on disk /
33
+ * left the scope). Pools the doc's live blocks for later resurrection, then drops
34
+ * FTS rows and tombstones the blocks + the doc with an "observed"-origin commit.
35
+ * Returns the minted commit id. Idempotent-safe: if the doc has no live blocks the
36
+ * UPDATEs simply affect zero rows.
37
+ */
38
+ export function tombstoneObservedDeletion(store, repoId, docId, ts) {
39
+ const db = store.db;
40
+ const commit = newCommit(db, { repoId, ts, origin: "observed", reason: "observed deletion" });
41
+ // Snapshot the doc's live blocks into the resurrection pool BEFORE tombstoning,
42
+ // mirroring core/ingest.ts's reconciling-delete snapshot (~30-day expiry) so a
43
+ // recreate of the same file can resurrect these block identities.
44
+ const expires = new Date(Date.parse(ts) + 30 * 24 * 3600 * 1000).toISOString();
45
+ db.prepare(`INSERT OR REPLACE INTO resurrection_pool (block_id, repo_id, doc_id, raw_hash, norm_hash, type, deleted_commit, expires_ts)
46
+ SELECT block_id, repo_id, doc_id, raw_hash, norm_hash, type, ?, ?
47
+ FROM blocks WHERE doc_id = ? AND deleted_commit IS NULL`).run(commit.commitId, expires, docId);
48
+ // Drop FTS index rows (external-content: delete with the original text before the
49
+ // blocks change), then tombstone the live blocks and the doc row.
50
+ ftsDeleteDoc(db, docId);
51
+ db.prepare("UPDATE blocks SET deleted_commit = ? WHERE doc_id = ? AND deleted_commit IS NULL").run(commit.commitId, docId);
52
+ db.prepare("UPDATE docs SET deleted_commit = ? WHERE doc_id = ?").run(commit.commitId, docId);
53
+ return commit.commitId;
54
+ }
55
+ //# sourceMappingURL=tombstone.js.map
@@ -0,0 +1,14 @@
1
+ /** True if a live watcher currently holds the lease. */
2
+ export declare function watchLeaseLive(omgbaseDir: string): boolean;
3
+ export declare class WatchLease {
4
+ private held;
5
+ private readonly leasePath;
6
+ private constructor();
7
+ /**
8
+ * Try to take the lease. Returns a held lease, or null if a live watcher
9
+ * already holds it. A lease whose holder pid is dead is stolen.
10
+ */
11
+ static tryAcquire(omgbaseDir: string): WatchLease | null;
12
+ private take;
13
+ release(): void;
14
+ }