rag-memory-epf-mcp 5.0.0 → 5.1.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/README.md CHANGED
@@ -64,7 +64,7 @@ Place this `.mcp.json` in each project folder with its own `DB_FILE_PATH`. Each
64
64
  ### Document Pipeline (9)
65
65
  | Tool | Description | Annotation |
66
66
  |------|------------|------------|
67
- | `storeDocument` | Store documents with metadata | idempotent |
67
+ | `storeDocument` | Store documents with metadata. Replacing an existing document reports what it destroyed: `{ replaced, deletedChunks }` | idempotent |
68
68
  | `chunkDocument` | Create text chunks with configurable parameters | — |
69
69
  | `embedChunks` | Generate 1024-dim embeddings + auto-link entities | idempotent |
70
70
  | `embedAllEntities` | Batch embed all entities (32 parallel) | idempotent |
@@ -72,7 +72,7 @@ Place this `.mcp.json` in each project folder with its own `DB_FILE_PATH`. Each
72
72
  | `linkEntitiesToDocument` | Link entities to chunks where they actually appear (text-matched) | idempotent |
73
73
  | `deleteDocuments` | Remove documents and associated data | destructive |
74
74
  | `listDocuments` | View all stored documents | readOnly |
75
- | `syncDocumentFromFile` | One-call server-side sync: reads file + delete/store/chunk/embed/link, content stays off model context. Atomic (embed-first transaction swap) + `content_hash` dedup (skips unchanged files) | idempotent |
75
+ | `syncDocumentFromFile` | One-call server-side sync: reads file + delete/store/chunk/embed/link, content stays off model context. Atomic (embed-first transaction swap) + `content_hash` dedup (skips unchanged files). `excludePattern` strips regions before indexing, and the hash follows the stripped text so changing the pattern re-indexes | idempotent |
76
76
 
77
77
  ### Search & Retrieval (9)
78
78
  | Tool | Description | Annotation |
