@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 +10 -0
- package/dist/cli.js +41 -7
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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;
|