@owrede/vault-memory 2.2.1 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -14,6 +14,40 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
14
14
 
15
15
  _Nothing yet._
16
16
 
17
+ ## [2.3.0] — 2026-07-01
18
+
19
+ ### Added
20
+
21
+ - **Datacore/Dataview rendered indexing — foundation** (ADR-033). Phase 1 strips
22
+ Datacore/Dataview query source from indexed text so raw query blocks no longer
23
+ pollute search (`src/reader/datacore.ts`). Phase 2 adds migration 016
24
+ (`notes.rendered_source_hash`) as the schema foundation for indexing rendered
25
+ content supplied by the Obsidian plugin. _Note: later phases of ADR-033 are not
26
+ yet complete; this release ships the foundation only._
27
+
28
+ ### Fixed
29
+
30
+ - **Incremental full-vault indexer now re-indexes changed notes** (#14). `indexVault`
31
+ in `incremental` mode decided whether to re-index from chunk count alone, so a note
32
+ whose body changed but already had chunks kept stale chunks/embeddings/sections/edges
33
+ while its `hash`/`content` were updated in place. The reindex decision now mirrors the
34
+ single-note indexer's three-way logic (hash-unchanged / frontmatter-only / body-changed),
35
+ and a latent foreign-key ordering bug (sections must be deleted before chunks) is fixed.
36
+ - **MCP server version is derived from `package.json`** (#14). `server.ts` hardcoded
37
+ `VERSION = "1.0.0"`, so the server advertised the wrong version and sink provisioning
38
+ stamped a stale sentinel. A new `src/version.ts` is the single source of truth.
39
+ - **Frontmatter long-string serialization** (#14). Long string values are written
40
+ single-line instead of being folded into a `>-` YAML block scalar that Obsidian's
41
+ Properties editor mishandles.
42
+
43
+ ### Changed
44
+
45
+ - **`engines.node` narrowed to `>=22 <26`** (#14). Node 26+ has no `better-sqlite3`
46
+ prebuild for the new ABI and building from source currently fails. README prerequisites
47
+ updated accordingly.
48
+ - **Release versions reconciled to 2.3.0** across `package.json`, `plugin/package.json`,
49
+ `plugin/manifest.json`, and README (#14).
50
+
17
51
  ## [2.2.1] — 2026-06-29
18
52
 
19
53
  ### Fixed
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  **Local-first, source-agnostic-ready agentic knowledge layer over your Obsidian notes,
4
4
  exposed to any MCP-aware agent.**
5
5
 
6
- > See [CHANGELOG.md](./CHANGELOG.md) for release history. Latest: **v2.0.0** — additive
6
+ > See [CHANGELOG.md](./CHANGELOG.md) for release history. Latest: **v2.3.0** — additive
7
7
  > over v1.x; the 23 v1 tool names + input schemas are preserved byte-identical.
8
8
 
9
9
  ## 30-second example
@@ -198,7 +198,9 @@ vault-memory supports two engines, selectable **per vault**:
198
198
 
199
199
  ### Prerequisites
200
200
 
201
- - **Node.js >= 22** — runtime for the MCP server (`brew install node@22`).
201
+ - **Node.js 22–25** (`>=22 <26`) — runtime for the MCP server (`brew install node@22`).
202
+ Node 26+ is not yet supported: the native `better-sqlite3` dependency has no
203
+ prebuild for the new ABI and building from source currently fails.
202
204
  - One or more Obsidian vaults; an MCP-aware client.
203
205
  - **Ollama engine only:** [Ollama](https://ollama.com) on `localhost:11434`
204
206
  (`brew install ollama && brew services start ollama`) + the `bge-m3` model
package/dist/cli.js CHANGED
@@ -1697,6 +1697,12 @@ function runMigration015(db, _ctx) {
1697
1697
  "DROP INDEX IF EXISTS sections_note_anchor; CREATE UNIQUE INDEX IF NOT EXISTS sections_note_headingpath_anchor ON sections(note_id, heading_path, anchor);"
1698
1698
  );
1699
1699
  }
1700
+ function runMigration016(db, _ctx) {
1701
+ const cols = db.prepare("PRAGMA table_info(notes)").all();
1702
+ if (!cols.some((c) => c.name === "rendered_source_hash")) {
1703
+ db.exec("ALTER TABLE notes ADD COLUMN rendered_source_hash TEXT");
1704
+ }
1705
+ }
1700
1706
  var INITIAL_SCHEMA, MIGRATION_002_ALIASES, MIGRATION_003_FIX_DELETE_FKS, MIGRATION_004_VARIABLE_DIMS, MIGRATION_006_BODY_HASH, MIGRATION_007_DOC_URI_ADD, MIGRATIONS;
1701
1707
  var init_schema = __esm({
1702
1708
  "src/db/schema.ts"() {
@@ -1977,6 +1983,11 @@ CREATE INDEX IF NOT EXISTS idx_notes_doc_uri ON notes(doc_uri);
1977
1983
  version: 15,
1978
1984
  description: "section identity = (note_id, heading_path, anchor) \u2014 context-aware, no longer collapse byte-identical siblings in different contexts (ADR-032 revised)",
1979
1985
  run: runMigration015
1986
+ },
1987
+ {
1988
+ version: 16,
1989
+ description: "notes.rendered_source_hash \u2014 overlay marker for plugin-rendered Datacore content (ADR-033)",
1990
+ run: runMigration016
1980
1991
  }
1981
1992
  ];
1982
1993
  }
@@ -5892,6 +5903,73 @@ var init_wikilinks2 = __esm({
5892
5903
  }
5893
5904
  });
5894
5905
 
5906
+ // src/reader/datacore.ts
5907
+ function stripDynamicViewBlocks(body) {
5908
+ if (!body.includes("```") && !body.includes("~~~")) {
5909
+ return { content: body, replaced: 0 };
5910
+ }
5911
+ const lines = body.split("\n");
5912
+ const out = [];
5913
+ let replaced = 0;
5914
+ let i = 0;
5915
+ while (i < lines.length) {
5916
+ const line = lines[i];
5917
+ const open2 = FENCE_OPEN_RE.exec(line);
5918
+ if (open2) {
5919
+ const indent = open2[1] ?? "";
5920
+ const marker = open2[2] ?? "";
5921
+ const lang = (open2[3] ?? "").toLowerCase();
5922
+ const markerChar = marker[0];
5923
+ const isDynamic = DYNAMIC_VIEW_LANGS.has(lang);
5924
+ let j = i + 1;
5925
+ let closed = false;
5926
+ while (j < lines.length) {
5927
+ const close = FENCE_OPEN_RE.exec(lines[j]);
5928
+ if (close && (close[2] ?? "")[0] === markerChar && (close[2] ?? "").length >= marker.length && (close[3] ?? "") === "") {
5929
+ closed = true;
5930
+ break;
5931
+ }
5932
+ j++;
5933
+ }
5934
+ if (isDynamic) {
5935
+ out.push(`${indent}${DATACORE_PLACEHOLDER}`);
5936
+ replaced++;
5937
+ i = closed ? j + 1 : lines.length;
5938
+ } else {
5939
+ out.push(line);
5940
+ if (closed) {
5941
+ for (let k = i + 1; k <= j; k++) out.push(lines[k]);
5942
+ i = j + 1;
5943
+ } else {
5944
+ for (let k = i + 1; k < lines.length; k++) out.push(lines[k]);
5945
+ i = lines.length;
5946
+ }
5947
+ }
5948
+ } else {
5949
+ out.push(line);
5950
+ i++;
5951
+ }
5952
+ }
5953
+ if (replaced === 0) return { content: body, replaced: 0 };
5954
+ return { content: out.join("\n"), replaced };
5955
+ }
5956
+ var DYNAMIC_VIEW_LANGS, DATACORE_PLACEHOLDER, FENCE_OPEN_RE;
5957
+ var init_datacore = __esm({
5958
+ "src/reader/datacore.ts"() {
5959
+ "use strict";
5960
+ init_esm_shims();
5961
+ DYNAMIC_VIEW_LANGS = /* @__PURE__ */ new Set([
5962
+ "datacore",
5963
+ "datacorejsx",
5964
+ "datacorejs",
5965
+ "dataview",
5966
+ "dataviewjs"
5967
+ ]);
5968
+ DATACORE_PLACEHOLDER = "[Datacore view]";
5969
+ FENCE_OPEN_RE = /^(\s*)(`{3,}|~{3,})\s*([A-Za-z0-9_-]*)\s*$/;
5970
+ }
5971
+ });
5972
+
5895
5973
  // src/adapters/source/obsidian-fs/hash.ts
5896
5974
  import { createHash as createHash3 } from "crypto";
5897
5975
  function sha256(input) {
@@ -5944,9 +6022,11 @@ async function parseNote(absolutePath, vaultRoot) {
5944
6022
  const wikilinks = frontmatterLinks.length === 0 ? bodyLinks : mergeFrontmatterIntoBody(bodyLinks, frontmatterLinks);
5945
6023
  const wordCount = countWords2(content);
5946
6024
  const relativePath = toPosix2(path3.relative(path3.resolve(vaultRoot), path3.resolve(absolutePath)));
6025
+ const indexedContent = stripDynamicViewBlocks(content).content;
5947
6026
  return {
5948
6027
  relativePath,
5949
6028
  content,
6029
+ indexedContent,
5950
6030
  frontmatter,
5951
6031
  title,
5952
6032
  hash,
@@ -5990,6 +6070,7 @@ var init_parser = __esm({
5990
6070
  "use strict";
5991
6071
  init_esm_shims();
5992
6072
  init_wikilinks2();
6073
+ init_datacore();
5993
6074
  init_hash();
5994
6075
  }
5995
6076
  });
@@ -6833,6 +6914,9 @@ async function indexVault(vault, options) {
6833
6914
  log(` skipped (parse error): ${rel} \u2014 ${msg}`);
6834
6915
  continue;
6835
6916
  }
6917
+ const previous = vault.db.notes.getByPath(parsed.relativePath);
6918
+ const hashUnchanged = previous != null && previous.hash === parsed.hash;
6919
+ const bodyUnchanged = previous != null && previous.body_hash != null && previous.body_hash === parsed.bodyHash;
6836
6920
  const upsert = vault.db.notes.upsertByPath({
6837
6921
  path: parsed.relativePath,
6838
6922
  content: parsed.content,
@@ -6845,24 +6929,28 @@ async function indexVault(vault, options) {
6845
6929
  });
6846
6930
  vault.db.notes.setStatus(upsert.id, extractStatus(parsed.frontmatter));
6847
6931
  vault.db.aliases.setForNote(upsert.id, extractAliases(parsed.frontmatter));
6848
- const noteExisted = !upsert.isNew;
6849
- const existing = noteExisted ? vault.db.notes.getById(upsert.id) : null;
6850
6932
  const chunkCount = vault.db.chunks.getByNote(upsert.id).length;
6851
- const needsReindex = mode === "full" || upsert.isNew || chunkCount === 0;
6933
+ const bodyChanged = !hashUnchanged && !bodyUnchanged;
6934
+ const needsReindex = mode === "full" || upsert.isNew || chunkCount === 0 || bodyChanged;
6935
+ const frontmatterOnly = !upsert.isNew && !needsReindex && !hashUnchanged;
6852
6936
  if (upsert.isNew) notesIndexed++;
6853
- else if (needsReindex) notesUpdated++;
6937
+ else if (needsReindex || frontmatterOnly) notesUpdated++;
6854
6938
  if (needsReindex) {
6855
6939
  parsedNotes.push({ parsed, noteId: upsert.id, needsReindex: true });
6940
+ } else if (frontmatterOnly) {
6941
+ vault.db.wikilinks.deleteByNote(upsert.id);
6942
+ vault.db.edges.deleteByNote(upsert.id);
6943
+ insertWikilinks(vault, upsert.id, parsed.wikilinks, firstPassResolver);
6944
+ writeAllEdges(vault, upsert.id, parsed, firstPassResolver);
6856
6945
  }
6857
- void existing;
6858
6946
  }
6859
6947
  log(`${parsedNotes.length} notes need (re-)indexing`);
6860
6948
  for (const { parsed, noteId } of parsedNotes) {
6949
+ vault.db.sections.deleteByNote(noteId);
6861
6950
  vault.db.chunks.deleteByNote(noteId);
6862
6951
  vault.db.wikilinks.deleteByNote(noteId);
6863
6952
  vault.db.edges.deleteByNote(noteId);
6864
- vault.db.sections.deleteByNote(noteId);
6865
- const chunks = chunkNote(parsed.content);
6953
+ const chunks = chunkNote(parsed.indexedContent);
6866
6954
  if (chunks.length === 0) {
6867
6955
  insertWikilinks(vault, noteId, parsed.wikilinks, firstPassResolver);
6868
6956
  writeAllEdges(vault, noteId, parsed, firstPassResolver);
@@ -6879,7 +6967,7 @@ async function indexVault(vault, options) {
6879
6967
  }));
6880
6968
  const chunkIds = vault.db.chunks.insertBatch(noteId, chunkInputs);
6881
6969
  try {
6882
- buildSectionsForNote(vault, noteId, parsed.content, chunkIds);
6970
+ buildSectionsForNote(vault, noteId, parsed.indexedContent, chunkIds);
6883
6971
  } catch (err) {
6884
6972
  const message = err instanceof Error ? err.message : String(err);
6885
6973
  console.error(
@@ -7216,7 +7304,7 @@ async function indexNote(options) {
7216
7304
  vault.db.chunks.deleteByNote(upsert.id);
7217
7305
  vault.db.wikilinks.deleteByNote(upsert.id);
7218
7306
  vault.db.edges.deleteByNote(upsert.id);
7219
- const chunks = chunkNote(parsed.content);
7307
+ const chunks = chunkNote(parsed.indexedContent);
7220
7308
  if (chunks.length === 0) {
7221
7309
  insertWikilinks2(vault, upsert.id, parsed.wikilinks);
7222
7310
  writeAllEdges2(vault, upsert.id, parsed);
@@ -7243,7 +7331,7 @@ async function indexNote(options) {
7243
7331
  }))
7244
7332
  );
7245
7333
  try {
7246
- buildSectionsForNote(vault, upsert.id, parsed.content, chunkIds);
7334
+ buildSectionsForNote(vault, upsert.id, parsed.indexedContent, chunkIds);
7247
7335
  } catch (err) {
7248
7336
  const message = err instanceof Error ? err.message : String(err);
7249
7337
  process.stderr.write(
@@ -7848,7 +7936,8 @@ async function writeNote(input) {
7848
7936
  };
7849
7937
  }
7850
7938
  }
7851
- const fileText = frontmatter !== null && Object.keys(frontmatter).length > 0 ? matter2.stringify(content, frontmatter) : content;
7939
+ const yamlDumpOptions = { lineWidth: -1 };
7940
+ const fileText = frontmatter !== null && Object.keys(frontmatter).length > 0 ? matter2.stringify(content, frontmatter, yamlDumpOptions) : content;
7852
7941
  input.onBeforeFsWrite?.();
7853
7942
  await atomicWriteFile(absPath, fileText);
7854
7943
  const written = await readExistingFile(absPath);
@@ -16138,6 +16227,92 @@ var init_contracts2 = __esm({
16138
16227
  }
16139
16228
  });
16140
16229
 
16230
+ // package.json
16231
+ var package_default;
16232
+ var init_package = __esm({
16233
+ "package.json"() {
16234
+ package_default = {
16235
+ name: "@owrede/vault-memory",
16236
+ version: "2.3.0",
16237
+ description: "Local-first semantic memory MCP server for Obsidian vaults",
16238
+ type: "module",
16239
+ license: "MIT",
16240
+ workspaces: [
16241
+ "plugin"
16242
+ ],
16243
+ repository: {
16244
+ type: "git",
16245
+ url: "git+https://github.com/owrede/vault-memory.git"
16246
+ },
16247
+ bin: {
16248
+ "vault-memory": "dist/cli.js"
16249
+ },
16250
+ files: [
16251
+ "dist",
16252
+ "README.md",
16253
+ "LICENSE",
16254
+ "CHANGELOG.md"
16255
+ ],
16256
+ engines: {
16257
+ node: ">=22 <26"
16258
+ },
16259
+ scripts: {
16260
+ build: "tsup",
16261
+ dev: "tsx watch src/cli.ts",
16262
+ start: "node dist/cli.js",
16263
+ test: "vitest run",
16264
+ "test:watch": "vitest",
16265
+ lint: "tsc --noEmit",
16266
+ "lint:adapters": "sh scripts/lint-adapters.sh",
16267
+ "lint:check": 'sh scripts/check-fixture-privacy.sh && sh scripts/lint-no-telemetry.sh && sh scripts/lint-adapters.sh && tsc --noEmit && prettier --check "src/**/*.ts"',
16268
+ format: 'prettier --write "src/**/*.ts"',
16269
+ "eval:baseline": "vitest run evals/v1-baseline/baseline.test.ts",
16270
+ "eval:snapshot": "node evals/v1-baseline/dump-tools.mjs > evals/v1-baseline/tools-list.snapshot.json && node evals/v1-baseline/dump-resources.mjs > evals/v1-baseline/resources-list.snapshot.json",
16271
+ "eval:smoketest": "npm run build && node scripts/smoketest-non-claude.mjs",
16272
+ release: "node scripts/release.mjs",
16273
+ "sync-marketplace": "node scripts/sync-marketplace.mjs"
16274
+ },
16275
+ dependencies: {
16276
+ "@huggingface/tokenizers": "^0.1.3",
16277
+ "@modelcontextprotocol/sdk": "^1.29.0",
16278
+ "better-sqlite3": "^11.7.0",
16279
+ chokidar: "^4.0.1",
16280
+ "cross-spawn": "^7.0.6",
16281
+ graphology: "^0.26.0",
16282
+ "graphology-communities-louvain": "^2.0.2",
16283
+ "gray-matter": "^4.0.3",
16284
+ "onnxruntime-node": "^1.26.0",
16285
+ seedrandom: "^3.0.5",
16286
+ "smol-toml": "^1.3.1",
16287
+ "sqlite-vec": "^0.1.6",
16288
+ yaml: "^2.9.0",
16289
+ zod: "^4.4.3"
16290
+ },
16291
+ devDependencies: {
16292
+ "@types/better-sqlite3": "^7.6.12",
16293
+ "@types/node": "^22.10.0",
16294
+ "@types/seedrandom": "^3.0.8",
16295
+ prettier: "^3.4.0",
16296
+ tsup: "^8.3.5",
16297
+ tsx: "^4.19.2",
16298
+ typescript: "^5.7.0",
16299
+ vitest: "^2.1.8"
16300
+ }
16301
+ };
16302
+ }
16303
+ });
16304
+
16305
+ // src/version.ts
16306
+ var VERSION;
16307
+ var init_version = __esm({
16308
+ "src/version.ts"() {
16309
+ "use strict";
16310
+ init_esm_shims();
16311
+ init_package();
16312
+ VERSION = package_default.version;
16313
+ }
16314
+ });
16315
+
16141
16316
  // src/server.ts
16142
16317
  var server_exports = {};
16143
16318
  __export(server_exports, {
@@ -17219,7 +17394,7 @@ async function serve(options = {}) {
17219
17394
  `);
17220
17395
  });
17221
17396
  }
17222
- var VERSION, MEMORY_AUTO_DISCOVERY_FOLDER;
17397
+ var MEMORY_AUTO_DISCOVERY_FOLDER;
17223
17398
  var init_server = __esm({
17224
17399
  "src/server.ts"() {
17225
17400
  "use strict";
@@ -17258,7 +17433,7 @@ var init_server = __esm({
17258
17433
  init_brief2();
17259
17434
  init_assembly2();
17260
17435
  init_contracts2();
17261
- VERSION = "1.0.0";
17436
+ init_version();
17262
17437
  MEMORY_AUTO_DISCOVERY_FOLDER = "_memory";
17263
17438
  }
17264
17439
  });