@omgbase/core 0.11.1 → 0.19.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 (72) hide show
  1. package/README.md +1 -1
  2. package/dist/src/core/ids.d.ts +4 -0
  3. package/dist/src/core/ids.js +17 -0
  4. package/dist/src/core/ingest.d.ts +39 -3
  5. package/dist/src/core/ingest.js +233 -202
  6. package/dist/src/core/store/schema.d.ts +3 -2
  7. package/dist/src/core/store/schema.js +28 -3
  8. package/dist/src/core/store/size.d.ts +76 -0
  9. package/dist/src/core/store/size.js +223 -0
  10. package/dist/src/core/store/store.d.ts +9 -0
  11. package/dist/src/core/store/store.js +11 -0
  12. package/dist/src/graph/history.js +15 -12
  13. package/dist/src/index.d.ts +12 -5
  14. package/dist/src/index.js +18 -7
  15. package/dist/src/mcp/arguments.d.ts +9 -2
  16. package/dist/src/mcp/arguments.js +15 -2
  17. package/dist/src/mcp/errors.d.ts +1 -1
  18. package/dist/src/mcp/errors.js +3 -0
  19. package/dist/src/mcp/reference.d.ts +1 -1
  20. package/dist/src/mcp/reference.js +27 -6
  21. package/dist/src/mcp/server.d.ts +56 -1
  22. package/dist/src/mcp/server.js +365 -203
  23. package/dist/src/mutate/apply.d.ts +93 -12
  24. package/dist/src/mutate/apply.js +203 -113
  25. package/dist/src/mutate/canonical.d.ts +27 -0
  26. package/dist/src/mutate/canonical.js +138 -0
  27. package/dist/src/mutate/commit.d.ts +150 -0
  28. package/dist/src/mutate/commit.js +349 -0
  29. package/dist/src/mutate/doc-store.d.ts +3 -3
  30. package/dist/src/mutate/doc-store.js +21 -6
  31. package/dist/src/mutate/docs.d.ts +42 -87
  32. package/dist/src/mutate/docs.js +50 -204
  33. package/dist/src/mutate/effects.d.ts +363 -0
  34. package/dist/src/mutate/effects.js +1161 -0
  35. package/dist/src/mutate/materialize.d.ts +49 -0
  36. package/dist/src/mutate/materialize.js +174 -0
  37. package/dist/src/mutate/opset.d.ts +27 -2
  38. package/dist/src/mutate/opset.js +12 -5
  39. package/dist/src/mutate/plan-update.d.ts +1 -46
  40. package/dist/src/mutate/plan-update.js +19 -67
  41. package/dist/src/mutate/plan.d.ts +210 -0
  42. package/dist/src/mutate/plan.js +512 -0
  43. package/dist/src/mutate/select.d.ts +89 -0
  44. package/dist/src/mutate/select.js +879 -0
  45. package/dist/src/mutate/sugar.d.ts +135 -0
  46. package/dist/src/mutate/sugar.js +167 -0
  47. package/dist/src/mutate/tree.d.ts +4 -2
  48. package/dist/src/oqx-js/planner.js +4 -0
  49. package/dist/src/oqx-js/run.d.ts +28 -1
  50. package/dist/src/oqx-js/run.js +85 -22
  51. package/dist/src/spec-versions.d.ts +6 -6
  52. package/dist/src/spec-versions.js +5 -5
  53. package/dist/src/surface-paths.d.ts +17 -26
  54. package/dist/src/surface-paths.js +27 -22
  55. package/dist/src/sync/checkpoint.d.ts +5 -2
  56. package/dist/src/sync/checkpoint.js +10 -6
  57. package/dist/src/sync/freshness.d.ts +10 -7
  58. package/dist/src/sync/freshness.js +15 -17
  59. package/dist/src/sync/observe.d.ts +4 -1
  60. package/dist/src/sync/observe.js +56 -5
  61. package/dist/src/sync/recovery.d.ts +4 -0
  62. package/dist/src/sync/recovery.js +4 -1
  63. package/dist/src/sync/stat-cache.d.ts +7 -0
  64. package/dist/src/sync/stat-cache.js +22 -0
  65. package/dist/src/sync/tombstone.js +23 -19
  66. package/dist/src/sync/watcher.d.ts +2 -0
  67. package/dist/src/sync/watcher.js +4 -1
  68. package/dist/src/sync/writer-lock.d.ts +15 -0
  69. package/dist/src/sync/writer-lock.js +104 -17
  70. package/dist/src/version.d.ts +7 -2
  71. package/dist/src/version.js +5 -2
  72. package/package.json +2 -2
@@ -1,9 +1,10 @@
1
- export declare const SCHEMA_VERSION = 13;
1
+ export declare const SCHEMA_VERSION = 14;
2
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
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
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
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
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
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 PENDING_MATERIALIZATIONS_DDL = "\nCREATE TABLE IF NOT EXISTS pending_materializations (\n repo_id TEXT NOT NULL REFERENCES repos(repo_id),\n path TEXT NOT NULL, -- where the file must end up\n kind TEXT NOT NULL CHECK (kind IN ('write','rename','remove')),\n from_path TEXT, -- rename: the old path\n doc_id TEXT NOT NULL REFERENCES docs(doc_id),\n rev_id TEXT REFERENCES revisions(rev_id), -- write/rename: the revision the bytes render (NULL for remove)\n rendered_hash BLOB NOT NULL, -- the bytes expected at path (remove: the bytes it is safe to unlink)\n commit_id TEXT NOT NULL REFERENCES commits(commit_id),\n PRIMARY KEY (repo_id, path)\n);\n";
8
9
  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";
