mcp-memory-bucket 0.12.3 → 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.
@@ -629,9 +629,11 @@ export class MemoryRepository {
629
629
  const existing = await this.get(folder, filename);
630
630
  if (!existing)
631
631
  throw new Error(`memory doc "${filename}" not found`);
632
- // `paused` is local-cache-only and must never reach writeMarkdownFile — split it off of
633
- // `existing` before spreading the rest into the frontmatter that gets written to disk.
634
- const { paused: existingPaused, ...existingForFile } = existing;
632
+ // `paused` is local-cache-only and `body` is written separately as the markdown body, not
633
+ // frontmatter — both must be split off of `existing` before spreading the rest into
634
+ // `merged`, or they'd ride along into the MemoryFrontmatter written to disk (duplicating the
635
+ // body inside its own frontmatter as a `body:` key).
636
+ const { paused: existingPaused, body: _existingBody, ...existingForFile } = existing;
635
637
  const merged = {
636
638
  ...existingForFile,
637
639
  ...frontmatter,
@@ -531,9 +531,11 @@ export class SkillRepository {
531
531
  const existing = await this.get(name, folder);
532
532
  if (!existing)
533
533
  throw new Error(`skill with name "${name}" not found`);
534
- // `paused` is local-cache-only and must never reach writeMarkdownFile — split it off of
535
- // `existing` before spreading the rest into the frontmatter that gets written to disk.
536
- const { paused: existingPaused, ...existingForFile } = existing;
534
+ // `paused` is local-cache-only and `body` is written separately as the markdown body, not
535
+ // frontmatter — both must be split off of `existing` before spreading the rest into
536
+ // `merged`, or they'd ride along into the SkillFrontmatter written to disk (duplicating the
537
+ // body inside its own frontmatter as a `body:` key).
538
+ const { paused: existingPaused, body: _existingBody, ...existingForFile } = existing;
537
539
  // Builtin skills (e.g. memory-bucket-authoring) are the server's own always-present
538
540
  // documentation, not user content — deprecating them would hide guidance every session needs.
539
541
  const deprecated = this.isBuiltin(existing) ? existing.deprecated : frontmatter?.deprecated;
@@ -585,7 +587,9 @@ export class SkillRepository {
585
587
  const newFilePath = path.join(newDir, 'SKILL.md');
586
588
  // Rename changes the skill's id, so it becomes a fresh cache row — paused (local-only,
587
589
  // keyed by id) does not carry over, same as it wouldn't survive deleting the cache file.
588
- const { paused: _existingPaused, ...existingForFile } = existing;
590
+ // `body` is split off too, for the same reason as in update() — it must not ride along into
591
+ // the SkillFrontmatter written to disk.
592
+ const { paused: _existingPaused, body: _existingBody, ...existingForFile } = existing;
589
593
  const merged = { ...existingForFile, name: newName };
590
594
  const fileContents = formatMarkdownFile(stripSourcePath(merged), existing.body);
591
595
  const remote = this.remoteFor(existing.folder);
@@ -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.3",
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": {