rag-memory-epf-mcp 3.6.0 → 5.0.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
@@ -13,8 +13,9 @@ A **project-local RAG memory** MCP server — knowledge graph + multilingual vec
13
13
  - **3-signal hybrid search** — vector similarity (bge-m3, 1024-dim) + FTS5 BM25 keyword matching + knowledge graph re-ranking, combined via Reciprocal Rank Fusion
14
14
  - **100+ languages** — Korean, Chinese, Japanese, Arabic, and more. Cross-lingual search works out of the box.
15
15
  - **Graph-aware scoring** — per-entity geometric decay (0.5^i) with hard cap prevents any single document from dominating results
16
- - **31 MCP tools** — knowledge graph CRUD, document pipeline, hybrid search, multi-hop traversal, graph analytics (centrality / community detection / structure), export/import, temporal queries
17
- - **Codepoint-safe chunking** — chunk offsets are Unicode codepoints, language-neutral across SQL `substr`, Python slicing, and JS `[...str]` iteration. Korean/CJK/emoji documents stay aligned. Verified by a publish-time invariant test.
16
+ - **38 MCP tools** — knowledge graph CRUD, observation lifecycle (correct / retract / history), document pipeline, hybrid search, multi-hop traversal, graph analytics (centrality / community detection / structure), export/import, temporal queries
17
+ - **Observations that hold their history** — corrections supersede instead of overwrite, search returns only current facts, and every revision keeps its provenance
18
+ - **Structure-anchored chunking (c1, v5)** — boundaries anchor to markdown structure (fence-aware, H1–H4 first, block-greedy, exact-token fallback), so editing the top of a file no longer re-embeds the whole document: unchanged text reuses its stored vectors at sync time. Chunk offsets are Unicode codepoints, language-neutral across SQL `substr`, Python slicing, and JS `[...str]` iteration; a publish-time invariant gate locks the gap-free partition. `overlap` is retired (omit or 0).
18
19
  - **SQLite optimized** — WAL mode, 32MB cache, 256MB mmap, FTS5 triggers, 7 indexes
19
20
  - **MCP SDK 1.27.1** — Tool Annotations (readOnly/destructive/idempotent), latest protocol 2025-11-25
20
21
 
@@ -36,7 +37,7 @@ A **project-local RAG memory** MCP server — knowledge graph + multilingual vec
36
37
 
37
38
  Place this `.mcp.json` in each project folder with its own `DB_FILE_PATH`. Each project maintains completely isolated memory.
38
39
 
39
- ## Tools (31)
40
+ ## Tools (38)
40
41
 
41
42
  ### Knowledge Graph (7)
42
43
  | Tool | Description | Annotation |
@@ -47,7 +48,18 @@ Place this `.mcp.json` in each project folder with its own `DB_FILE_PATH`. Each
47
48
  | `updateRelations` | Update relationship confidence and metadata | idempotent |
48
49
  | `deleteEntities` | Remove entities and relationships | destructive |
49
50
  | `deleteRelations` | Remove specific relationships | destructive |
50
- | `deleteObservations` | Remove specific observations | destructive |
51
+ | `deleteObservations` | **Deprecated** soft-retract shim — see Observation Lifecycle | destructive |
52
+
53
+ ### Observation Lifecycle (7)
54
+ | Tool | Description | Annotation |
55
+ |------|------------|------------|
56
+ | `correctObservation` | Supersede a revision with corrected text, keeping the old one | |
57
+ | `retractObservation` | `active` → `retracted` (hidden from search, kept in history) | |
58
+ | `restoreObservation` | `retracted` → `active` | |
59
+ | `approveObservation` | `provisional` → `active` | |
60
+ | `declineObservation` | `provisional` → `retracted` (reason required) | |
61
+ | `purgeObservation` | Physically delete a revision and its successors (`confirm='PURGE'`) | destructive |
62
+ | `getObservationHistory` | Every revision, status, provenance and event | read-only |
51
63
 
52
64
  ### Document Pipeline (9)
53
65
  | Tool | Description | Annotation |
@@ -93,7 +105,7 @@ Place this `.mcp.json` in each project folder with its own `DB_FILE_PATH`. Each
93
105
 
