@owrede/vault-memory 0.9.0 → 0.9.1

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/README.md CHANGED
@@ -6,9 +6,9 @@ Reads one or more Obsidian vaults, indexes them with local embeddings via Ollama
6
6
 
7
7
  ## Status
8
8
 
9
- **v0.9.0** — Agent-Compatibility & Self-Orientation: OB1-compatible `search`/`fetch` tools so ChatGPT Custom Connectors, Claude.ai, and Deep-Research modes can use vault-memory as a connector; `vault_stats` and `recent_notes` for agent self-orientation on first connect. See `_research/vault-memory-openbrain-comparison.md` in a consuming vault for the gap analysis driving these additions.
9
+ **v0.9.1** — Body-hash short-circuit (migration 006): frontmatter-only edits (the common case for `update_frontmatter`, `/log-fact`, `/import-person`) no longer trigger chunk + embedding regeneration. A new `body_hash` column on `notes` lets the watcher detect "body unchanged, frontmatter changed" and keep all existing chunks/embeddings in place saving one Ollama roundtrip per chunk per frontmatter edit. Legacy rows pre-migration self-heal on next touch.
10
10
 
11
- Previous: **v0.8.3** — Phase 8 (real ONNX cross-encoder reranker) + search-quality fixes + skills consolidation.
11
+ Previous: **v0.9.0** — Agent-Compatibility & Self-Orientation: OB1-compatible `search`/`fetch` tools so ChatGPT Custom Connectors, Claude.ai, and Deep-Research modes can use vault-memory as a connector; `vault_stats` and `recent_notes` for agent self-orientation on first connect.
12
12
 
13
13
  ## Architecture in one paragraph
14
14
 
@@ -214,7 +214,7 @@ fetch({id}) → { id, title, text, url, metadata }
214
214
  ```bash
215
215
  npm install
216
216
  npm run dev # MCP server on stdio with hot reload
217
- npm test # 318 tests across 36 files (v0.9.0)
217
+ npm test # 324 tests across 35 files (v0.9.1)
218
218
  npm run build
219
219
  ```
220
220
 
package/dist/cli.js CHANGED
@@ -302,7 +302,7 @@ function runMigration005(db) {
302
302
  }
303
303
  }
304
304
  }
