@owrede/vault-memory 2.2.0 → 2.2.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/CHANGELOG.md +6 -0
- package/dist/cli.js +31 -12
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -14,6 +14,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
14
14
|
|
|
15
15
|
_Nothing yet._
|
|
16
16
|
|
|
17
|
+
## [2.2.1] — 2026-06-29
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
|
|
21
|
+
- **Section identity is now content + context, not content alone (ADR-032, migration 015).** Two byte-identical sibling sections in *different* contexts (e.g. `# Q1 > ## Risks "TBD"` and `# Q2 > ## Risks "TBD"`) previously collapsed into one row under `UNIQUE(note_id, anchor)` — silently dropping the second — because the content-hash anchor excludes the ancestor chain. Section identity is now `(note_id, heading_path, anchor)`, so differently-placed identical sections persist as distinct rows. `anchor` stays a pure content hash (ADR-003 H-7 and the D-05 `source_hashes` contract are unchanged); `search-sections` dedup widened to match. Migration 015 swaps the unique index; a verbatim block repeated under the *same* parent still collapses (one citation). Existing DBs reconcile on the next `index --full`.
|
|
22
|
+
|
|
17
23
|
## [2.2.0] — 2026-06-29
|
|
18
24
|
|
|
19
25
|
### Added
|
package/dist/cli.js
CHANGED
|
@@ -1320,7 +1320,7 @@ function backfillSectionsFromChunks(db) {
|
|
|
1320
1320
|
@parent_id, @ord, @chunk_id_first, @chunk_id_last, @created_at)
|
|
1321
1321
|
`);
|
|
1322
1322
|
const lookupExistingSection = db.prepare(
|
|
1323
|
-
"SELECT id FROM sections WHERE note_id = ? AND anchor = ?"
|
|
1323
|
+
"SELECT id FROM sections WHERE note_id = ? AND heading_path = ? AND anchor = ?"
|
|
1324
1324
|
);
|
|
1325
1325
|
let backfilled = 0;
|
|
1326
1326
|
const now = Date.now();
|
|
@@ -1356,7 +1356,11 @@ function backfillSectionsFromChunks(db) {
|
|
|
1356
1356
|
if (info.changes > 0) {
|
|
1357
1357
|
insertedIds.push(Number(info.lastInsertRowid));
|
|
1358
1358
|
} else {
|
|
1359
|
-
const existing2 = lookupExistingSection.get(
|
|
1359
|
+
const existing2 = lookupExistingSection.get(
|
|
1360
|
+
note.id,
|
|
1361
|
+
JSON.stringify(s.heading_path),
|
|
1362
|
+
s.anchor
|
|
1363
|
+
);
|
|
1360
1364
|
insertedIds.push(existing2 ? Number(existing2.id) : null);
|
|
1361
1365
|
}
|
|
1362
1366
|
}
|
|
@@ -1688,6 +1692,11 @@ function runMigration014(db, _ctx) {
|
|
|
1688
1692
|
ON contract_audit(verb);
|
|
1689
1693
|
`);
|
|
1690
1694
|
}
|
|
1695
|
+
function runMigration015(db, _ctx) {
|
|
1696
|
+
db.exec(
|
|
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
|
+
);
|
|
1699
|
+
}
|
|
1691
1700
|
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;
|
|
1692
1701
|
var init_schema = __esm({
|
|
1693
1702
|
"src/db/schema.ts"() {
|
|
@@ -1963,6 +1972,11 @@ CREATE INDEX IF NOT EXISTS idx_notes_doc_uri ON notes(doc_uri);
|
|
|
1963
1972
|
version: 14,
|
|
1964
1973
|
description: "contract_audit table \u2014 Phase 6 / CON-* / Q-AUD",
|
|
1965
1974
|
run: runMigration014
|
|
1975
|
+
},
|
|
1976
|
+
{
|
|
1977
|
+
version: 15,
|
|
1978
|
+
description: "section identity = (note_id, heading_path, anchor) \u2014 context-aware, no longer collapse byte-identical siblings in different contexts (ADR-032 revised)",
|
|
1979
|
+
run: runMigration015
|
|
1966
1980
|
}
|
|
1967
1981
|
];
|
|
1968
1982
|
}
|
|
@@ -3067,6 +3081,9 @@ var init_sections = __esm({
|
|
|
3067
3081
|
this._getByAnchor = db.prepare(
|
|
3068
3082
|
"SELECT * FROM sections WHERE note_id = ? AND anchor = ?"
|
|
3069
3083
|
);
|
|
3084
|
+
this._getByIdentity = db.prepare(
|
|
3085
|
+
"SELECT * FROM sections WHERE note_id = ? AND heading_path = ? AND anchor = ?"
|
|
3086
|
+
);
|
|
3070
3087
|
this._findContainingChunk = db.prepare(
|
|
3071
3088
|
// `chunk_id` is monotonically increasing per note; chunk_id_first
|
|
3072
3089
|
// and chunk_id_last carve disjoint ranges (or both NULL for a
|
|
@@ -3090,6 +3107,7 @@ var init_sections = __esm({
|
|
|
3090
3107
|
_deleteByNote;
|
|
3091
3108
|
_getByNote;
|
|
3092
3109
|
_getByAnchor;
|
|
3110
|
+
_getByIdentity;
|
|
3093
3111
|
_findContainingChunk;
|
|
3094
3112
|
_countByNote;
|
|
3095
3113
|
/**
|
|
@@ -3122,14 +3140,15 @@ var init_sections = __esm({
|
|
|
3122
3140
|
return ids;
|
|
3123
3141
|
}
|
|
3124
3142
|
/**
|
|
3125
|
-
* Insert one section, collision-safe. Returns the id of the row that
|
|
3126
|
-
*
|
|
3127
|
-
* same-
|
|
3128
|
-
* row's id (so callers can resolve parent_id
|
|
3129
|
-
*
|
|
3130
|
-
*
|
|
3131
|
-
*
|
|
3132
|
-
*
|
|
3143
|
+
* Insert one section, collision-safe. Returns the id of the row that now
|
|
3144
|
+
* owns the identity (note_id, heading_path, anchor): the freshly inserted
|
|
3145
|
+
* row, or — when a same-context byte-identical sibling already won the
|
|
3146
|
+
* unique slot — that surviving row's id (so callers can resolve parent_id
|
|
3147
|
+
* linkage). Per ADR-032 (revised), a collision now requires BOTH same anchor
|
|
3148
|
+
* AND same heading_path, so differently-placed identical sections persist as
|
|
3149
|
+
* distinct rows. Mirrors src/sections/backfill.ts. The live indexer uses
|
|
3150
|
+
* this instead of `insertMany` so duplicate sibling headings can't abort the
|
|
3151
|
+
* whole index run (see ISSUE-indexer-duplicate-anchor.md).
|
|
3133
3152
|
*/
|
|
3134
3153
|
insertOneResolving(r) {
|
|
3135
3154
|
const info = this._insert.run({
|
|
@@ -3145,7 +3164,7 @@ var init_sections = __esm({
|
|
|
3145
3164
|
created_at: Date.now()
|
|
3146
3165
|
});
|
|
3147
3166
|
if (info.changes > 0) return Number(info.lastInsertRowid);
|
|
3148
|
-
const existing = this.
|
|
3167
|
+
const existing = this._getByIdentity.get(r.note_id, r.heading_path, r.anchor);
|
|
3149
3168
|
return existing ? Number(existing.id) : null;
|
|
3150
3169
|
}
|
|
3151
3170
|
deleteByNote(noteId) {
|
|
@@ -10756,7 +10775,7 @@ async function searchSections(deps, args2) {
|
|
|
10756
10775
|
const resolution = deps.sectionForHit(hit.vault, hit.notePath, hit.chunkIdx);
|
|
10757
10776
|
if (!resolution) continue;
|
|
10758
10777
|
if (resolution.headingPath.length === 0) continue;
|
|
10759
|
-
const key = `${resolution.noteId}#${resolution.anchor}`;
|
|
10778
|
+
const key = `${resolution.noteId}#${resolution.headingPath.join("\0")}#${resolution.anchor}`;
|
|
10760
10779
|
const existing = sectionMap.get(key);
|
|
10761
10780
|
if (!existing) {
|
|
10762
10781
|
sectionMap.set(key, {
|