package/dist/index.d.ts CHANGED
@@ -227,6 +227,7 @@ export declare class RAGKnowledgeGraphManager {
227
227
  syncDocumentFromFile(filePath: string, documentId: string, options?: {
228
228
  metadata?: Record<string, any>;
229
229
  content?: string;
230
+ excludePattern?: string | string[];
230
231
  entityNames?: string[];
231
232
  chunkParams?: {
232
233
  maxTokens?: number;
@@ -252,6 +253,8 @@ export declare class RAGKnowledgeGraphManager {
252
253
  storeDocument(id: string, content: string, metadata?: Record<string, any>): Promise<{
253
254
  id: string;
254
255
  stored: boolean;
256
+ replaced: boolean;
257
+ deletedChunks: number;
255
258
  }>;
256
259
  chunkDocument(documentId: string, options?: {
257
260
  maxTokens?: number;
package/dist/index.js CHANGED
@@ -146,6 +146,28 @@ function safeRowid(value) {
146
146
  }
147
147
  return n;
148
148
  }
149
+ // Remove regions the caller does not want indexed. Compiled with `s` because the intended use is
150
+ // spanning a marked block (`<!-- SECRET -->…<!-- /SECRET -->`) and JS has no inline (?s) flag —
151
+ // without it every such pattern would silently match nothing.
152
+ // A malformed pattern throws rather than degrading to "no exclusion": indexing is a disclosure
153
+ // path, so believing you excluded something you did not is worse than a failed sync.
154
+ function applyExcludePatterns(text, pattern) {
155
+ if (pattern === undefined)
156
+ return text;
157
+ const patterns = Array.isArray(pattern) ? pattern : [pattern];
158
+ let out = text;
159
+ for (const p of patterns) {
160
+ let re;
161
+ try {
162
+ re = new RegExp(p, 'gs');
163
+ }
164
+ catch (e) {
165
+ throw new Error(`excludePattern is not a valid regular expression: ${JSON.stringify(p)} (${e.message})`);
166
+ }
167
+ out = out.replace(re, '');
168
+ }
169
+ return out;
170
+ }
149
171
  // Enhanced RAG-enabled Knowledge Graph Manager
150
172
  export class RAGKnowledgeGraphManager {
151
173
  db = null;
@@ -1935,7 +1957,11 @@ export class RAGKnowledgeGraphManager {
1935
1957
  const zero = { reusedChunks: 0, newlyEmbeddedChunks: 0, queuedChunks: 0, deletedChunks: 0, chunkerTransitioned: false };
1936
1958
  for (let attempt = 1; attempt <= 3; attempt++) {
1937
1959
  // r6-3: CAS 재시작 = 처음부터 — 파일 읽기·hash·metadata 도 attempt 안에서 재계산한다.
1938
- const content = options.content !== undefined ? options.content : fsSync.readFileSync(filePath, 'utf-8');
1960
+ // Strip excluded regions before anything else looks at the text. Everything downstream —
1961
+ // content_hash, bytes, chunking — then describes what was actually indexed, so changing the
1962
+ // pattern alone still invalidates the dedup gate below. Hashing the raw file instead would
1963
+ // report `unchanged` for a different exclusion, which is the silent-wrong case.
1964
+ const content = applyExcludePatterns(options.content !== undefined ? options.content : fsSync.readFileSync(filePath, 'utf-8'), options.excludePattern);
1939
1965
  const bytes = Buffer.byteLength(content, 'utf-8');
1940
1966
  const today = new Date().toISOString().slice(0, 10);
1941
1967
  const contentHash = shaHex(content);
@@ -2104,15 +2130,18 @@ export class RAGKnowledgeGraphManager {
2104
2130
  if (!this.db)
2105
2131
  throw new Error('Database not initialized');
2106
2132
  console.error(`📄 Storing document: ${id}`);
2133
+ // Decide `replaced` from the document row, not from the chunk count: a document stored but
2134
+ // never chunked still gets overwritten here, and reporting that as a fresh write would be a lie.
2135
+ const existed = this.db.prepare(`SELECT 1 FROM documents WHERE id = ?`).get(id) !== undefined;
2107
2136
  // Clean up existing document
2108
- await this.cleanupDocument(id);
2137
+ const cleaned = await this.cleanupDocument(id);
2109
2138
  // Store document
2110
2139
  this.db.prepare(`
2111
2140
  INSERT OR REPLACE INTO documents (id, content, metadata)
2112
2141
  VALUES (?, ?, ?)
2113
2142
  `).run(id, content, JSON.stringify(metadata));
2114
2143
  console.error(`✅ Document stored: ${id}`);
2115
- return { id, stored: true };
2144
+ return { id, stored: true, replaced: existed, deletedChunks: cleaned.deletedChunks };
2116
2145
  }
2117
2146
  async chunkDocument(documentId, options = {}) {
2118
2147
  if (!this.db)
@@ -2430,9 +2459,13 @@ export class RAGKnowledgeGraphManager {
2430
2459
  console.error(`✅ Entities linked: ${linkedCount} entities linked to document`);
2431
2460
  return { documentId, linkedEntities: linkedCount };
2432
2461
  }
2462
+ // Report what was destroyed. The counts were already computed here and thrown away, so a caller
2463
+ // that replaces a document could not tell from the return value that anything was deleted
2464
+ // (2026-08-05 field report from a deployed project: "{stored:true} came back and I did not know
2465
+ // what I had just wiped"). Silent destruction is the defect; the numbers are free.
2433
2466
  async cleanupDocument(documentId) {
2434
2467
  if (!this.db)
2435
- return;
2468
+ return { deletedChunks: 0, deletedAssociations: 0, deletedVectors: 0 };
2436
2469
  console.error(`🧹 Cleaning up document: ${documentId}`);
2437
2470
  // Get existing chunks
2438
2471
  const existingChunks = this.db.prepare(`
@@ -2460,6 +2493,7 @@ export class RAGKnowledgeGraphManager {
2460
2493
  console.error(` ├─ Deleted ${deletedVectors} vector embeddings`);
2461
2494
  console.error(` └─ Deleted ${metadata.changes} chunk metadata records`);
2462
2495
  }
2496
+ return { deletedChunks: existingChunks.length, deletedAssociations, deletedVectors };
2463
2497
  }
2464
2498
  async deleteDocument(documentId) {
2465
2499
  if (!this.db)
@@ -3839,6 +3873,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
3839
3873
  return { content: [{ type: "text", text: JSON.stringify(await ragKgManager.syncDocumentFromFile(validatedArgs.path, validatedArgs.documentId, {
3840
3874
  metadata: validatedArgs.metadata,
3841
3875
  content: validatedArgs.content,
3876
+ excludePattern: validatedArgs.excludePattern,
3842
3877
  entityNames: validatedArgs.entityNames,
3843
3878
  chunkParams: validatedArgs.chunkParams,
3844
3879
  }), null, 2) }] };
@@ -594,6 +594,7 @@ const syncDocumentFromFileSchema = {
594
594
  documentId: z.string().describe('RAG document ID to (re)create from the file'),
595
595
  metadata: z.record(z.any()).optional().describe('Metadata merged into the stored document'),
596
596
  content: z.string().optional().describe('Optional content override; stored instead of reading the file'),
597
+ excludePattern: z.union([z.string(), z.array(z.string())]).optional().describe('Regular expression(s) whose matches are stripped before indexing. Applied to the file (or to `content`) first, so hash, byte count and chunking all describe what was actually indexed. Compiled with the dotAll flag, so a pattern may span lines to drop a marked block. An invalid expression fails the call rather than indexing the whole file.'),
597
598
  entityNames: z.array(z.string()).optional().describe('Optional entities to explicitly link'),
598
599
  chunkParams: z.record(z.any()).optional().describe('Optional chunking parameters { maxTokens }. overlap: omit or 0 only (rejected otherwise since v5.0.0)'),
599
600
  };
package/docs/UPDATING.md CHANGED
@@ -108,6 +108,41 @@ path and holder pid (e.g. `.download-<key>.lock`). Verify the holder process
108
108
  is genuinely gone or hung (`ps -p <pid>`), then remove the lock file manually;
109
109
  the next start becomes a clean download owner.
110
110
 
111
+ ## v5.1.0 (schema v14, unchanged): destructive-replace reporting + `excludePattern`
112
+
113
+ **What changes on upgrade**: nothing you have to do. No migration, no schema
114
+ change, no re-embedding. Both changes are additive — existing calls keep their
115
+ arguments and keep working, and the new response fields are extra keys.
116
+
117
+ **`storeDocument` now says what it destroyed.** It has always deleted the
118
+ previous document's chunks, vectors and entity links before writing, but the
119
+ response was `{ id, stored: true }`, so a caller replacing a document could not
120
+ tell from the return value that anything was removed. It now returns
121
+ `{ id, stored, replaced, deletedChunks }`, matching what `syncDocumentFromFile`
122
+ already reported. `replaced` is decided by the document row, not the chunk
123
+ count — a document that was stored but never chunked still gets overwritten,
124
+ and reporting that as a fresh write would be wrong.
125
+
126
+ **`syncDocumentFromFile` accepts `excludePattern`** (string or array of
127
+ strings): regions matching these regular expressions are stripped before
128
+ indexing. Previously the only way to leave part of a file out was to read it
129
+ yourself and pass the whole edited text through `content`, which defeats the
130
+ point of a tool that reads server-side to keep content off the model context.
131
+
132
+ Three properties worth knowing:
133
+
134
+ 1. **The exclusion happens first**, before hashing and chunking, so
135
+ `content_hash`, the reported `bytes` and the chunk boundaries all describe
136
+ what was actually indexed. Changing only the pattern therefore invalidates
137
+ the dedup gate and re-indexes; it does not silently return `unchanged`.
138
+ 2. **Patterns are compiled with the dotAll flag**, so one pattern can span
139
+ lines to drop a marked block (`<!-- SECRET -->[\s\S]*?<!-- /SECRET -->`).
140
+ JavaScript has no inline `(?s)`, so without this every such pattern would
141
+ quietly match nothing.
142
+ 3. **An invalid expression fails the call.** Degrading to "no exclusion" would
143
+ index the whole file while the caller believes it was filtered, and an index
144
+ is a disclosure path — a failed sync is the safer error.
145
+
111
146
  ## v5.0.0 (schema v14): chunker c1 + vector reuse
112
147
 
113
148
  **Breaking**: `chunkParams.overlap` is rejected on BOTH public paths
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "rag-memory-epf-mcp",
3
- "version": "5.0.0",
3
+ "version": "5.1.0",
4
4
  "engines": {
5
5
  "node": ">=24"
6
6
  },
7
- "description": "Project-local RAG memory MCP server \u2014 knowledge graph + multilingual vector + FTS5 in a single SQLite file. Per-project isolation, 38 MCP tools, codepoint-safe chunking (Korean/CJK/emoji).",
7
+ "description": "Project-local RAG memory MCP server knowledge graph + multilingual vector + FTS5 in a single SQLite file. Per-project isolation, 38 MCP tools, codepoint-safe chunking (Korean/CJK/emoji).",
8
8
  "keywords": [
9
9
  "mcp",
10
10
  "model-context-protocol",
@@ -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/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/stats-chunking.test.mjs && node test/migration14-realdata.test.mjs && node test/migration14-realdata-sync.test.mjs && node test/search-summaries-off.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/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/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",
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
  },