305
- var INITIAL_SCHEMA, MIGRATION_002_ALIASES, MIGRATION_003_FIX_DELETE_FKS, MIGRATION_004_VARIABLE_DIMS, MIGRATIONS;
305
+ var INITIAL_SCHEMA, MIGRATION_002_ALIASES, MIGRATION_003_FIX_DELETE_FKS, MIGRATION_004_VARIABLE_DIMS, MIGRATION_006_BODY_HASH, MIGRATIONS;
306
306
  var init_schema = __esm({
307
307
  "src/db/schema.ts"() {
308
308
  "use strict";
@@ -310,6 +310,9 @@ var init_schema = __esm({
310
310
  INITIAL_SCHEMA = `
311
311
  -- \u2500\u2500 3.1 Raw Layer \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
312
312
 
313
+ -- Migration 006 adds body_hash to this table (kept out of v1 schema so
314
+ -- the migration chain has historical accuracy and frequent DB-rebuild
315
+ -- tests do not trip over duplicate-column errors).
313
316
  CREATE TABLE IF NOT EXISTS notes (
314
317
  id INTEGER PRIMARY KEY AUTOINCREMENT,
315
318
  path TEXT NOT NULL UNIQUE,
@@ -493,6 +496,10 @@ INSERT INTO embeddings_1024 (chunk_id, model_id, vector)
493
496
  SELECT chunk_id, model_id, vector FROM embeddings;
494
497
 
495
498
  DROP TABLE embeddings;
499
+ `;
500
+ MIGRATION_006_BODY_HASH = `
501
+ ALTER TABLE notes ADD COLUMN body_hash TEXT;
502
+ CREATE INDEX IF NOT EXISTS idx_notes_body_hash ON notes(body_hash);
496
503
  `;
497
504
  MIGRATIONS = [
498
505
  {
@@ -519,6 +526,11 @@ DROP TABLE embeddings;
519
526
  version: 5,
520
527
  description: "add partition key on model_id (two models per dim can coexist)",
521
528
  run: runMigration005
529
+ },
530
+ {
531
+ version: 6,
532
+ description: "add body_hash for frontmatter-only-change short-circuit",
533
+ sql: MIGRATION_006_BODY_HASH
522
534
  }
523
535
  ];
524
536
  }
@@ -540,8 +552,8 @@ var init_notes = __esm({
540
552
  "SELECT * FROM notes WHERE id = ?"
541
553
  );
542
554
  this._insert = db.prepare(`
543
- INSERT INTO notes (path, content, frontmatter, title, hash, mtime, word_count, created_at, updated_at)
544
- VALUES (@path, @content, @frontmatter, @title, @hash, @mtime, @word_count, @now, @now)
555
+ INSERT INTO notes (path, content, frontmatter, title, hash, body_hash, mtime, word_count, created_at, updated_at)
556
+ VALUES (@path, @content, @frontmatter, @title, @hash, @body_hash, @mtime, @word_count, @now, @now)
545
557
  `);
546
558
  this._update = db.prepare(`
547
559
  UPDATE notes
@@ -549,6 +561,7 @@ var init_notes = __esm({
549
561
  frontmatter = @frontmatter,
550
562
  title = @title,
551
563
  hash = @hash,
564
+ body_hash = @body_hash,
552
565
  mtime = @mtime,
553
566
  word_count = @word_count,
554
567
  updated_at = @now
@@ -583,6 +596,7 @@ var init_notes = __esm({
583
596
  frontmatter: input.frontmatter,
584
597
  title: input.title,
585
598
  hash: input.hash,
599
+ body_hash: input.bodyHash,
586
600
  mtime: input.mtime,
587
601
  word_count: input.wordCount,
588
602
  now
@@ -595,6 +609,7 @@ var init_notes = __esm({
595
609
  frontmatter: input.frontmatter,
596
610
  title: input.title,
597
611
  hash: input.hash,
612
+ body_hash: input.bodyHash,
598
613
  mtime: input.mtime,
599
614
  word_count: input.wordCount,
600
615
  now
@@ -2297,6 +2312,9 @@ function canonicalJsonStringify(value) {
2297
2312
  function computeNoteHash(content, frontmatter) {
2298
2313
  return sha256(content + canonicalJsonStringify(frontmatter ?? {}));
2299
2314
  }
2315
+ function computeBodyHash(content) {
2316
+ return sha256(content);
2317
+ }
2300
2318
  var init_hash = __esm({
2301
2319
  "src/reader/hash.ts"() {
2302
2320
  "use strict";
@@ -2536,6 +2554,7 @@ async function parseNote(absolutePath, vaultRoot) {
2536
2554
  const frontmatter = fmData !== void 0 && Object.keys(fmData).length > 0 ? fmData : null;
2537
2555
  const title = extractTitle(content) ?? path3.basename(absolutePath, ".md");
2538
2556
  const hash = computeNoteHash(content, frontmatter);
2557
+ const bodyHash = computeBodyHash(content);
2539
2558
  const mtime = Math.floor(stat.mtimeMs);
2540
2559
  const bodyLinks = extractWikilinks(content);
2541
2560
  const frontmatterLinks = extractFrontmatterWikilinks(frontmatter);
@@ -2550,6 +2569,7 @@ async function parseNote(absolutePath, vaultRoot) {
2550
2569
  frontmatter,
2551
2570
  title,
2552
2571
  hash,
2572
+ bodyHash,
2553
2573
  mtime,
2554
2574
  wikilinks,
2555
2575
  wordCount
@@ -3038,6 +3058,7 @@ async function indexVault(vault, options) {
3038
3058
  frontmatter: parsed.frontmatter ? JSON.stringify(parsed.frontmatter) : null,
3039
3059
  title: parsed.title,
3040
3060
  hash: parsed.hash,
3061
+ bodyHash: parsed.bodyHash,
3041
3062
  mtime: parsed.mtime,
3042
3063
  wordCount: parsed.wordCount
3043
3064
  });
@@ -3499,6 +3520,7 @@ async function updateFrontmatter(input) {
3499
3520
  frontmatter: fmJson,
3500
3521
  title,
3501
3522
  hash: newHash,
3523
+ bodyHash: computeBodyHash(content),
3502
3524
  mtime: Math.floor(stat.mtimeMs),
3503
3525
  wordCount
3504
3526
  });
@@ -3582,6 +3604,31 @@ async function indexNote(options) {
3582
3604
  isNew: false
3583
3605
  };
3584
3606
  }
3607
+ if (existing && existing.body_hash && existing.body_hash === parsed.bodyHash) {
3608
+ const upsert2 = vault.db.notes.upsertByPath({
3609
+ path: parsed.relativePath,
3610
+ content: parsed.content,
3611
+ frontmatter: parsed.frontmatter ? JSON.stringify(parsed.frontmatter) : null,
3612
+ title: parsed.title,
3613
+ hash: parsed.hash,
3614
+ bodyHash: parsed.bodyHash,
3615
+ mtime: parsed.mtime,
3616
+ wordCount: parsed.wordCount
3617
+ });
3618
+ vault.db.aliases.setForNote(
3619
+ upsert2.id,
3620
+ extractAliases(parsed.frontmatter)
3621
+ );
3622
+ vault.db.wikilinks.deleteByNote(upsert2.id);
3623
+ insertWikilinks2(vault, upsert2.id, parsed.wikilinks);
3624
+ return {
3625
+ status: "indexed",
3626
+ notePath: parsed.relativePath,
3627
+ noteId: upsert2.id,
3628
+ chunksCreated: 0,
3629
+ isNew: false
3630
+ };
3631
+ }
3585
3632
  const activeModel = vault.db.models.getActive();
3586
3633
  if (!activeModel) {
3587
3634
  throw new Error(
@@ -3599,6 +3646,7 @@ async function indexNote(options) {
3599
3646
  frontmatter: parsed.frontmatter ? JSON.stringify(parsed.frontmatter) : null,
3600
3647
  title: parsed.title,
3601
3648
  hash: parsed.hash,
3649
+ bodyHash: parsed.bodyHash,
3602
3650
  mtime: parsed.mtime,
3603
3651
  wordCount: parsed.wordCount
3604
3652
  });
@@ -4133,6 +4181,7 @@ async function writeNote(input) {
4133
4181
  frontmatter: written.frontmatter ? JSON.stringify(written.frontmatter) : null,
4134
4182
  title,
4135
4183
  hash: written.hash,
4184
+ bodyHash: computeBodyHash(written.content),
4136
4185
  mtime: Math.floor(stat.mtimeMs),
4137
4186
  wordCount: countWords3(written.content)
4138
4187
  });
@@ -5702,7 +5751,7 @@ var init_server = __esm({
5702
5751
  init_audit3();
5703
5752
  init_watcher2();
5704
5753
  init_indexer2();
5705
- VERSION = "0.9.0";
5754
+ VERSION = "0.9.1";
5706
5755
  ReadNoteArgs = z3.object({
5707
5756
  vault: z3.string(),
5708
5757
  path: z3.string()