@owrede/vault-memory 2.0.0-rc.5 → 2.0.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
@@ -10,6 +10,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
10
10
  > with today's date, and a fresh `## [Unreleased]` block is started above it. See the
11
11
  > bottom of this file for the one-paragraph cut-a-release recipe.
12
12
 
13
+ ## [Unreleased]
14
+
15
+ _Nothing yet._
16
+
17
+ ## [2.0.0] — 2026-06-25
18
+
19
+ ### Fixed
20
+
21
+ - **Live indexer no longer crashes on duplicate-anchor sibling sections** (`ISSUE-indexer-duplicate-anchor.md`). A note containing two sibling sections with identical heading + body produces the same content-hash anchor and collided on `UNIQUE(note_id, anchor)`, aborting the entire `vault-memory index` / `index --full` run with `SQLITE_CONSTRAINT_UNIQUE`. `SectionsQueries` now uses `INSERT OR IGNORE` and a new `insertOneResolving()` that returns the surviving row's id on collision (so `parent_id` linkage still resolves); `buildSectionsForNote` uses it inside a per-note try/catch so one pathological note can never abort the whole vault's index. Mirrors the migration-010 backfill fix already shipped. Verified end-to-end: a full index of a vault with a colliding note exits 0.
22
+
13
23
  ## [2.0.0-rc.1] — 2026-05-19
14
24
 
15
25
 
package/dist/cli.js CHANGED
@@ -3056,7 +3056,7 @@ var init_sections = __esm({
3056
3056
  constructor(db) {
3057
3057
  this.db = db;
3058
3058
  this._insert = db.prepare(`
3059
- INSERT INTO sections
3059
+ INSERT OR IGNORE INTO sections
3060
3060
  (note_id, anchor, heading_path, heading_text, level,
3061
3061
  parent_id, ord, chunk_id_first, chunk_id_last, created_at)
3062
3062
  VALUES
@@ -3126,6 +3126,33 @@ var init_sections = __esm({
3126
3126
  tx(rows);
3127
3127
  return ids;
3128
3128
  }
3129
+ /**
3130
+ * Insert one section, collision-safe. Returns the id of the row that
3131
+ * now owns (note_id, anchor): the freshly inserted row, or — when a
3132
+ * same-anchor sibling already won the unique slot — that surviving
3133
+ * row's id (so callers can resolve parent_id linkage). Mirrors the
3134
+ * backfill behavior in src/sections/backfill.ts. The live indexer
3135
+ * uses this instead of `insertMany` so duplicate-anchor sibling
3136
+ * headings can't abort the whole index run
3137
+ * (see ISSUE-indexer-duplicate-anchor.md).
3138
+ */
3139
+ insertOneResolving(r) {
3140
+ const info = this._insert.run({
3141
+ note_id: r.note_id,
3142
+ anchor: r.anchor,
3143
+ heading_path: r.heading_path,
3144
+ heading_text: r.heading_text,
3145
+ level: r.level,
3146
+ parent_id: r.parent_id,
3147
+ ord: r.ord,
3148
+ chunk_id_first: r.chunk_id_first,
3149
+ chunk_id_last: r.chunk_id_last,
3150
+ created_at: Date.now()
3151
+ });
3152
+ if (info.changes > 0) return Number(info.lastInsertRowid);
3153
+ const existing = this._getByAnchor.get(r.note_id, r.anchor);
3154
+ return existing ? Number(existing.id) : null;
3155
+ }
3129
3156
  deleteByNote(noteId) {
3130
3157
  return this._deleteByNote.run(noteId).changes;
3131
3158
  }
@@ -6352,7 +6379,14 @@ async function indexVault(vault, options) {
6352
6379
  chunkIdFragment: computeChunkIdFragment(c.text)
6353
6380
  }));
6354
6381
  const chunkIds = vault.db.chunks.insertBatch(noteId, chunkInputs);
6355
- buildSectionsForNote(vault, noteId, parsed.content, chunkIds);
6382
+ try {
6383
+ buildSectionsForNote(vault, noteId, parsed.content, chunkIds);
6384
+ } catch (err) {
6385
+ const message = err instanceof Error ? err.message : String(err);
6386
+ console.error(
6387
+ `[indexer:${vault.config.name}] section build failed for ${parsed.relativePath}: ${message} \u2014 skipping sections for this note`
6388
+ );
6389
+ }
6356
6390
  const embedResult = await options.ollama.embed({
6357
6391
  model: options.embeddingModel,
6358
6392
  texts: chunks.map((c) => c.text)
@@ -6517,15 +6551,15 @@ function buildSectionsForNote(vault, noteId, content, insertedChunkIds) {
6517
6551
  chunk_id_first: pair.first,
6518
6552
  chunk_id_last: pair.last
6519
6553
  };
6520
- const ids = vault.db.sections.insertMany([row]);
6521
- insertedIds.push(ids[0]);
6554
+ insertedIds.push(vault.db.sections.insertOneResolving(row));
6522
6555
  }
6523
6556
  return insertedIds.length;
6524
6557
  }
6525
6558
  function mapChunksToSections(chunks, sectionRanges) {
6526
- const out = sectionRanges.map(
6527
- () => ({ first: null, last: null })
6528
- );
6559
+ const out = sectionRanges.map(() => ({
6560
+ first: null,
6561
+ last: null
6562
+ }));
6529
6563
  for (const chunk of chunks) {
6530
6564
  const offset = chunk.start_offset;
6531
6565
  let chosenIdx = null;