94
106
  ```
95
107
  storeDocument(id, content, metadata)
96
- → chunkDocument(documentId, maxTokens, overlap)
108
+ → chunkDocument(documentId, maxTokens) # overlap retired in v5 (omit or 0)
97
109
  → embedChunks(documentId)
98
110
  ├── generates vector embeddings for each chunk
99
111
  ├── auto-links entities to chunks (word boundary + CJK aware)
@@ -121,7 +133,8 @@ storeDocument(id, content, metadata)
121
133
  │ │ ├── chunks (sqlite-vec, 1024-dim) │ │
122
134
  │ │ ├── entity_embeddings (sqlite-vec) │ │
123
135
  │ │ ├── entities_fts + chunks_fts (FTS5) │ │
124
- │ │ └── 11 migrations (auto-applied) │ │
136
+ │ │ ├── observation lifecycle (4 tables) │ │
137
+ │ │ └── 13 migrations (auto-applied) │ │
125
138
  │ └────────────────────────────────────────┘ │
126
139
  │ │
127
140
  │ bge-m3 (ONNX, 100+ langs) │
@@ -140,6 +153,45 @@ storeDocument(id, content, metadata)
140
153
 
141
154
  ## Changelog
142
155
 
156
+ ### v4.0.0
157
+
158
+ **Observation lifecycle (schema v13).** Observations used to be a JSON array of strings on the
159
+ entity row. A correction overwrote a string, so the fact that it *was* a correction disappeared —
160
+ and if you deleted the wrong duplicate, nothing recorded that either. Observations now have stable
161
+ ids, provenance, and a status, and `entities.observations` becomes a projection synthesised from
162
+ the `active` revisions.
163
+
164
+ - **Corrections keep the previous revision.** `correctObservation(observation_id, content, change_kind, reason)`
165
+ marks the old revision `superseded` and inserts a new one that inherits its position in the array,
166
+ so a correction does not reorder anything.
167
+ - **Search returns `active` revisions only.** A retracted or superseded fact stops coming back from
168
+ `openNodes` / `searchNodes` / `readGraph` / `getNeighbors` without being destroyed.
169
+ - **`getObservationHistory({entity_name | observation_id | root_id})` is the only history surface.**
170
+ It always returns `{ roots: [...] }`, one root per logical observation, revisions oldest-first.
171
+ - **State transitions are a table, not a guess**: `retract` / `restore` / `approve` / `decline`.
172
+ `superseded` is terminal. Anything outside the table is rejected.
173
+ - **Provenance**: `addObservations` and `createEntities` accept `sources: [{source_kind, source_ref, source_hash?}]`.
174
+ Repeated content from a *new* source adds evidence to the existing revision instead of a duplicate.
175
+ Unknown provenance is zero source rows — the engine does not invent one.
176
+ - **`observation_ids`**: `addObservations` and `createEntities` return ids aligned 1:1 with the input,
177
+ `null` where no revision was created (dedup or source-only).
178
+ - **`purgeObservation(observation_id, 'PURGE')`** physically deletes, as a suffix purge from the
179
+ target to the newest revision of that root. It is separate, explicit, and almost never what you want.
180
+ - **⚠️ BREAKING**: `deleteObservations` is deprecated. It now performs a soft **retract** instead of
181
+ a delete, and a batch where any item matches two or more active revisions **aborts with zero
182
+ mutations** — v3.6 deleted every duplicate and carried on, but a machine cannot tell which
183
+ revision was meant. Use `retractObservation(observation_id)` to say which one.
184
+ - Migration to v13 writes a recovery point first (`<db>.v12.bak`) using the SQLite Online Backup API,
185
+ and verifies it — `quick_check` plus FTS5's own `integrity-check`, because a snapshot can be
186
+ structurally valid and still have a broken full-text index. **An existing one is never
187
+ overwritten**: the next attempt writes `.bak.1`, then `.bak.2`. Every file in that rotation was
188
+ taken before any schema change, so each is a valid pre-migration snapshot on its own and nothing
189
+ has to prove which matches the live database. Slots are bounded and a full set refuses to migrate.
190
+ Conversion runs in one transaction with two gates: `PRAGMA foreign_key_check`, then a byte-exact
191
+ comparison of the rebuilt projection against the original array. `foreign_keys` is checked at boot
192
+ and the server refuses to migrate without it. Restore and slot-exhaustion runbook:
193
+ `docs/UPDATING.md`.
194
+
143
195
  ### v3.6.0
144
196
  - **Lite install / lazy boot**: the MCP server connects immediately — FTS5 search, knowledge graph and CRUD work from the first second, while the bge-m3 model (~1.2GB) loads or downloads in the background. Hybrid search switches on automatically. Requires Node **>= 24**.
145
197
  - **Version-independent model cache** with a cross-process download lock: engine version bumps no longer re-download the model, and concurrent servers on one machine never corrupt a download. Cleaning the npx cache no longer deletes the model.
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ import { type SourceInput } from './src/observations/lifecycle.js';
2
3
  import { EmbeddingGate } from './src/embeddingGate.js';
3
4
  import type { EmbedPriority } from './src/embeddingGate.js';
4
5
  import { BackfillCoordinator } from './src/backfillCoordinator.js';
@@ -45,6 +46,22 @@ interface DetailedContext {
45
46
  entities: string[];
46
47
  metadata: Record<string, any>;
47
48
  }
49
+ export declare class SyncCasConflictError extends Error {
50
+ constructor(documentId: string);
51
+ }
52
+ export declare function setSyncFaultPoint(point: string | null, fn: (() => void) | null): void;
53
+ export interface ReuseCandidate {
54
+ rowid: number;
55
+ text: string;
56
+ input_hash: string | null;
57
+ profile_id: number | null;
58
+ provenance_state: string | null;
59
+ embedding: Buffer | null;
60
+ }
61
+ export declare function selectReusableVector(candidates: ReuseCandidate[], text: string, currentProfileId: number, sha256hex: (s: string) => string): {
62
+ vec: Buffer;
63
+ provenance: 'verified' | 'legacy_assumed';
64
+ } | null;
48
65
  export declare class RAGKnowledgeGraphManager {
49
66
  private db;
50
67
  private encoding;
@@ -59,6 +76,7 @@ export declare class RAGKnowledgeGraphManager {
59
76
  initialize(opts?: {
60
77
  skipModel?: boolean;
61
78
  gate?: EmbeddingGate;
79
+ __testForceFkOff?: boolean;
62
80
  }): Promise<void>;
63
81
  private ensureCurrentProfile;
64
82
  private buildRealLoader;
@@ -67,6 +85,7 @@ export declare class RAGKnowledgeGraphManager {
67
85
  entityInputHash(entityId: string): string | null;
68
86
  tryEmbedEntity(entityId: string, priority?: EmbedPriority): Promise<'embedded' | 'queued' | 'disabled'>;
69
87
  private mutateEntityAndInvalidate;
88
+ private invalidateDerivedForEntity;
70
89
  invalidateEntityVector(entityId: string): void;
71
90
  reembedChunkByRowid(rowid: number): Promise<boolean>;
72
91
  shutdownAll(): Promise<void>;
@@ -80,15 +99,41 @@ export declare class RAGKnowledgeGraphManager {
80
99
  }>;
81
100
  cleanup(): void;
82
101
  private _timestampObservation;
83
- createEntities(entities: Entity[]): Promise<Entity[]>;
102
+ createEntities(entities: Array<Entity & {
103
+ status?: 'active' | 'provisional';
104
+ sources?: SourceInput[];
105
+ }>): Promise<Array<Entity & {
106
+ created?: boolean;
107
+ observation_ids?: (string | null)[];
108
+ }>>;
84
109
  createRelations(relations: Relation[]): Promise<Relation[]>;
85
110
  addObservations(observations: {
86
111
  entityName: string;
87
112
  contents: string[];
88
- }[]): Promise<{
113
+ status?: 'active' | 'provisional';
114
+ sources?: SourceInput[];
115
+ }[]): Promise<Array<{
89
116
  entityName: string;
117
+ observation_ids: (string | null)[];
90
118
  addedObservations: string[];
91
- }[]>;
119
+ embedding_status?: string;
120
+ }>>;
121
+ correctObservation(observationId: string, content: string, changeKind?: 'correction' | 'world_change', reason?: string): Promise<string>;
122
+ private _transition;
123
+ retractObservation(observationId: string, reason?: string): Promise<void>;
124
+ restoreObservation(observationId: string, reason?: string): Promise<void>;
125
+ approveObservation(observationId: string, reason?: string): Promise<void>;
126
+ declineObservation(observationId: string, reason: string): Promise<void>;
127
+ purgeObservation(observationId: string, confirm: string): Promise<{
128
+ purged: number;
129
+ }>;
130
+ getObservationHistory(sel: {
131
+ entity_name?: string;
132
+ observation_id?: string;
133
+ root_id?: string;
134
+ }): Promise<{
135
+ roots: any[];
136
+ }>;
92
137
  deleteEntities(entityNames: string[]): Promise<void>;
93
138
  deleteObservations(deletions: {
94
139
  entityName: string;
@@ -97,7 +142,7 @@ export declare class RAGKnowledgeGraphManager {
97
142
  results: Array<{
98
143
  entityName: string;
99
144
  deleted: number;
100
- embedding_status: 'embedded' | 'queued' | 'disabled' | 'n/a';
145
+ embedding_status: string;
101
146
  }>;
102
147
  total_deleted: number;
103
148
  }>;
@@ -176,7 +221,8 @@ export declare class RAGKnowledgeGraphManager {
176
221
  private translateQueryWithMap;
177
222
  private buildCrossLingualVariants;
178
223
  private extractTermsFromText;
179
- private chunkText;
224
+ private chunkStructured;
225
+ private validateChunkParams;
180
226
  private generateEmbedding;
181
227
  syncDocumentFromFile(filePath: string, documentId: string, options?: {
182
228
  metadata?: Record<string, any>;
@@ -196,6 +242,12 @@ export declare class RAGKnowledgeGraphManager {
196
242
  warning?: string;
197
243
  skipped?: boolean;
198
244
  reason?: string;
245
+ embedding_status?: string;
246
+ reusedChunks: number;
247
+ newlyEmbeddedChunks: number;
248
+ queuedChunks: number;
249
+ deletedChunks: number;
250
+ chunkerTransitioned: boolean;
199
251
  }>;
200
252
  storeDocument(id: string, content: string, metadata?: Record<string, any>): Promise<{
201
253
  id: string;
@@ -223,6 +275,7 @@ export declare class RAGKnowledgeGraphManager {
223
275
  errors?: string[];
224
276
  }>;
225
277
  private hasCJK;
278
+ private buildEntityRangeFinder;
226
279
  private buildEntityMatcher;
227
280
  private autoLinkEntities;
228
281
  extractTerms(documentId: string, options?: {
@@ -275,6 +328,10 @@ export declare class RAGKnowledgeGraphManager {
275
328
  entities: any[];
276
329
  relations: any[];
277
330
  documents: any[];
331
+ observation_roots: any[];
332
+ entity_observations: any[];
333
+ observation_sources: any[];
334
+ observation_events: any[];
278
335
  metadata: {
279
336
  exportedAt: string;
280
337
  version: string;
@@ -287,6 +344,10 @@ export declare class RAGKnowledgeGraphManager {
287
344
  entities?: any[];
288
345
  relations?: any[];
289
346
  documents?: any[];
347
+ observation_roots?: any[];
348
+ entity_observations?: any[];
349
+ observation_sources?: any[];
350
+ observation_events?: any[];
290
351
  }, options?: {
291
352
  merge?: boolean;
292
353
  }): Promise<{
@@ -300,6 +361,12 @@ export declare class RAGKnowledgeGraphManager {
300
361
  relations: number;
301
362
  documents: number;
302
363
  };
364
+ observation_order_remap: Array<{
365
+ root_id: string;
366
+ entity_id: string;
367
+ from: number;
368
+ to: number;
369
+ }>;
303
370
  }>;
304
371
  hybridSearch(query: string, limit?: number, useGraph?: boolean): Promise<{
305
372
  results: EnhancedSearchResult[];
@@ -335,6 +402,8 @@ export declare class RAGKnowledgeGraphManager {
335
402
  version: number;
336
403
  description: string;
337
404
  }>;
405
+ semanticRollback?: boolean;
406
+ warning?: string;
338
407
  }>;
339
408
  }
340
409
  export {};