rag-memory-epf-mcp 6.3.0 → 6.3.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/README.md CHANGED
@@ -154,6 +154,22 @@ storeDocument(id, content, metadata)
154
154
 
155
155
  ## Changelog
156
156
 
157
+ ### v6.3.1
158
+
159
+ - **Changed — the engine empties the WAL while it is idle.** It runs `wal_checkpoint(TRUNCATE)` itself:
160
+ on a 1 s tick whenever the `-wal` file has bytes in it, ~200 ms after a tool call, first thing in the
161
+ signal handler, and before closing. SQLite folds the WAL only when the *last* connection closes and
162
+ only if that close gets to run; with several engines on one file, or a host that kills the engine
163
+ (measured: codex-cli 0.155.1 sends SIGTERM and SIGKILLs ~185 ms later; Windows terminates children
164
+ outright), frames stayed in the WAL — on 6.3.0 a fresh database sat at 4 KB main + 663 KB WAL for as
165
+ long as the engine ran. In a cloud-synced folder such a WAL can be replayed onto a main file written
166
+ by another machine. A write made in the last ~1 s before a hard kill can still be in the WAL; a 0-byte
167
+ `-wal` and a `-shm` left behind by a hard kill are harmless. Running engines on two machines against
168
+ the same synced file at the same time is still unsafe.
169
+ - **Changed — SIGHUP and SIGBREAK shut the engine down cleanly.** Closing the terminal or multiplexer
170
+ pane used to kill the process by default signal action, before the database was closed.
171
+ - No tool, argument, return shape or schema changed.
172
+
157
173
  ### v6.3.0 (2026-09-16)
158
174
 
159
175
  - **Changed — a relative `DB_FILE_PATH` now resolves against the server's working directory.** It used
package/dist/index.d.ts CHANGED
@@ -101,6 +101,11 @@ export declare class RAGKnowledgeGraphManager {
101
101
  description: string;
102
102
  }>;
103
103
  }>;
104
+ checkpointWal(): boolean;
105
+ private walTimer;
106
+ private walSoon;
107
+ startWalKeeper(tickMs?: number): void;
108
+ noteActivity(delayMs?: number): void;
104
109
  cleanup(): void;
105
110
  private _timestampObservation;
106
111
  createEntities(entities: Array<Entity & {
package/dist/index.js CHANGED
@@ -593,13 +593,78 @@ export class RAGKnowledgeGraphManager {
593
593
  }))
594
594
  };
595
595
  }
