mcp-memory-bucket 0.12.4 → 0.12.5

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.
@@ -182,8 +182,42 @@ export function upsertFile(db, spec, filePath) {
182
182
  .map((c) => `${c} = excluded.${c}`)
183
183
  .join(', ');
184
184
  const conflictTarget = scopedById ? 'folder, id' : 'source_path';
185
- db.prepare(`INSERT INTO ${spec.table} (${cols.join(', ')}) VALUES (${placeholders})
186
- ON CONFLICT(${conflictTarget}) DO UPDATE SET ${updateClause}`).run(...values);
185
+ const insert = () => db
186
+ .prepare(`INSERT INTO ${spec.table} (${cols.join(', ')}) VALUES (${placeholders})
187
+ ON CONFLICT(${conflictTarget}) DO UPDATE SET ${updateClause}`)
188
+ .run(...values);
189
+ try {
190
+ insert();
191
+ }
192
+ catch (err) {
193
+ // Belt-and-suspenders: the collision guards above are pre-checks against a fresh SELECT, and
194
+ // can still miss a stale row above (e.g. a source_path left over from before a folder/root
195
+ // rename, or a filesystem case-sensitivity mismatch between the path stored earlier and the
196
+ // one passed in now) — in which case the plain INSERT itself throws the raw SQLITE_CONSTRAINT
197
+ // error, which used to propagate straight out of upsertFile and abort the entire remote
198
+ // resync/poll tick for every OTHER file too (see the "initial remote skill resync failed"
199
+ // crash report this was added for). Self-heal instead: whatever row is squatting on this
200
+ // conflict target, evict it (same as a real delete would) and retry the insert once. If it
201
+ // still fails after that, log and skip this one file rather than taking the whole sync down.
202
+ if (err instanceof Error && 'code' in err && err.code === 'SQLITE_CONSTRAINT_UNIQUE') {
203
+ const staleSourcePath = scopedById
204
+ ? db.prepare(`SELECT source_path FROM ${spec.table} WHERE folder = ? AND id = ?`).get(folder, id)?.source_path
205
+ : filePath;
206
+ if (staleSourcePath)
207
+ removeFile(db, spec.table, staleSourcePath);
208
+ try {
209
+ insert();
210
+ console.error(`[memory-bucket] recovered from a stale ${spec.table} row blocking ${filePath} (evicted ${staleSourcePath}) — re-indexed successfully`);
211
+ }
212
+ catch (retryErr) {
213
+ console.error(`[memory-bucket] SKIPPED indexing ${filePath}: unresolvable ${spec.table} conflict — ${retryErr.message}`);
214
+ return;
215
+ }
216
+ }
217
+ else {
218
+ throw err;
219
+ }
220
+ }
187
221
  // ref_folder scopes these two tables' delete-then-reinsert the same way the skills upsert above
188
222
  // does: two skills in different folders can now legitimately share an `id`, so ref_id alone is
189
223
  // no longer enough to identify "this file's" search_index/doc_dates rows without also clobbering
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-memory-bucket",
3
- "version": "0.12.4",
3
+ "version": "0.12.5",
4
4
  "type": "module",
5
5
  "description": "MCP server exposing skill_* (reusable coding patterns) and memory_* (point-in-time working context) tools over a markdown+frontmatter source, cached into SQLite at runtime.",
6
6
  "repository": {