10
+ 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\nCREATE TABLE IF NOT EXISTS pending_materializations (\n repo_id TEXT NOT NULL REFERENCES repos(repo_id),\n path TEXT NOT NULL, -- where the file must end up\n kind TEXT NOT NULL CHECK (kind IN ('write','rename','remove')),\n from_path TEXT, -- rename: the old path\n doc_id TEXT NOT NULL REFERENCES docs(doc_id),\n rev_id TEXT REFERENCES revisions(rev_id), -- write/rename: the revision the bytes render (NULL for remove)\n rendered_hash BLOB NOT NULL, -- the bytes expected at path (remove: the bytes it is safe to unlink)\n commit_id TEXT NOT NULL REFERENCES commits(commit_id),\n PRIMARY KEY (repo_id, path)\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";
@@ -1,7 +1,7 @@
1
1
  // Full DDL — data-model §3 (durable) + §4 (derived). SQLite dialect.
2
2
  // Kept as one string so migrations and rebuild-index can apply it verbatim.
3
3
  // No dialect-specific SQL leaks above the store module (02 §8).
4
- export const SCHEMA_VERSION = 13;
4
+ export const SCHEMA_VERSION = 14;
5
5
  // file_stats (02 §4; derived, rebuildable by a full re-stat) backs the CLI
6
6
  // freshness sweep (11 §3.3): (mtime_ns, size) cheap-change detection so a
7
7
  // one-shot command re-ingests only files that changed on disk since last ingest.
@@ -136,10 +136,34 @@ CREATE TABLE IF NOT EXISTS doc_embeddings (
136
136
  PRIMARY KEY (doc_id, model)
137
137
  );
138
138
  `;
139
+ // pending_materializations (spec/store §5.7, store 14.0). The recorded intent
140
+ // of the api path's file half: the commit transaction inserts one row per file
141
+ // the request must leave on disk — `write` the revision's bytes at `path`,
142
+ // `rename` the file from `from_path` to `path`, `remove` the file at `path` —
143
+ // and the materializer deletes each row once the file is there (after COMMIT,
144
+ // per file, autocommit). A row that survives a crash is replayed by the next
145
+ // materializer (B', the freshness sweep's step 0, recovery) from
146
+ // `reconstruct at rev_id`; `rendered_hash` names the bytes expected at `path`
147
+ // (for `remove`: the bytes it is safe to unlink). No bytes column: the
148
+ // revision already holds them. One row per path: a later request for the same
149
+ // path supersedes the earlier intent (`INSERT OR REPLACE`).
150
+ export const PENDING_MATERIALIZATIONS_DDL = /* sql */ `
151
+ CREATE TABLE IF NOT EXISTS pending_materializations (
152
+ repo_id TEXT NOT NULL REFERENCES repos(repo_id),
153
+ path TEXT NOT NULL, -- where the file must end up
154
+ kind TEXT NOT NULL CHECK (kind IN ('write','rename','remove')),
155
+ from_path TEXT, -- rename: the old path
156
+ doc_id TEXT NOT NULL REFERENCES docs(doc_id),
157
+ rev_id TEXT REFERENCES revisions(rev_id), -- write/rename: the revision the bytes render (NULL for remove)
158
+ rendered_hash BLOB NOT NULL, -- the bytes expected at path (remove: the bytes it is safe to unlink)
159
+ commit_id TEXT NOT NULL REFERENCES commits(commit_id),
160
+ PRIMARY KEY (repo_id, path)
161
+ );
162
+ `;
139
163
  // Additive migrations keyed by the version they upgrade TO. Each runs inside a
140
164
  // transaction. Only forward, idempotent DDL (CREATE ... IF NOT EXISTS) — no
141
165
  // destructive changes. store.ts applies these in order for an older db.
142
- // Migrations 3, 4, 6, 7, 9, 11 are programmatic — see store.ts.
166
+ // Migrations 3, 4, 6, 7, 9, 11, 13 are programmatic — see store.ts.
143
167
  export const MIGRATIONS = {
144
168
  2: FILE_STATS_DDL,
145
169
  3: "", // handled programmatically in store.ts
@@ -153,6 +177,7 @@ export const MIGRATIONS = {
153
177
  11: "", // ALTER TABLE documents RENAME TO docs, handled programmatically in store.ts
154
178
  12: DOC_EMBEDDINGS_DDL, // doc-grain semantic vectors
155
179
  13: "", // drop repos.root_path (migrate → fs sources), handled programmatically in store.ts
180
+ 14: PENDING_MATERIALIZATIONS_DDL, // the api path's recorded file intent (spec/store §5.7)
156
181
  };
157
182
  export const DDL = /* sql */ `
158
183
  -- ---- durable tables (02 §3) --------------------------------------------------
@@ -304,7 +329,7 @@ CREATE TABLE IF NOT EXISTS resurrection_pool (
304
329
  deleted_commit TEXT NOT NULL,
305
330
  expires_ts TEXT NOT NULL
306
331
  );
307
-
332
+ ${PENDING_MATERIALIZATIONS_DDL}
308
333
  -- ---- derived tables (02 §4; rebuildable) ------------------------------------
309
334
  CREATE TABLE IF NOT EXISTS sections (
310
335
  heading_block TEXT NOT NULL,
@@ -0,0 +1,76 @@
1
+ import type { Store } from "./store.js";
2
+ export declare const SIZE_CATEGORIES: readonly ["content", "history", "search", "sync"];
3
+ export type SizeCategory = (typeof SIZE_CATEGORIES)[number];
4
+ /** spec/store §7: the category of every table `schema.sql` creates. */
5
+ export declare const TABLE_CATEGORY: Readonly<Record<string, SizeCategory>>;
6
+ /** The category of a table: the spec's assignment, `sync` for anything else. */
7
+ export declare function categoryOf(table: string): SizeCategory;
8
+ export interface SizeTable {
9
+ name: string;
10
+ /** Pages of the table and its indexes (and shadow tables), in bytes; null without dbstat. */
11
+ bytes: number | null;
12
+ /** `bytes` over the total, one decimal; null without dbstat. */
13
+ share: number | null;
14
+ rows: number;
15
+ /** The bytes of the table's indexes alone (included in `bytes`); null without dbstat. */
16
+ indexes: number | null;
17
+ }
18
+ export interface SizeCategoryReport {
19
+ name: SizeCategory;
20
+ bytes: number | null;
21
+ share: number | null;
22
+ rows: number;
23
+ /** By bytes desc then name (rows desc then name without dbstat). */
24
+ tables: SizeTable[];
25
+ }
26
+ export interface SizeFile {
27
+ path: string;
28
+ /** The main file's bytes on disk (`page_count * page_size` for an in-memory database). */
29
+ bytes: number;
30
+ /** The `-wal` file's bytes on disk, 0 when absent. */
31
+ wal_bytes: number;
32
+ page_size: number;
33
+ page_count: number;
34
+ freelist_count: number;
35
+ /** `freelist_count * page_size`: what `VACUUM` returns to the filesystem. */
36
+ reclaimable: number;
37
+ }
38
+ export interface SizeReport {
39
+ file: SizeFile;
40
+ /** Always the four categories, in the fixed order content, history, search, sync. */
41
+ categories: SizeCategoryReport[];
42
+ advice: string[];
43
+ /** Present only when the SQLite build lacks `dbstat` (row counts only). */
44
+ detail?: "dbstat unavailable";
45
+ }
46
+ /** The advice thresholds (spec/store §7). */
47
+ export declare const VACUUM_ADVICE_MIN_BYTES: number;
48
+ export declare const VACUUM_ADVICE_MIN_SHARE = 10;
49
+ export declare const HISTORY_ADVICE_MIN_SHARE = 50;
50
+ export declare const EMBEDDINGS_ADVICE_MIN_SHARE = 25;
51
+ /**
52
+ * `<n> B` under 1024, else one decimal of KB/MB/GB/TB (1024-based), the tenths
53
+ * rounded half up — `4096` → `4.0 KB`, `13734297` → `13.1 MB`. Both engines
54
+ * format from the integer tenths so the bytes never differ by a rounding mode.
55
+ */
56
+ export declare function humanBytes(n: number): string;
57
+ /** A share as `<n>.<d>%` from its one-decimal value (`48.2` → `48.2%`, `50` → `50.0%`). */
58
+ export declare function formatShare(share: number): string;
59
+ /** `bytes / total` in percent, one decimal, rounded half up; 0 when the total is 0. */
60
+ export declare function shareOf(bytes: number, total: number): number;
61
+ /** spec/store §7: the advice lines, in this order; `nothing to reclaim` when none applies. */
62
+ export declare function sizeAdvice(file: SizeFile, categories: SizeCategoryReport[]): string[];
63
+ /** The size report of an open store (spec/store §7 "Size"). */
64
+ export declare function sizeReport(store: Store): SizeReport;
65
+ export interface VacuumResult {
66
+ /** `freelist_count * page_size` before the `VACUUM`: the bytes it returned. */
67
+ reclaimable: number;
68
+ }
69
+ /**
70
+ * `VACUUM` the database (spec/store §7). Not a transaction's work — SQLite
71
+ * refuses a VACUUM inside one — so it runs on the bare connection; returns the
72
+ * free-page bytes measured before, which is what the file shrinks by.
73
+ */
74
+ export declare function vacuum(store: Store): VacuumResult;
75
+ /** `freelist_count * page_size`: the bytes a `VACUUM` would return now. */
76
+ export declare function reclaimableBytes(store: Store): number;
@@ -0,0 +1,223 @@
1
+ import { statSync } from "node:fs";
2
+ // The size report (spec/store §7 "Size", 14.1): what the database is made of,
3
+ // table by table through SQLite's `dbstat` virtual table, grouped into the four
4
+ // categories a reader reasons about — the live state (`content`), the
5
+ // append-only record (`history`), the derived indexes (`search`) and the
6
+ // workspace bookkeeping (`sync`) — plus the file's page accounting and the
7
+ // deterministic advice `omg doctor --size` prints. The categorization is the
8
+ // spec's: every table of `schema.sql` is assigned, an index counts toward its
9
+ // table, an FTS5 virtual table's shadow tables fold into it, and anything the
10
+ // spec does not name (`sqlite_schema`, `sqlite_stat1`, a table from a newer
11
+ // schema) is `sync`.
12
+ export const SIZE_CATEGORIES = ["content", "history", "search", "sync"];
13
+ /** spec/store §7: the category of every table `schema.sql` creates. */
14
+ export const TABLE_CATEGORY = {
15
+ docs: "content",
16
+ blocks: "content",
17
+ sections: "content",
18
+ nodes: "content",
19
+ edges: "content",
20
+ doc_edges: "content",
21
+ inferred_edges: "content",
22
+ external_nodes: "content",
23
+ properties: "content",
24
+ collections: "content",
25
+ blobs: "history",
26
+ tree_nodes: "history",
27
+ revisions: "history",
28
+ commits: "history",
29
+ dispositions: "history",
30
+ block_changes: "history",
31
+ resurrection_pool: "history",
32
+ pending_materializations: "history",
33
+ blocks_fts: "search",
34
+ nodes_fts: "search",
35
+ embeddings: "search",
36
+ doc_embeddings: "search",
37
+ repos: "sync",
38
+ sources: "sync",
39
+ adapters: "sync",
40
+ attachments: "sync",
41
+ checkpoints: "sync",
42
+ file_stats: "sync",
43
+ sync_state: "sync",
44
+ workspace_settings: "sync",
45
+ };
46
+ /** The category of a table: the spec's assignment, `sync` for anything else. */
47
+ export function categoryOf(table) {
48
+ return TABLE_CATEGORY[table] ?? "sync";
49
+ }
50
+ /** The advice thresholds (spec/store §7). */
51
+ export const VACUUM_ADVICE_MIN_BYTES = 1024 * 1024;
52
+ export const VACUUM_ADVICE_MIN_SHARE = 10;
53
+ export const HISTORY_ADVICE_MIN_SHARE = 50;
54
+ export const EMBEDDINGS_ADVICE_MIN_SHARE = 25;
55
+ /**
56
+ * `<n> B` under 1024, else one decimal of KB/MB/GB/TB (1024-based), the tenths
57
+ * rounded half up — `4096` → `4.0 KB`, `13734297` → `13.1 MB`. Both engines
58
+ * format from the integer tenths so the bytes never differ by a rounding mode.
59
+ */
60
+ export function humanBytes(n) {
61
+ if (n < 1024)
62
+ return `${n} B`;
63
+ const units = ["KB", "MB", "GB", "TB"];
64
+ let v = n / 1024;
65
+ let u = 0;
66
+ while (v >= 1024 && u < units.length - 1) {
67
+ v /= 1024;
68
+ u++;
69
+ }
70
+ const tenths = Math.round(v * 10);
71
+ return `${Math.floor(tenths / 10)}.${tenths % 10} ${units[u]}`;
72
+ }
73
+ /** A share as `<n>.<d>%` from its one-decimal value (`48.2` → `48.2%`, `50` → `50.0%`). */
74
+ export function formatShare(share) {
75
+ const tenths = Math.round(share * 10);
76
+ return `${Math.floor(tenths / 10)}.${tenths % 10}%`;
77
+ }
78
+ /** `bytes / total` in percent, one decimal, rounded half up; 0 when the total is 0. */
79
+ export function shareOf(bytes, total) {
80
+ if (total <= 0)
81
+ return 0;
82
+ return Math.round((bytes * 1000) / total) / 10;
83
+ }
84
+ /** spec/store §7: the advice lines, in this order; `nothing to reclaim` when none applies. */
85
+ export function sizeAdvice(file, categories) {
86
+ const advice = [];
87
+ const logical = file.page_count * file.page_size;
88
+ if (file.reclaimable > VACUUM_ADVICE_MIN_BYTES && file.reclaimable * 100 > logical * VACUUM_ADVICE_MIN_SHARE) {
89
+ advice.push(`run \`omg gc --vacuum\` to return ${humanBytes(file.reclaimable)} of free pages to the filesystem`);
90
+ }
91
+ const history = categories.find((c) => c.name === "history");
92
+ if (history && history.share !== null && history.share > HISTORY_ADVICE_MIN_SHARE) {
93
+ advice.push(`history dominates (${formatShare(history.share)}): blobs, tree nodes and dispositions grow with every commit — enable gc (\`omg config set gc.enabled true\`) and run \`omg gc --vacuum\``);
94
+ }
95
+ const search = categories.find((c) => c.name === "search");
96
+ if (search && search.bytes !== null) {
97
+ const total = categories.reduce((n, c) => n + (c.bytes ?? 0), 0);
98
+ const embBytes = search.tables.filter((t) => t.name === "embeddings" || t.name === "doc_embeddings").reduce((n, t) => n + (t.bytes ?? 0), 0);
99
+ const embShare = shareOf(embBytes, total);
100
+ if (embShare > EMBEDDINGS_ADVICE_MIN_SHARE) {
101
+ advice.push(`embeddings are ${formatShare(embShare)} of the database: a smaller model (\`embedding.model\` / \`embedding.dim\`) or \`omg embed drain --prune\` after a model switch shrinks them`);
102
+ }
103
+ }
104
+ if (advice.length === 0)
105
+ advice.push("nothing to reclaim");
106
+ return advice;
107
+ }
108
+ /** The `dbstat` bytes per b-tree name, or null when the build lacks the virtual table. */
109
+ function dbstatBytes(store) {
110
+ try {
111
+ const rows = store.db.prepare("SELECT name, sum(pgsize) AS bytes FROM dbstat GROUP BY name").all();
112
+ return new Map(rows.map((r) => [r.name, Number(r.bytes)]));
113
+ }
114
+ catch {
115
+ return null;
116
+ }
117
+ }
118
+ /** The size report of an open store (spec/store §7 "Size"). */
119
+ export function sizeReport(store) {
120
+ const db = store.db;
121
+ const path = db.name === "" ? ":memory:" : db.name;
122
+ const page_size = db.pragma("page_size", { simple: true });
123
+ const page_count = db.pragma("page_count", { simple: true });
124
+ const freelist_count = db.pragma("freelist_count", { simple: true });
125
+ const onDisk = path !== ":memory:";
126
+ const fileBytes = (p) => {
127
+ try {
128
+ return statSync(p).size;
129
+ }
130
+ catch {
131
+ return 0;
132
+ }
133
+ };
134
+ const file = {
135
+ path,
136
+ bytes: onDisk ? fileBytes(path) : page_count * page_size,
137
+ wal_bytes: onDisk ? fileBytes(`${path}-wal`) : 0,
138
+ page_size,
139
+ page_count,
140
+ freelist_count,
141
+ reclaimable: freelist_count * page_size,
142
+ };
143
+ const master = db.prepare("SELECT type, name, tbl_name, sql FROM sqlite_master").all();
144
+ const virtuals = master.filter((r) => r.type === "table" && /^CREATE VIRTUAL TABLE/i.test(r.sql ?? "")).map((r) => r.name);
145
+ // A shadow table (`blocks_fts_data`, …) folds into its virtual table.
146
+ const ownerOf = (name) => virtuals.find((v) => name.startsWith(`${v}_`)) ?? name;
147
+ const stat = dbstatBytes(store);
148
+ // Every table: sqlite_master's (shadow tables folded into their owner), plus
149
+ // the schema table itself, which sqlite_master does not list but dbstat does.
150
+ const tableNames = new Set(["sqlite_schema"]);
151
+ for (const r of master)
152
+ if (r.type === "table")
153
+ tableNames.add(ownerOf(r.name));
154
+ if (stat)
155
+ for (const name of stat.keys())
156
+ if (!master.some((r) => r.name === name && r.type === "index"))
157
+ tableNames.add(ownerOf(name));
158
+ const indexBytes = new Map();
159
+ const tableBytes = new Map();
160
+ if (stat) {
161
+ for (const [name, bytes] of stat) {
162
+ const idx = master.find((r) => r.type === "index" && r.name === name);
163
+ if (idx) {
164
+ indexBytes.set(idx.tbl_name, (indexBytes.get(idx.tbl_name) ?? 0) + bytes);
165
+ tableBytes.set(idx.tbl_name, (tableBytes.get(idx.tbl_name) ?? 0) + bytes);
166
+ }
167
+ else {
168
+ const owner = ownerOf(name);
169
+ tableBytes.set(owner, (tableBytes.get(owner) ?? 0) + bytes);
170
+ }
171
+ }
172
+ }
173
+ const total = stat ? [...stat.values()].reduce((n, b) => n + b, 0) : 0;
174
+ const countRows = (name) => {
175
+ // An external-content FTS5 table answers `count(*)` from its content table;
176
+ // the rows it indexes are its `_docsize` shadow's.
177
+ const from = virtuals.includes(name) ? `${name}_docsize` : name;
178
+ try {
179
+ return db.prepare(`SELECT count(*) AS c FROM "${from.replace(/"/g, '""')}"`).get().c;
180
+ }
181
+ catch {
182
+ return 0;
183
+ }
184
+ };
185
+ const byCategory = new Map(SIZE_CATEGORIES.map((c) => [c, []]));
186
+ for (const name of tableNames) {
187
+ const bytes = stat ? (tableBytes.get(name) ?? 0) : null;
188
+ byCategory.get(categoryOf(name)).push({
189
+ name,
190
+ bytes,
191
+ share: bytes === null ? null : shareOf(bytes, total),
192
+ rows: countRows(name),
193
+ indexes: stat ? (indexBytes.get(name) ?? 0) : null,
194
+ });
195
+ }
196
+ const byBytesThenName = (a, b) => (b.bytes ?? b.rows) - (a.bytes ?? a.rows) || (a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
197
+ const categories = SIZE_CATEGORIES.map((name) => {
198
+ const tables = byCategory.get(name).sort(byBytesThenName);
199
+ const bytes = stat ? tables.reduce((n, t) => n + (t.bytes ?? 0), 0) : null;
200
+ return { name, bytes, share: bytes === null ? null : shareOf(bytes, total), rows: tables.reduce((n, t) => n + t.rows, 0), tables };
201
+ });
202
+ const report = { file, categories, advice: sizeAdvice(file, categories) };
203
+ if (!stat)
204
+ report.detail = "dbstat unavailable";
205
+ return report;
206
+ }
207
+ /**
208
+ * `VACUUM` the database (spec/store §7). Not a transaction's work — SQLite
209
+ * refuses a VACUUM inside one — so it runs on the bare connection; returns the
210
+ * free-page bytes measured before, which is what the file shrinks by.
211
+ */
212
+ export function vacuum(store) {
213
+ const reclaimable = reclaimableBytes(store);
214
+ store.db.exec("VACUUM");
215
+ return { reclaimable };
216
+ }
217
+ /** `freelist_count * page_size`: the bytes a `VACUUM` would return now. */
218
+ export function reclaimableBytes(store) {
219
+ const page_size = store.db.pragma("page_size", { simple: true });
220
+ const freelist_count = store.db.pragma("freelist_count", { simple: true });
221
+ return freelist_count * page_size;
222
+ }
223
+ //# sourceMappingURL=size.js.map
@@ -23,6 +23,15 @@ export declare class Store {
23
23
  * commit). better-sqlite3 transactions are synchronous and serialized.
24
24
  */
25
25
  write<T>(fn: (db: Database.Database) => T): T;
26
+ /**
27
+ * Run fn inside a `BEGIN IMMEDIATE` transaction: the write lock is taken at
28
+ * BEGIN, so every read fn makes is already protected from another writer and
29
+ * a concurrent `BEGIN IMMEDIATE` waits (SQLITE_BUSY after the busy timeout).
30
+ * The commit phase of a request uses it (spec/mutate §4 step 5(c), 2.2) so
31
+ * the preconditions it re-validates cannot move before it writes. Nested
32
+ * inside an open transaction it is a savepoint, like `write`.
33
+ */
34
+ writeImmediate<T>(fn: (db: Database.Database) => T): T;
26
35
  pragma(name: string): unknown;
27
36
  close(): void;
28
37
  }
@@ -191,6 +191,17 @@ export class Store {
191
191
  write(fn) {
192
192
  return this.db.transaction(fn)(this.db);
193
193
  }
194
+ /**
195
+ * Run fn inside a `BEGIN IMMEDIATE` transaction: the write lock is taken at
196
+ * BEGIN, so every read fn makes is already protected from another writer and
197
+ * a concurrent `BEGIN IMMEDIATE` waits (SQLITE_BUSY after the busy timeout).
198
+ * The commit phase of a request uses it (spec/mutate §4 step 5(c), 2.2) so
199
+ * the preconditions it re-validates cannot move before it writes. Nested
200
+ * inside an open transaction it is a savepoint, like `write`.
201
+ */
202
+ writeImmediate(fn) {
203
+ return this.db.transaction(fn).immediate(this.db);
204
+ }
194
205
  pragma(name) {
195
206
  return this.db.pragma(name, { simple: true });
196
207
  }
@@ -278,12 +278,15 @@ export function changesSince(store, repoId, opts = {}) {
278
278
  const digests = page.map((c) => {
279
279
  const revs = store.db.prepare("SELECT r.doc_id AS doc, r.path AS path, r.rendered_hash AS rendered_hash FROM revisions r WHERE r.commit_id = ?").all(c.commit_id).map((r) => ({ id: r.doc, path: referencePath(r.path), hash: r.rendered_hash.toString("hex") }));
280
280
  const dispCounts = store.db.prepare("SELECT kind, count(*) n FROM dispositions WHERE commit_id = ? GROUP BY kind").all(c.commit_id);
281
- // A deletion or a move writes no revision: the tombstone names the commit
282
- // (`docs.deleted_commit`), a move only its reason (`move <from> -> <to>`).
281
+ // A deletion writes no revision: the tombstone names the commit
282
+ // (`docs.deleted_commit`). A move names itself by its reason (`move <from>
283
+ // -> <to>`) and, since spec/mutate 2.1, carries the inbound retarget's
284
+ // revisions under the same commit — so the move is read off the reason
285
+ // whether or not the commit has revisions.
283
286
  const deleted = revs.length === 0
284
287
  ? store.db.prepare("SELECT path FROM docs WHERE deleted_commit = ? ORDER BY path").all(c.commit_id).map((r) => referencePath(r.path))
285
288
  : [];
286
- const moved = revs.length === 0 ? parseMove(c.reason) : null;
289
+ const moved = parseMove(c.reason);
287
290
  const summary = renderSummary(c.origin, c.actor, revs, dispCounts, { deleted, moved: moved ? { from: referencePath(moved.from), to: referencePath(moved.to) } : null });
288
291
  return { commit: c.commit_id, seq: c.seq, ts: c.ts, origin: c.origin, actor: c.actor, summary, revisions: revs };
289
292
  });
@@ -387,18 +390,18 @@ function parseMove(reason) {
387
390
  }
388
391
  /**
389
392
  * One line per commit: `<origin>[(<actor>)]: <paths> — <n> <kind>, …`. A
390
- * commit that touched no revision — a document deletion (observed or api) or a
391
- * move — says so instead: `deleted <path>` / `moved <from> → <to>`
392
- * (spec/cli §6 `log`; the summary is unpinned by spec/surface).
393
+ * commit that touched no revision — a document deletion (observed or api) —
394
+ * says so instead: `deleted <path>`. A move says `moved <from> → <to>`, and
395
+ * when it also rewrote the inbound sources (spec/mutate §6, 2.1) their paths
396
+ * follow: `moved a → b; /index.md — 31 edited` (spec/cli §6 `log`; the summary
397
+ * is unpinned by spec/surface).
393
398
  */
394
399
  function renderSummary(origin, actor, revs, disp, extra = { deleted: [], moved: null }) {
395
400
  let subject = revs.map((r) => r.path).join(", ");
396
- if (revs.length === 0) {
397
- if (extra.deleted.length > 0)
398
- subject = `deleted ${extra.deleted.join(", ")}`;
399
- else if (extra.moved)
400
- subject = `moved ${extra.moved.from} → ${extra.moved.to}`;
401
- }
401
+ if (extra.moved)
402
+ subject = `moved ${extra.moved.from} → ${extra.moved.to}${revs.length > 0 ? `; ${subject}` : ""}`;
403
+ else if (revs.length === 0 && extra.deleted.length > 0)
404
+ subject = `deleted ${extra.deleted.join(", ")}`;
402
405
  const parts = disp.filter((d) => d.kind !== "same").map((d) => `${d.n} ${d.kind}`);
403
406
  const detail = parts.length > 0 ? ` — ${parts.join(", ")}` : "";
404
407
  if (origin === "api" || origin === "import")
@@ -16,7 +16,7 @@ export { Workspace, RepoSelectionError, selectRepo, type RepoRow } from "./sync/
16
16
  export { resolveSettings, workspaceSettings, repoOwnSettings, writeWorkspaceSettings, writeRepoSettings, deepMerge, type Settings, } from "./sync/settings.js";
17
17
  export { freshnessSweep, rebuildFileStats, recordFileStat, recordStat, detectDiskDrift, sweepPlan, sweepCandidates, loadStatCache, snapshotOf, type SweepResult, type DiskDrift, type SweepPlan, type StatCacheRow, type SnapshotEntry, } from "./sync/freshness.js";
18
18
  export { recoverRepo, type RecoveryResult } from "./sync/recovery.js";
19
- export { withWriterLock, writerLockFree, WriterLockTimeout } from "./sync/writer-lock.js";
19
+ export { withWriterLock, withWriterLockAsync, writerLockFree, writerLockHeld, setWriterLockDefaults, writerLockDefaults, WriterLockTimeout, type WriterLockOptions } from "./sync/writer-lock.js";
20
20
  export { WatchLease, watchLeaseLive } from "./sync/watch-lease.js";
21
21
  export { ingestDirectory, type IngestDirResult } from "./sync/attach.js";
22
22
  export { ensureRepo } from "./core/attach.js";
@@ -28,7 +28,7 @@ export { createExternalSource, type ExternalSourceSpec } from "./sync/external-s
28
28
  export { ensureAdapter, listAdapters, createSource, deleteSource, listSources, getSourceByName, attachSourceToRepo, detachSourceFromRepo, sourcesForRepo, renderConfigFlags, type AdapterRow, type SourceRow, } from "./sync/sources.js";
29
29
  export { reconcileChanges, attachSource } from "./sync/driver.js";
30
30
  export { observeFile, observeMany, observeOne, observeDelete, observeBatch, type ObserveResult, type ObserveOneResult, type ObserveDeleteResult, type BatchItem, type BatchOutcome } from "./sync/observe.js";
31
- export { buildServer, type ServerContext } from "./mcp/server.js";
31
+ export { buildServer, toolDescriptions, toolNames, unknownTool, UNKNOWN_TOOL_CODE, WRITE_TOOLS, MINIMAL_TOOLS, PROFILES, TOOLS_PROFILES, parseToolsProfile, type ServerContext, type ToolsProfile } from "./mcp/server.js";
32
32
  export { serveStdio, type ServeStdioHandle } from "./mcp/stdio.js";
33
33
  export { processCheckpoint, type CheckpointResult } from "./sync/checkpoint.js";
34
34
  export { reposStatus, syncStatus, reposList, type RepoStatus, type SyncStatus, type DiskStatus, type ReposList } from "./sync/admin.js";
@@ -60,12 +60,19 @@ export { sha256 } from "./core/hash.js";
60
60
  export { ingestFile, type IngestResult } from "./core/ingest.js";
61
61
  export { apply, type ApplyRequest, type ApplyResult, type Op } from "./mutate/apply.js";
62
62
  export { type DocStore, FsDocStore, NullDocStore, resolveDocStore } from "./mutate/doc-store.js";
63
+ export { materializePending, materializeRow, listPending, readPending, type PendingRow, type PendingKind } from "./mutate/materialize.js";
63
64
  export { type To, type At, type Expect } from "./mutate/ops.js";
64
65
  export { tasksComplete, sectionsAppend, sectionsRename, sectionsMove, listsInsertItem, linksRetarget, nodeSet, editablePropsFor, type RetargetHit, } from "./mutate/macros.js";
65
- export { docsCreate, docsMove, docsDelete, docsSetMeta, type DocOpContext, type DocOpResult, type DocMoveOptions, type DocMoveResult, } from "./mutate/docs.js";
66
+ export { canonicalDocPath, composeFile, splitFrontmatter, planInboundRetarget } from "./mutate/docs.js";
67
+ export { createEffects, moveDocEffects, deleteDocEffects, setMetaEffects, updateContentEffects, insertEffects, updateBlockEffects, moveBlocksEffects, removeBlocksEffects, splitEffects, mergeEffects, checkEffects, nodeSetEffects, appendEffects, docsAppendEffects, retargetEffects, guardOf, requireAbsentPath, applySugar, createdDoc, docOutcome, moveLinks, type Sugar, type SugarDoc, type SugarRequest, type DocOpOutcome, type MoveLinks, } from "./mutate/sugar.js";
68
+ export { planEffects, executePlan, executeByDigest, applyEffects, applyEffectsDetailed, substituteSymbols, admitPlan, canonicalJson, planDigest, PLAN_VERSION, PLANNER_V, type Plan, type PlanCore, type PlanOpset, type PlanPreconditions, type PlanPreview, type Receipt, type PlanEffectsRequest, type ExecuteRequest, } from "./mutate/plan.js";
69
+ export { validateEffects, EFFECT_OPS, type Effect, type EffectTarget, type EffectExpect, type ResolvedEffect, type CreateEntry, type RetargetPreview } from "./mutate/effects.js";
70
+ export { compileSelection, selectionQuery, SELECTION_CEILING, type Selection, type SelectionExpect, type CompiledSelection } from "./mutate/select.js";
71
+ export { oqxRunQuery, reservedAlias } from "./oqx-js/run.js";
66
72
  export { inboundLinksTo, retargetLinksInRaw, type InboundLink } from "./graph/inbound-links.js";
67
- export { planUpdate, applyOpset, docsUpdate, type PlanUpdateOptions, type ApplyOpsetRequest, type DocsUpdateContext, type DocsUpdateResult, } from "./mutate/plan-update.js";
68
- export { opsetKernelOps, renderOpsetPlan, serializeOpset, parseOpset, type Opset, type PlanOp, type OpsetSummary, type OpsetPrecondition, type PlanDisposition, } from "./mutate/opset.js";
73
+ export { planUpdate, type PlanUpdateOptions } from "./mutate/plan-update.js";
74
+ export { opsetKernelOps, renderOpsetPlan, renderIdentityPlan, type IdentityPreview, serializeOpset, parseOpset, type Opset, type PlanOp, type OpsetSummary, type OpsetPrecondition, type PlanDisposition, } from "./mutate/opset.js";
69
75
  export { rebuildIndex, type RebuildTarget } from "./core/store/rebuild.js";
70
76
  export { runGc, type GcResult } from "./core/store/gc.js";
77
+ export { sizeReport, sizeAdvice, vacuum, reclaimableBytes, humanBytes, formatShare, shareOf, categoryOf, TABLE_CATEGORY, SIZE_CATEGORIES, type SizeReport, type SizeCategory, type SizeCategoryReport, type SizeTable, type SizeFile, type VacuumResult, } from "./core/store/size.js";
71
78
  export { LIVE_LEAF_SQL, ftsIndexedRowCount, liveLeafCount } from "./core/store/fts.js";
package/dist/src/index.js CHANGED
@@ -17,7 +17,7 @@ export { Workspace, RepoSelectionError, selectRepo } from "./sync/workspace.js";
17
17
  export { resolveSettings, workspaceSettings, repoOwnSettings, writeWorkspaceSettings, writeRepoSettings, deepMerge, } from "./sync/settings.js";
18
18
  export { freshnessSweep, rebuildFileStats, recordFileStat, recordStat, detectDiskDrift, sweepPlan, sweepCandidates, loadStatCache, snapshotOf, } from "./sync/freshness.js";
19
19
  export { recoverRepo } from "./sync/recovery.js";
20
- export { withWriterLock, writerLockFree, WriterLockTimeout } from "./sync/writer-lock.js";
20
+ export { withWriterLock, withWriterLockAsync, writerLockFree, writerLockHeld, setWriterLockDefaults, writerLockDefaults, WriterLockTimeout } from "./sync/writer-lock.js";
21
21
  export { WatchLease, watchLeaseLive } from "./sync/watch-lease.js";
22
22
  export { ingestDirectory } from "./sync/attach.js";
23
23
  export { ensureRepo } from "./core/attach.js";
@@ -28,7 +28,7 @@ export { createExternalSource } from "./sync/external-source.js";
28
28
  export { ensureAdapter, listAdapters, createSource, deleteSource, listSources, getSourceByName, attachSourceToRepo, detachSourceFromRepo, sourcesForRepo, renderConfigFlags, } from "./sync/sources.js";
29
29
  export { reconcileChanges, attachSource } from "./sync/driver.js";
30
30
  export { observeFile, observeMany, observeOne, observeDelete, observeBatch } from "./sync/observe.js";
31
- export { buildServer } from "./mcp/server.js";
31
+ export { buildServer, toolDescriptions, toolNames, unknownTool, UNKNOWN_TOOL_CODE, WRITE_TOOLS, MINIMAL_TOOLS, PROFILES, TOOLS_PROFILES, parseToolsProfile } from "./mcp/server.js";
32
32
  export { serveStdio } from "./mcp/stdio.js";
33
33
  export { processCheckpoint } from "./sync/checkpoint.js";
34
34
  export { reposStatus, syncStatus, reposList } from "./sync/admin.js";
@@ -67,16 +67,27 @@ export { ingestFile } from "./core/ingest.js";
67
67
  // Mutation surface consumed by CLI write commands (11 §5.6).
68
68
  export { apply } from "./mutate/apply.js";
69
69
  export { FsDocStore, NullDocStore, resolveDocStore } from "./mutate/doc-store.js";
70
+ export { materializePending, materializeRow, listPending, readPending } from "./mutate/materialize.js";
70
71
  export {} from "./mutate/ops.js";
71
72
  export { tasksComplete, sectionsAppend, sectionsRename, sectionsMove, listsInsertItem, linksRetarget, nodeSet, editablePropsFor, } from "./mutate/macros.js";
72
- // Admin/maintenance (11 §5.9). Graph traversal is OQX `follow` now (see oqx/).
73
- export { docsCreate, docsMove, docsDelete, docsSetMeta, } from "./mutate/docs.js";
73
+ // The document operations' shared pieces (spec/mutate §6); the operations themselves are the effect builders below.
74
+ export { canonicalDocPath, composeFile, splitFrontmatter, planInboundRetarget } from "./mutate/docs.js";
75
+ // The effect builders (spec/mutate §6/§10, 3.0; spec/surface §4 "Sugar over effects", 4.0): one per operation,
76
+ // shared by the MCP server, the `omg` verbs and the fixture runner, plus `applySugar` and the Receipt adapters.
77
+ export { createEffects, moveDocEffects, deleteDocEffects, setMetaEffects, updateContentEffects, insertEffects, updateBlockEffects, moveBlocksEffects, removeBlocksEffects, splitEffects, mergeEffects, checkEffects, nodeSetEffects, appendEffects, docsAppendEffects, retargetEffects, guardOf, requireAbsentPath, applySugar, createdDoc, docOutcome, moveLinks, } from "./mutate/sugar.js";
78
+ // The Effect layer (ADR-021, spec/mutate §11): plan / execute / apply { effects } and the plan's shapes.
79
+ export { planEffects, executePlan, executeByDigest, applyEffects, applyEffectsDetailed, substituteSymbols, admitPlan, canonicalJson, planDigest, PLAN_VERSION, PLANNER_V, } from "./mutate/plan.js";
80
+ export { validateEffects, EFFECT_OPS } from "./mutate/effects.js";
81
+ // Query-directed planning (spec/mutate §11.8, 3.1): the selector binding behind `plan`/`apply`/`execute { select }`.
82
+ export { compileSelection, selectionQuery, SELECTION_CEILING } from "./mutate/select.js";
83
+ export { oqxRunQuery, reservedAlias } from "./oqx-js/run.js";
74
84
  // Inbound-link discovery + destination-aware rewrite (docs_move's dangling report / retarget_inbound).
75
85
  export { inboundLinksTo, retargetLinksInRaw } from "./graph/inbound-links.js";
76
- // Whole-document update: reconcile → opset → inspect/apply.
77
- export { planUpdate, applyOpset, docsUpdate, } from "./mutate/plan-update.js";
78
- export { opsetKernelOps, renderOpsetPlan, serializeOpset, parseOpset, } from "./mutate/opset.js";
86
+ // Whole-document update: reconcile → opset (the `set content` effect's internal planner since 3.0; `preview.identity` surfaces it).
87
+ export { planUpdate } from "./mutate/plan-update.js";
88
+ export { opsetKernelOps, renderOpsetPlan, renderIdentityPlan, serializeOpset, parseOpset, } from "./mutate/opset.js";
79
89
  export { rebuildIndex } from "./core/store/rebuild.js";
80
90
  export { runGc } from "./core/store/gc.js";
91
+ export { sizeReport, sizeAdvice, vacuum, reclaimableBytes, humanBytes, formatShare, shareOf, categoryOf, TABLE_CATEGORY, SIZE_CATEGORIES, } from "./core/store/size.js";
81
92
  export { LIVE_LEAF_SQL, ftsIndexedRowCount, liveLeafCount } from "./core/store/fts.js";
82
93
  //# sourceMappingURL=index.js.map