596
+ // Fold the WAL into the main file and empty it. Returns true when the WAL holds no
597
+ // frames afterwards.
598
+ //
599
+ // Why the engine does this itself instead of leaving it to SQLite: SQLite folds the WAL
600
+ // only when the LAST connection closes, and only if that close gets to run. Neither holds
601
+ // here — several engines keep the same file open (one per CLI), and some hosts end the
602
+ // engine without letting it finish (codex-cli 0.155.1, measured: SIGTERM, then SIGKILL
603
+ // ~185 ms later; on Windows a child is simply terminated). A WAL with frames that outlives
604
+ // its process is replayed at the next open against whatever main file is there by then, and
605
+ // in a cloud-synced folder that can be a main file written by another machine. SQLite does
606
+ // not check that a WAL belongs to the main file next to it. That corrupted a live database
607
+ // twice. So the invariant is kept continuously: while idle, the WAL is empty.
608
+ //
609
+ // TRUNCATE, not PASSIVE: PASSIVE copies the frames into the main file but leaves them in
610
+ // the WAL, still valid, still replayable onto a foreign main file.
611
+ // busy_timeout is dropped to 0 around the call: TRUNCATE runs the busy handler, and a 5 s
612
+ // stall inside a signal handler or a timer tick is worse than retrying on the next tick.
613
+ checkpointWal() {
614
+ if (!this.db)
615
+ return true;
616
+ try {
617
+ const st = fsSync.statSync(DB_FILE_PATH + '-wal', { throwIfNoEntry: false });
618
+ if (!st || st.size === 0)
619
+ return true;
620
+ this.db.pragma('busy_timeout = 0');
621
+ try {
622
+ const r = this.db.pragma('wal_checkpoint(TRUNCATE)');
623
+ return r?.[0]?.busy === 0;
624
+ }
625
+ finally {
626
+ this.db.pragma('busy_timeout = 5000');
627
+ }
628
+ }
629
+ catch {
630
+ return false; // inside a transaction, or the file is locked — the next tick retries
631
+ }
632
+ }
633
+ walTimer = null;
634
+ walSoon = null;
635
+ // Every write path ends up here without having to be listed: the periodic tick looks at
636
+ // the size of the -wal file, not at which tool ran, so background writers (reconciliation,
637
+ // backfill) are covered as well. noteActivity() only shortens the wait after a tool call.
638
+ startWalKeeper(tickMs = 1000) {
639
+ if (this.walTimer)
640
+ return;
641
+ this.walTimer = setInterval(() => { this.checkpointWal(); }, tickMs);
642
+ this.walTimer.unref();
643
+ }
644
+ noteActivity(delayMs = 200) {
645
+ if (this.walSoon)
646
+ return;
647
+ this.walSoon = setTimeout(() => { this.walSoon = null; this.checkpointWal(); }, delayMs);
648
+ this.walSoon.unref();
649
+ }
596
650
  cleanup() {
651
+ if (this.walTimer) {
652
+ clearInterval(this.walTimer);
653
+ this.walTimer = null;
654
+ }
655
+ if (this.walSoon) {
656
+ clearTimeout(this.walSoon);
657
+ this.walSoon = null;
658
+ }
597
659
  if (this.encoding) {
598
660
  this.encoding.free();
599
661
  this.encoding = null;
600
662
  }
601
663
  this.embeddingCache.clear();
602
664
  if (this.db) {
665
+ // close() folds the WAL only for the last connection; with other engines attached it
666
+ // would leave every frame behind.
667
+ this.checkpointWal();
603
668
  this.db.close();
604
669
  this.db = null;
605
670
  }
@@ -4061,6 +4126,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
4061
4126
  try {
4062
4127
  // Validate arguments using our structured schema
4063
4128
  const validatedArgs = validateToolArgs(name, args);
4129
+ ragKgManager.noteActivity(); // fold the WAL shortly after this call, whatever it wrote
4064
4130
  switch (name) {
4065
4131
  // Original MCP tools
4066
4132
  case "createEntities":
@@ -4235,6 +4301,9 @@ async function main() {
4235
4301
  if (shuttingDown)
4236
4302
  return;
4237
4303
  shuttingDown = true;
4304
+ // Synchronously, before anything is awaited: a host may SIGKILL before the settle
4305
+ // sequence below completes (codex: ~185 ms after SIGTERM).
4306
+ ragKgManager.checkpointWal();
4238
4307
  void (async () => {
4239
4308
  try {
4240
4309
  await server.close();
@@ -4250,6 +4319,13 @@ async function main() {
4250
4319
  };
4251
4320
  process.on('SIGINT', shutdown);
4252
4321
  process.on('SIGTERM', shutdown);
4322
+ // SIGHUP = the terminal or multiplexer pane was closed. Without a handler the default
4323
+ // action kills the process before 'exit' listeners run, so the database is never closed.
4324
+ // On Windows, Node raises SIGHUP when the console window is closed and SIGBREAK on
4325
+ // Ctrl+Break; registering them is harmless where they never fire.
4326
+ process.on('SIGHUP', shutdown);
4327
+ process.on('SIGBREAK', shutdown);
4328
+ ragKgManager.startWalKeeper();
4253
4329
  process.on('exit', () => { try {
4254
4330
  ragKgManager.cleanup();
4255
4331
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rag-memory-epf-mcp",
3
- "version": "6.3.0",
3
+ "version": "6.3.1",
4
4
  "engines": {
5
5
  "node": ">=24"
6
6
  },
@@ -45,7 +45,7 @@
45
45
  "prepare": "npm run build",
46
46
  "watch": "tsc --watch",
47
47
  "verify:invariants": "node test/chunk-invariants.test.mjs",
48
- "verify:engine": "node test/engine-smoke.test.mjs && node test/launch-smoke.test.mjs && node test/sync-atomicity.test.mjs && node test/dedup.test.mjs && node test/search-degradation.test.mjs && node test/entity-embed-cap.test.mjs && node test/migration12.test.mjs && node test/model-cache.test.mjs && node test/embedding-gate.test.mjs && node test/lazy-boot.test.mjs && node test/reconciliation.test.mjs && node test/backfill.test.mjs && node test/fts-query.test.mjs && node test/search-contracts.test.mjs && node test/tool-contracts.test.mjs && node test/bounded-exit.test.mjs && node test/observation-schema.test.mjs && node test/search-graph-default.test.mjs && node test/observation-migration.test.mjs && node test/observation-lifecycle.test.mjs && node test/observation-contracts.test.mjs && node test/observation-search.test.mjs && node test/observation-cascade.test.mjs && node test/observation-realdata.test.mjs && node test/chunker-c.test.mjs && node test/migration14.test.mjs && node test/chunk-params-validation.test.mjs && node test/vector-reuse.test.mjs && node test/entity-range-linking.test.mjs && node test/alias-link-gate.test.mjs && node test/entity-name-boundary.test.mjs && node test/stats-chunking.test.mjs && node test/migration14-realdata.test.mjs && node test/migration14-realdata-sync.test.mjs && node test/search-summaries-off.test.mjs && node test/document-return-contracts.test.mjs && node test/observation-date-prefix.test.mjs && node test/delete-entities-cascade.test.mjs && node test/delete-entities-kg-hygiene.test.mjs && node test/backup-publish-portable.test.mjs && node test/graph-context-explain.test.mjs && node test/eval-graph-role-libs.test.mjs && node test/eval-graph-role-t5b.test.mjs && node --test test/eval-graph-role-t8-fix.test.mjs && node --test test/eval-graph-role-t7-upstream.test.mjs && node --test test/eval-graph-role-t11-decision.test.mjs && node --test test/eval-graph-role-prereq-fix.test.mjs && node test/db-path.test.mjs && node test/mmap-option.test.mjs",
48
+ "verify:engine": "node test/engine-smoke.test.mjs && node test/launch-smoke.test.mjs && node test/sync-atomicity.test.mjs && node test/dedup.test.mjs && node test/search-degradation.test.mjs && node test/entity-embed-cap.test.mjs && node test/migration12.test.mjs && node test/model-cache.test.mjs && node test/embedding-gate.test.mjs && node test/lazy-boot.test.mjs && node test/reconciliation.test.mjs && node test/backfill.test.mjs && node test/fts-query.test.mjs && node test/search-contracts.test.mjs && node test/tool-contracts.test.mjs && node test/bounded-exit.test.mjs && node test/observation-schema.test.mjs && node test/search-graph-default.test.mjs && node test/observation-migration.test.mjs && node test/observation-lifecycle.test.mjs && node test/observation-contracts.test.mjs && node test/observation-search.test.mjs && node test/observation-cascade.test.mjs && node test/observation-realdata.test.mjs && node test/chunker-c.test.mjs && node test/migration14.test.mjs && node test/chunk-params-validation.test.mjs && node test/vector-reuse.test.mjs && node test/entity-range-linking.test.mjs && node test/alias-link-gate.test.mjs && node test/entity-name-boundary.test.mjs && node test/stats-chunking.test.mjs && node test/migration14-realdata.test.mjs && node test/migration14-realdata-sync.test.mjs && node test/search-summaries-off.test.mjs && node test/document-return-contracts.test.mjs && node test/observation-date-prefix.test.mjs && node test/delete-entities-cascade.test.mjs && node test/delete-entities-kg-hygiene.test.mjs && node test/backup-publish-portable.test.mjs && node test/graph-context-explain.test.mjs && node test/eval-graph-role-libs.test.mjs && node test/eval-graph-role-t5b.test.mjs && node --test test/eval-graph-role-t8-fix.test.mjs && node --test test/eval-graph-role-t7-upstream.test.mjs && node --test test/eval-graph-role-t11-decision.test.mjs && node --test test/eval-graph-role-prereq-fix.test.mjs && node test/db-path.test.mjs && node test/mmap-option.test.mjs && node test/wal-exit.test.mjs",
49
49
  "test": "npm run build && npm run verify:invariants && npm run verify:engine",
50
50
  "prepublishOnly": "npm run build && npm run verify:invariants && npm run verify:engine"
51
51
  },