@lotargo/memory_plugin 1.1.6 → 1.1.8

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
@@ -1,243 +1,247 @@
1
- <div align="center">
2
-
3
- <img src="https://raw.githubusercontent.com/Lotargo/memory_pugin/main/assets/hero.jpg" alt="@lotargo/memory_plugin" width="480" style="max-width: 100%; border-radius: 12px; margin-bottom: 16px;">
4
-
5
- # @lotargo/memory_plugin
6
-
7
- [![npm version](https://img.shields.io/npm/v/@lotargo/memory_plugin)](https://www.npmjs.com/package/@lotargo/memory_plugin)
8
- [![license](https://img.shields.io/badge/license-MIT-blue)](./LICENSE)
9
-
10
- <br>
11
-
12
- **Zero-Docker Local Hybrid RAG Engine & Long-Term Memory for AI Coding Agents**
13
-
14
- Automatically remembers durable user facts, ingests complex document repositories, and performs high-precision hybrid retrieval across sessions and platforms.
15
-
16
- </div>
17
-
18
- ---
19
-
20
- ## Why @lotargo/memory_plugin?
21
-
22
- Standard AI coding assistants lose context as soon as a chat session closes or a conversation is reset. You end up having to repeatedly re-explain your preferences, architecture decisions, code style, or project conventions.
23
-
24
- `@lotargo/memory_plugin` gives your AI tools durable, 100% local long-term memory and document retrieval capabilities that persist across restarts and work seamlessly across all supported coding environments.
25
-
26
- ### Practical Use Cases
27
-
28
- #### 1. Software Development
29
-
30
- - **Architectural Decisions**: _"In this project, we use Fastify instead of Express and strict schema validation via Zod."_
31
- - **Coding Conventions**: _"Place all helper utilities inside `src/utils/` and always cover new functions with Vitest tests."_
32
- - **Environment Constraints**: _"Our target deployment environment is Node.js 20 on AWS Lambda."_
33
-
34
- #### 2. Everyday Chat & Interaction
35
-
36
- - **User Profile & Communication Tone**: _"My name is Alex. I prefer concise, direct answers without conversational filler."_
37
- - **Explanation Format**: _"Explain complex technical concepts using real-world code examples."_
38
- - **Goals & Context**: _"I am currently building a multi-platform memory plugin and RAG engine."_
39
-
40
- ---
41
-
42
- ## Dual-Layer Architecture
43
-
44
- 1. **Layer 1: Notebook Store (Durable Personal & Project Facts)**
45
- - Managed via `remember`, `recall`, and `forget`.
46
- - Stores user preferences, identity, project conventions, and system rules in human-readable Markdown format.
47
- - Guaranteed 100% precision instant retrieval as persistent context without threshold filtering or vector degradation.
48
-
49
- 2. **Layer 2: RAG Knowledge Base (Documentation & Repositories)**
50
- - Managed via `ingest_document`, `query_knowledge_base`, and `manage_knowledge_base`.
51
- - Ingests raw files, Markdown, HTML, and code repositories.
52
- - Dynamic 3-tier hierarchy chunking (Big / Medium / Small), SQLite FTS5 BM25 search, ONNX dense vector embeddings (`multilingual-e5-small`), Reciprocal Rank Fusion (RRF), and GraphRAG Lite code symbol extraction.
53
-
54
- ---
55
-
56
- ## Key Features
57
-
58
- - **Zero Heavy Infrastructure**: No Docker, no Python server, no binary C++ build dependencies (`node-gyp`). Uses Node.js native SQLite database.
59
- - **Bilingual & Multilingual Support**: SOTA semantic understanding across Russian, English, and technical code symbols.
60
- - **3-Tier Hierarchy Chunking**: Document (Big) -> Section (Medium) -> Micro-Chunk (Small).
61
- - **Hybrid RRF Fusion**: Combines SQLite FTS5 keyword precision with ONNX dense vector similarity.
62
- - **GraphRAG Lite**: Automatically links documents and extracted code symbols (classes, functions, types).
63
- - **Content-Addressable Storage (CAS)**: Local S3-style compressed blob store for raw original documents.
64
- - **Dual-Source Model Failover**: Automatic HuggingFace CDN model downloading with fallback to GitHub Repository Mirror in case of rate-limits or HF outages.
65
- - **Embedded Web Admin Dashboard**: Interactive single-page app served on `http://localhost:8765` with dynamic port resolution.
66
-
67
- ---
68
-
69
- ## Supported Platforms
70
-
71
- | Platform | Status | Mechanism |
72
- | ---------------------------- | --------- | --------------------------------------------------------------------------- |
73
- | **Antigravity / Gemini CLI** | Supported | MCP Server (`~/.gemini/config/mcp_config.json` & `.agents/mcp_config.json`) |
74
- | **OpenCode** | Native | Native plugin + MCP Server (`~/.config/opencode/opencode.json`) |
75
- | **Claude Code** | Supported | MCP Server (`~/.claude.json`) |
76
- | **Codex** | Supported | MCP Server (`~/.codex/config.toml`) |
77
-
78
- ---
79
-
80
- ## Minimum System Requirements
81
-
82
- - **Node.js**: version `18.0.0` or higher
83
- - **Package Manager**: `npm` / `npx` (included with Node.js)
84
- - **Supported Environment**: OpenCode, Antigravity / Gemini CLI, Claude Code, or Codex
85
-
86
- ---
87
-
88
- ## Installation & Setup
89
-
90
- Run this single command in your terminal to automatically configure memory for your AI tools:
91
-
92
- ### Install for All Detected Environments
93
-
94
- ```bash
95
- npx @lotargo/memory_plugin setup
96
- ```
97
-
98
- ### Targeted Installation for a Specific Platform
99
-
100
- - **Antigravity / Gemini CLI only**:
101
- ```bash
102
- npx @lotargo/memory_plugin setup --antigravity
103
- ```
104
- - **OpenCode only**:
105
- ```bash
106
- npx @lotargo/memory_plugin setup --opencode
107
- ```
108
- - **Claude Code only**:
109
- ```bash
110
- npx @lotargo/memory_plugin setup --claude
111
- ```
112
- - **Codex only**:
113
- ```bash
114
- npx @lotargo/memory_plugin setup --codex
115
- ```
116
-
117
- ### Launch Web Admin Dashboard
118
-
119
- ```bash
120
- npx @lotargo/memory_plugin admin
121
- ```
122
-
123
- ---
124
-
125
- ## Available MCP Tools
126
-
127
- ### 1. Memory Tools (Key-Value Notebook)
128
-
129
- | Tool | Description |
130
- | ---------- | ------------------------------------------------------------ |
131
- | `remember` | Save an important durable fact (`global` or `project` scope) |
132
- | `recall` | Display saved facts (`project`, `global`, or `all`) |
133
- | `forget` | Remove a saved fact by number or query |
134
-
135
- ### 2. Hybrid RAG Knowledge Base Tools
136
-
137
- | Tool | Description |
138
- | ----------------------- | ----------------------------------------------------------------------------------------------------------------------- |
139
- | `ingest_document` | Ingest local files, web URLs, or raw text into 3-tier hierarchy index with ONNX vector embeddings and symbol extraction |
140
- | `query_knowledge_base` | Perform hybrid RSF/RRF search (BM25 + Vector) to retrieve relevant candidate sections, code symbols, and context |
141
- | `manage_knowledge_base` | List documents, delete documents (purging CAS & SQLite), view database stats, or export/import portable snapshots |
142
-
143
- ---
144
-
145
- ## Interactive CLI & Engine Tuning
146
-
147
- Launch the interactive CLI terminal interface to configure fusion algorithm (RSF / RRF), alpha weights, embedding models, and reranker options:
148
-
149
- ```bash
150
- # From local repository folder:
151
- node mcp-server/index.js cli
152
- # or
153
- npx . cli
154
-
155
- # If installed / linked globally via npm link:
156
- memory_plugin cli
157
- # or
158
- memory-cli
159
- ```
160
-
161
- ### CLI Menu Structure
162
-
163
- The interactive menu exposes the same runtime knobs that `hybridQuery` honours, so you can change search behaviour without editing code or restarting the MCP server. Keys: **↑ / ↓** to navigate, **ENTER** to select, **BACKSPACE** to go back.
164
-
165
- | Block | Item | What it does |
166
- | ------------------- | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
167
- | **Engine Settings** | Fusion Algorithm | Switch between `rsf`, `rrf`, `semantic_only`, `lexical_only`. Affects every `query_knowledge_base` call until changed. |
168
- | | RSF Alpha Balance | Weight of semantic over lexical in `rsf` fusion (`α ∈ [0,1]`). Default `0.5`; best-in-class tuning is reported by the benchmark (see §Testing). |
169
- | | Embedding Model | Pick any HF `Xenova/...` ONNX model. First query after switching downloads weights and pays a one-time memory cost. |
170
- | | Reranker Model | Enable a cross-encoder (e.g. `bge-reranker-base`) on top of hybrid results, or disable for zero-latency fusion. |
171
- | **Notebook** | Layer 1 Facts | Browse / delete `global` and per-project `.md` fact stores. Hooks `remember` / `recall` / `forget`. |
172
- | **RAG Docs** | Layer 2 RAG Base | List ingested documents, inspect micro-chunk/section counts, and purge a document from FTS5 + vector index + CAS blobs. |
173
- | **Diagnostics** | Run Search Quality Benchmark | Executes the full benchmark suite in-process and prints the winner table (see §Testing). Surfaces `winner` + RRF-vs-RSF significance so you can decide before flipping the algorithm. |
174
- | | Run Search Verification Query | Issue a one-off `hybridQuery` against the live index to sanity-check retrieval with current settings. |
175
- | | Clear Benchmark Corpus Cache | Delete the cached GitHub README corpus used by the benchmark (frees disk for re-fetch from scratch). |
176
- | | Reset Config to Factory Defaults | Restore `config_defaults.json` to disk. |
177
-
178
- ### Why use the CLI?
179
-
180
- - **Iterative tuning**: change `alpha` and re-run the benchmark in <60 s to see if MRR/Recall move — no model reload, corpus is cached.
181
- - **Reproducible diagnostics**: the benchmark tabulates MRR/Recall/NDCG per mode and per category, so you can attribute a regression to a specific query or fusion knob.
182
- - **Zero config drift**: settings persist to `~/.config/opencode/memory/config.json` and are picked up by the MCP server on next `query_knowledge_base` / `hybridQuery` call.
183
-
184
- ---
185
-
186
- ## Testing & Benchmarking
187
-
188
- To run the automated test suite and benchmarks locally:
189
-
190
- ```bash
191
- cd mcp-server
192
-
193
- # Run unit and integration tests
194
- npm test
195
-
196
- # Run benchmark suite (ONNX embeddings + real technical corpus)
197
- npm run benchmark
198
- ```
199
-
200
- ### Benchmark Methodology
201
-
202
- The benchmark suite (`mcp-server/benchmarks/`) is the canonical way to evaluate retrieval quality changes. It runs three phases end-to-end:
203
-
204
- 1. **Dual-layer verification** asserts Notebook and RAG layers are isolated (zero crosstalk, 100% precision on `recall`).
205
- 2. **Ingestion benchmark** — fetches 27 real GitHub README documents, ingests them with ONNX `multilingual-e5-small` embeddings, and reports throughput, DB size, blob footprint, and heap delta.
206
- 3. **Search quality benchmark** — evaluates 21 challenging Russian→English / cross-lingual / code-keyword queries against 4 retrieval strategies with per-category breakdown, bootstrap 95% CIs, paired t-tests, and grid searches over RSF `α` and RRF `k`.
207
-
208
- **Strict matching policy**: a query is counted as hit iff the returned chunk belongs to one of the query's predefined `expectedDocIds` (derived from corpus source-id, e.g. `axios_readme`). This avoids false positives from substring overlap (e.g. query "next" against any doc mentioning "next").
209
-
210
- **Outputs**: In addition to the human-readable markdown report at `dev_docs/benchmark_results.md`, each run also writes a machine-readable JSON sidecar `dev_docs/benchmark_<timestamp>.json` and archives a copy under `dev_docs/benchmark_history/` for regression tracking across runs.
211
-
212
- > **Note**: The runner auto-respawns with `--expose-gc` so heap deltas can be measured post-GC. Pass `--no-respawn` to disable.
213
-
214
- ### Empirical Search Quality & Benchmark Summary
215
-
216
- The search quality of `@lotargo/memory_plugin` is evaluated across real-world multi-document technical repositories using Mean Reciprocal Rank (MRR@5), Recall@5, and Normalized Discounted Cumulative Gain (NDCG@5).
217
-
218
- #### Current Benchmark Performance (Instruction-Tuned Paradigm)
219
-
220
- _Model: Xenova/multilingual-e5-small over full 32-document technical corpus (21 queries)._
221
-
222
- | Retrieval Strategy | MRR@5 | Recall@5 | NDCG@5 |
223
- | ----------------------------- | :--------: | :--------: | :--------: |
224
- | BM25 Lexical Search Only | 0.5873 | 66.67% | 0.6077 |
225
- | Dense ONNX Vector Only | 0.8333 | 85.71% | 0.8396 |
226
- | Hybrid RRF ($k=60$) | 0.9048 | 90.48% | 0.9048 |
227
- | **Hybrid RSF ($\alpha=0.5$)** | **0.9206** | **95.24%** | **0.9286** |
228
-
229
- For complete benchmark methodology, baseline comparisons, mathematical formulations, and category breakdowns, refer to [BENCHMARKS.md](./BENCHMARKS.md).
230
-
231
- ---
232
-
233
- ## Storage & Privacy
234
-
235
- - **100% Local Storage**: All SQLite indexes, ONNX models, CAS blobs, and Markdown notebooks are stored locally under `~/.config/opencode/memory/`.
236
- - **Dual-Source Failover Model Fetching**: Primary model weights are fetched from HuggingFace CDN with automatic failover to GitHub Repository Mirror if rate-limited or offline.
237
- - **Zero External Telemetry**: No third-party network calls are required after initial model setup.
238
-
239
- ---
240
-
241
- ## License
242
-
243
- MIT
1
+ <div align="center">
2
+
3
+ <img src="./assets/hero.jpg" alt="@lotargo/memory_plugin" width="800" style="max-width: 100%; border-radius: 12px; margin-bottom: 16px;">
4
+
5
+ <br>
6
+
7
+ <img src="./assets/title.svg" alt="@lotargo/memory_plugin" width="520" style="max-width: 100%; margin-bottom: 12px;">
8
+
9
+ <br>
10
+
11
+ [![npm version](https://img.shields.io/npm/v/@lotargo/memory_plugin)](https://www.npmjs.com/package/@lotargo/memory_plugin)
12
+ [![license](https://img.shields.io/badge/license-MIT-blue)](./LICENSE)
13
+ [![node version](https://img.shields.io/badge/node-%3E%3D18.0.0-brightgreen)](https://nodejs.org)
14
+ [![mcp](https://img.shields.io/badge/MCP-Supported-8A2BE2)](https://modelcontextprotocol.io)
15
+ [![storage](https://img.shields.io/badge/Storage-100%25%20Local-success)](#storage--privacy)
16
+
17
+ <br>
18
+
19
+ **Zero-Docker Local Hybrid RAG Engine & Long-Term Memory for AI Coding Agents**
20
+
21
+ Automatically remembers durable user facts, ingests complex document repositories, and performs high-precision hybrid retrieval across sessions and platforms.
22
+
23
+ </div>
24
+
25
+ ---
26
+
27
+ ## Overview
28
+
29
+ Standard AI coding assistants lose context as soon as a chat session closes or a conversation is reset. You end up repeatedly re-explaining your preferences, architectural decisions, coding style, or project conventions.
30
+
31
+ `@lotargo/memory_plugin` gives your AI tools durable, 100% local long-term memory and document retrieval capabilities that persist across restarts and work seamlessly across all supported coding environments.
32
+
33
+ > **Project Scope & Runtime Notes**:
34
+ > `@lotargo/memory_plugin` is designed primarily as a practical plugin to expand capabilities and streamline daily interaction with AI coding tools. Benchmark scores in this repository represent internal synthetic evaluation runs and are not intended as generalized RAG benchmarks.
35
+ >
36
+ > **Hardware Acceleration**: GPU execution mode is an experimental feature and may vary in stability across different operating systems or models. For optimal stability and consistent runtime performance, using standard CPU mode with `multilingual-e5-small` or `multilingual-e5-base` is recommended.
37
+
38
+ ### Practical Use Cases
39
+
40
+ - **Architectural Decisions**: _"In this project, we use Fastify instead of Express and strict schema validation via Zod."_
41
+ - **Coding Conventions**: _"Place all helper utilities inside `src/utils/` and cover new functions with Vitest tests."_
42
+ - **Environment Constraints**: _"Our target deployment environment is Node.js 20 on AWS Lambda."_
43
+ - **User Profile & Tone**: _"My name is Alex. I prefer concise, direct answers without conversational filler."_
44
+
45
+ ---
46
+
47
+ ## Quick Start
48
+
49
+ ### Minimum System Requirements
50
+
51
+ - **Node.js**: `18.0.0` or higher
52
+ - **Package Manager**: `npm` / `npx` (included with Node.js)
53
+ - **Supported Environment**: OpenCode, Antigravity / Gemini CLI, Claude Code, or Codex
54
+
55
+ ### Installation
56
+
57
+ Run the unified setup command to configure all detected AI environments automatically:
58
+
59
+ ```bash
60
+ npx @lotargo/memory_plugin setup
61
+ ```
62
+
63
+ To target a specific environment:
64
+
65
+ ```bash
66
+ # Antigravity / Gemini CLI
67
+ npx @lotargo/memory_plugin setup --antigravity
68
+
69
+ # OpenCode
70
+ npx @lotargo/memory_plugin setup --opencode
71
+
72
+ # Claude Code
73
+ npx @lotargo/memory_plugin setup --claude
74
+
75
+ # Codex
76
+ npx @lotargo/memory_plugin setup --codex
77
+ ```
78
+
79
+ ---
80
+
81
+ ## Dual-Layer Architecture
82
+
83
+ 1. **Layer 1: Notebook Store (Durable Facts)**
84
+ - **Tools**: `remember`, `recall`, `forget`
85
+ - **Scope**: User preferences, identity, project conventions, system rules.
86
+ - **Storage**: Human-readable Markdown format (`global` and per-project stores).
87
+ - **Performance**: Guaranteed 100% precision instant lookup without vector degradation or threshold filtering.
88
+
89
+ 2. **Layer 2: RAG Knowledge Base (Technical Documents & Codebases)**
90
+ - **Tools**: `ingest_document`, `query_knowledge_base`, `manage_knowledge_base`
91
+ - **Capabilities**: Ingests raw text files, Markdown, HTML, and full code repositories.
92
+ - **Engine Components**: 3-tier hierarchy chunking (Big / Medium / Small), SQLite FTS5 BM25 search, ONNX dense vector embeddings (`multilingual-e5-small`), Reciprocal Rank Fusion (RRF / RSF), and GraphRAG Lite code symbol extraction.
93
+
94
+ ---
95
+
96
+ ## Key Features
97
+
98
+ - **Zero Heavy Infrastructure**: No Docker, no Python server, no C++ compilation (`node-gyp`). Uses Node.js native SQLite database.
99
+ - **Bilingual & Multilingual Support**: State-of-the-art semantic precision across Russian, English, and technical code symbols.
100
+ - **3-Tier Hierarchy Chunking**: Document (Big) -> Section (Medium) -> Micro-Chunk (Small).
101
+ - **Hybrid RRF/RSF Fusion**: Combines SQLite FTS5 keyword precision with ONNX dense vector similarity.
102
+ - **GraphRAG Lite**: Automatically links documents and extracted code symbols (classes, functions, types).
103
+ - **Content-Addressable Storage (CAS)**: Local S3-style compressed blob store for raw original documents.
104
+ - **Dual-Source Model Failover**: Automatic HuggingFace CDN model downloading with GitHub Repository Mirror fallback.
105
+ - **Interactive CLI Management**: Terminal GUI interface for runtime engine tuning, database maintenance, and diagnostics.
106
+
107
+ ---
108
+
109
+ ## Supported Platforms
110
+
111
+ | Platform | Status | Configuration Mechanism |
112
+ | :--------------------------- | :----------- | :-------------------------------------------------------------------------- |
113
+ | **Antigravity / Gemini CLI** | Supported | MCP Server (`~/.gemini/config/mcp_config.json` & `.agents/mcp_config.json`) |
114
+ | **OpenCode** | Native | Native plugin + MCP Server (`~/.config/opencode/opencode.json`) |
115
+ | **Claude Code** | Supported | MCP Server (`~/.claude.json`) |
116
+ | **Codex** | Supported | MCP Server (`~/.codex/config.toml`) |
117
+ | **Google Jules** | Experimental | MCP Server via global install (`npm install -g @lotargo/memory_plugin`) |
118
+
119
+ ### Google Jules Integration (Experimental)
120
+
121
+ The plugin has been verified inside the **Google Jules** cloud workspace environment.
122
+
123
+ - **Setup Method**: Global pre-installation:
124
+ ```bash
125
+ npm install -g @lotargo/memory_plugin
126
+ ```
127
+ - **Verification**: Google Jules automatically discovers the registered MCP server upon workspace initialization and seamlessly interacts with memory & RAG tools (`remember`, `recall`, `ingest_document`, `query_knowledge_base`).
128
+ - **Current Limitation**: All memory stores and vector indexes operate locally within the workspace environment. Cross-session cloud synchronization across different Jules runs is planned for upcoming releases.
129
+
130
+ ---
131
+
132
+ ## Available MCP Tools
133
+
134
+ ### 1. Memory Tools (Key-Value Notebook)
135
+
136
+ | Tool | Scope / Target | Description |
137
+ | :--------- | :---------------------------- | :------------------------------------------- |
138
+ | `remember` | `global` or `project` | Save an important durable fact or preference |
139
+ | `recall` | `project`, `global`, or `all` | Display saved facts |
140
+ | `forget` | Index ID or query | Remove a saved fact |
141
+
142
+ ### 2. Hybrid RAG Knowledge Base Tools
143
+
144
+ | Tool | Target | Description |
145
+ | :---------------------- | :------------------------------ | :--------------------------------------------------------------------------- |
146
+ | `ingest_document` | Local files, Web URLs, Raw text | Ingest into 3-tier index with ONNX vector embeddings & symbol extraction |
147
+ | `query_knowledge_base` | Text / Code query | Perform hybrid RSF/RRF search (BM25 + Vector) to retrieve candidate sections |
148
+ | `manage_knowledge_base` | Actions / Documents | List documents, delete entries, view DB stats, or export/import snapshots |
149
+
150
+ ---
151
+
152
+ ## Interactive CLI & Engine Tuning
153
+
154
+ Launch the interactive CLI terminal interface to manage engine settings, inspect databases, and tune retrieval parameters:
155
+
156
+ ```bash
157
+ # From local repository folder:
158
+ node mcp-server/index.js cli
159
+ # or
160
+ npx . cli
161
+
162
+ # If installed / linked globally:
163
+ memory_plugin cli
164
+ # or
165
+ memory-cli
166
+ ```
167
+
168
+ ### CLI Menu Overview
169
+
170
+ The interactive menu exposes runtime parameters that `hybridQuery` honors, allowing search behavior modifications without restarting the MCP server. Use **Up / Down** arrows to navigate, **ENTER** to select, and **BACKSPACE** to go back.
171
+
172
+ | Block | Menu Item | Functionality |
173
+ | :------------------ | :--------------------------- | :----------------------------------------------------------------------------- |
174
+ | **Engine Settings** | Fusion Algorithm | Switch between `rsf`, `rrf`, `semantic_only`, `lexical_only`. |
175
+ | | RSF Alpha Balance | Weight of semantic over lexical in `rsf` fusion ( [0,1]`). Default: `0.5`. |
176
+ | | Embedding Model | Select ONNX model (e.g. `Xenova/multilingual-e5-small`). |
177
+ | | Reranker Model | Enable Cross-Encoder reranking or disable for zero-latency fusion. |
178
+ | **Notebook** | Layer 1 Facts | Browse and manage `global` and per-project `.md` fact stores. |
179
+ | **RAG Docs** | Layer 2 RAG Base | List ingested documents, inspect chunk counts, and purge entries. |
180
+ | **Diagnostics** | Run Search Quality Benchmark | Execute in-process search evaluation across benchmark query set. |
181
+ | | Verification Query | Run a test `hybridQuery` against the active index. |
182
+ | | Clear Cache & Reset | Clear cached benchmark corpus or restore factory default config. |
183
+
184
+ Settings persist to `~/.config/opencode/memory/config.json` and are immediately loaded by the MCP server.
185
+
186
+ ---
187
+
188
+ ## Testing & Benchmarking
189
+
190
+ To run the automated test suite and benchmarks locally:
191
+
192
+ ```bash
193
+ cd mcp-server
194
+
195
+ # Run unit and integration tests
196
+ npm test
197
+
198
+ # Run search quality & ingestion benchmarks
199
+ npm run benchmark
200
+ ```
201
+
202
+ ### Benchmark Methodology
203
+
204
+ The benchmark suite (`mcp-server/benchmarks/`) evaluates retrieval quality across three phases:
205
+
206
+ 1. **Dual-Layer Verification**: Asserts Notebook and RAG layers are isolated (zero crosstalk, 100% precision on `recall`).
207
+ 2. **Ingestion Benchmark**: Ingests test documents with ONNX `multilingual-e5-small` embeddings, reporting throughput, DB size, CAS blob footprint, and heap delta.
208
+ 3. **Search Quality Benchmark**: Evaluates cross-lingual and code-keyword queries against 4 retrieval strategies with bootstrap 95% CIs, paired t-tests, and hyperparameter sweeps over RSF $\alpha$ and RRF $k$.
209
+
210
+ ### Search Quality Results (Smoke Test)
211
+
212
+ _Note: The following metrics reflect a quick smoke-test evaluation run performed on a reduced subset of documents to verify retrieval logic precision._
213
+
214
+ Evaluated across a reduced document subset using Mean Reciprocal Rank (MRR@5), Recall@5, and Normalized Discounted Cumulative Gain (NDCG@5):
215
+
216
+ | Retrieval Strategy | MRR@5 | Recall@5 | NDCG@5 |
217
+ | :---------------------------- | :--------: | :---------: | :--------: |
218
+ | BM25 Lexical Search Only | 0.6706 | 76.19% | 0.6934 |
219
+ | Dense ONNX Vector Only | 0.8135 | 100.00% | 0.8612 |
220
+ | Hybrid RRF ($k=10$) | 0.8810 | 95.24% | 0.8997 |
221
+ | **Hybrid RSF ($\alpha=0.5$)** | **0.9286** | **100.00%** | **0.9473** |
222
+
223
+ For complete methodology details, see [`docs/BENCHMARKS.md`](./docs/BENCHMARKS.md).
224
+
225
+ ---
226
+
227
+ ## Documentation & Reports
228
+
229
+ Detailed technical documentation and architectural specifications are available in the [`docs/`](./docs) directory:
230
+
231
+ - [**Verification Report (`MEMORY_PLUGIN_REPORT.md`)**](./docs/MEMORY_PLUGIN_REPORT.md): Summary report covering MCP Tool Registry, JSON-RPC integration testing, layer isolation validation, and search precision.
232
+ - [**Comprehensive Technical Report (`MEMORY_PLUGIN_COMPREHENSIVE_REPORT.md`)**](./docs/MEMORY_PLUGIN_COMPREHENSIVE_REPORT.md): Scientific analysis of system architecture, dual-layer model, hardware environment specifications, mathematical search formulations, and event-loop profiling.
233
+ - [**Benchmark Methodology & Guide (`BENCHMARKS.md`)**](./docs/BENCHMARKS.md): Guide to automated benchmark execution, hyperparameter sweeps (RSF $\alpha$, RRF $k$), search quality metrics, and performance tracking across releases.
234
+
235
+ ---
236
+
237
+ ## Storage & Privacy
238
+
239
+ - **100% Local Storage**: All SQLite indexes, ONNX models, CAS blobs, and Markdown notebooks are stored locally under `~/.config/opencode/memory/`.
240
+ - **Dual-Source Failover Model Fetching**: Primary model weights are fetched from HuggingFace CDN with automatic failover to GitHub Repository Mirror.
241
+ - **Zero External Telemetry**: No third-party network calls are required after initial model setup.
242
+
243
+ ---
244
+
245
+ ## License
246
+
247
+ [MIT](./LICENSE)
package/mcp-server/cli.js CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
  import readline from "readline";
3
3
  import { join } from "node:path";
4
4
  import { getConfig, updateConfig, resetConfig } from "./config/config_manager.js";
@@ -629,6 +629,24 @@ function waitForEnter() {
629
629
  }
630
630
 
631
631
  export async function runCli() {
632
+ const cliArgs = process.argv.slice(2);
633
+ if (cliArgs.includes("--enable-prompt") || cliArgs.includes("enable-prompt")) {
634
+ const { enableGlobalPrompt } = await import("./prompt_manager.js");
635
+ const results = await enableGlobalPrompt();
636
+ console.log("\n [OK] Global prompt enabled across client configurations:\n");
637
+ results.forEach((r) => console.log(` - ${r.name}: ${r.filePath} (${r.status})`));
638
+ console.log("");
639
+ return;
640
+ }
641
+ if (cliArgs.includes("--disable-prompt") || cliArgs.includes("disable-prompt")) {
642
+ const { disableGlobalPrompt } = await import("./prompt_manager.js");
643
+ const results = await disableGlobalPrompt();
644
+ console.log("\n [OK] Global prompt disabled across client configurations:\n");
645
+ results.forEach((r) => console.log(` - ${r.name}: ${r.filePath} (${r.status})`));
646
+ console.log("");
647
+ return;
648
+ }
649
+
632
650
  let running = true;
633
651
  let selectedIndex = 0;
634
652
 
@@ -731,6 +749,21 @@ export async function runCli() {
731
749
  },
732
750
  ],
733
751
  },
752
+ {
753
+ title: "Global Prompt & Integration Management",
754
+ items: [
755
+ {
756
+ label: "[PROMPT ENABLE] Enable Global Prompt (Antigravity / Codex / Claude)",
757
+ value: "enable_prompt",
758
+ info: "Inject memory instructions into ~/.gemini/config/AGENTS.md, ~/.codex/AGENTS.md, ~/.claude/CLAUDE.md",
759
+ },
760
+ {
761
+ label: "[PROMPT DISABLE] Disable Global Prompt",
762
+ value: "disable_prompt",
763
+ info: "Remove memory instructions from global AGENTS.md / CLAUDE.md files",
764
+ },
765
+ ],
766
+ },
734
767
  {
735
768
  title: "Diagnostics & System Actions",
736
769
  items: [
@@ -1590,6 +1623,24 @@ export async function runCli() {
1590
1623
  }
1591
1624
  break;
1592
1625
  }
1626
+ case "enable_prompt": {
1627
+ const { enableGlobalPrompt } = await import("./prompt_manager.js");
1628
+ const results = await enableGlobalPrompt();
1629
+ console.clear();
1630
+ console.log("\n [OK] Global prompt enabled across client configurations:\n");
1631
+ results.forEach((r) => console.log(` - ${r.name}: ${r.filePath} (${r.status})`));
1632
+ await waitForEnter();
1633
+ break;
1634
+ }
1635
+ case "disable_prompt": {
1636
+ const { disableGlobalPrompt } = await import("./prompt_manager.js");
1637
+ const results = await disableGlobalPrompt();
1638
+ console.clear();
1639
+ console.log("\n [OK] Global prompt disabled across client configurations:\n");
1640
+ results.forEach((r) => console.log(` - ${r.name}: ${r.filePath} (${r.status})`));
1641
+ await waitForEnter();
1642
+ break;
1643
+ }
1593
1644
  case "reset": {
1594
1645
  resetConfig();
1595
1646
  console.clear();
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
3
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
4
  import * as z from "zod/v4";
@@ -0,0 +1,116 @@
1
+ import { readFile, writeFile, mkdir, unlink } from "fs/promises";
2
+ import { existsSync } from "fs";
3
+ import { join } from "path";
4
+ import { homedir } from "os";
5
+
6
+ const START_MARKER = "<!-- START MEMORY AGENT PROMPT -->";
7
+ const END_MARKER = "<!-- END MEMORY AGENT PROMPT -->";
8
+
9
+ export const PROMPT_BLOCK = `${START_MARKER}
10
+ [SYSTEM INSTRUCTION: PERSONAL CONTEXT & MEMORY AGENT]
11
+ When starting a session or when personal/project context is relevant, use \`recall\` from \`memory-agent\` to load saved facts, user preferences, and project guidelines. Use \`remember\` to save any durable high-signal facts provided by the user.
12
+ ${END_MARKER}`;
13
+
14
+ export function getGlobalPromptTargets() {
15
+ const home = homedir();
16
+ return [
17
+ {
18
+ name: "Antigravity",
19
+ filePath: join(home, ".gemini", "config", "AGENTS.md"),
20
+ },
21
+ {
22
+ name: "Codex",
23
+ filePath: join(home, ".codex", "AGENTS.md"),
24
+ },
25
+ {
26
+ name: "Claude Code",
27
+ filePath: join(home, ".claude", "CLAUDE.md"),
28
+ },
29
+ ];
30
+ }
31
+
32
+ function stripPromptBlock(content) {
33
+ const startIndex = content.indexOf(START_MARKER);
34
+ const endIndex = content.indexOf(END_MARKER);
35
+
36
+ if (startIndex !== -1 && endIndex !== -1 && endIndex > startIndex) {
37
+ const before = content.substring(0, startIndex);
38
+ const after = content.substring(endIndex + END_MARKER.length);
39
+ return (before + after).replace(/\n{3,}/g, "\n\n").trim();
40
+ }
41
+ return content.trim();
42
+ }
43
+
44
+ export async function enableGlobalPrompt() {
45
+ const targets = getGlobalPromptTargets();
46
+ const results = [];
47
+
48
+ for (const target of targets) {
49
+ try {
50
+ const parentDir = join(target.filePath, "..");
51
+ if (!existsSync(parentDir)) {
52
+ await mkdir(parentDir, { recursive: true });
53
+ }
54
+
55
+ let existing = "";
56
+ if (existsSync(target.filePath)) {
57
+ existing = await readFile(target.filePath, "utf-8");
58
+ }
59
+
60
+ const clean = stripPromptBlock(existing);
61
+ const updated = clean ? `${clean}\n\n${PROMPT_BLOCK}\n` : `${PROMPT_BLOCK}\n`;
62
+
63
+ await writeFile(target.filePath, updated, "utf-8");
64
+ results.push({ name: target.name, filePath: target.filePath, status: "enabled" });
65
+ } catch (err) {
66
+ results.push({ name: target.name, filePath: target.filePath, status: "failed", error: err.message });
67
+ }
68
+ }
69
+
70
+ return results;
71
+ }
72
+
73
+ export async function disableGlobalPrompt() {
74
+ const targets = getGlobalPromptTargets();
75
+ const results = [];
76
+
77
+ for (const target of targets) {
78
+ try {
79
+ if (!existsSync(target.filePath)) {
80
+ results.push({ name: target.name, filePath: target.filePath, status: "skipped" });
81
+ continue;
82
+ }
83
+
84
+ const existing = await readFile(target.filePath, "utf-8");
85
+ const clean = stripPromptBlock(existing);
86
+
87
+ if (clean.length === 0) {
88
+ await unlink(target.filePath);
89
+ results.push({ name: target.name, filePath: target.filePath, status: "removed_file" });
90
+ } else {
91
+ await writeFile(target.filePath, clean + "\n", "utf-8");
92
+ results.push({ name: target.name, filePath: target.filePath, status: "disabled" });
93
+ }
94
+ } catch (err) {
95
+ results.push({ name: target.name, filePath: target.filePath, status: "failed", error: err.message });
96
+ }
97
+ }
98
+
99
+ return results;
100
+ }
101
+
102
+ export async function getGlobalPromptStatus() {
103
+ const targets = getGlobalPromptTargets();
104
+ const status = [];
105
+
106
+ for (const target of targets) {
107
+ let enabled = false;
108
+ if (existsSync(target.filePath)) {
109
+ const content = await readFile(target.filePath, "utf-8");
110
+ enabled = content.includes(START_MARKER) && content.includes(END_MARKER);
111
+ }
112
+ status.push({ name: target.name, filePath: target.filePath, enabled });
113
+ }
114
+
115
+ return status;
116
+ }
@@ -144,5 +144,19 @@ export async function runSetup() {
144
144
  }
145
145
  }
146
146
 
147
+ // 5. Global Prompt Instructions (Antigravity, Codex, Claude Code)
148
+ try {
149
+ const { enableGlobalPrompt } = await import("./prompt_manager.js");
150
+ const promptResults = await enableGlobalPrompt();
151
+ promptResults.forEach((r) => {
152
+ if (r.status === "enabled") {
153
+ console.log(` [OK] ${r.name}: enabled global prompt instruction in ${r.filePath}`);
154
+ }
155
+ });
156
+ } catch (err) {
157
+ console.log(" [SKIP] Global prompt setup skipped:", err.message);
158
+ }
159
+
147
160
  console.log(`\nSetup complete. Configured ${configuredCount} environment(s).\n`);
148
161
  }
162
+
@@ -153,7 +153,12 @@ export const MemoryPlugin = async ({ directory, worktree, client }) => {
153
153
  },
154
154
  },
155
155
  "remember": {
156
- description: "Save an important, durable fact to memory. Only use for high-signal information (name, goals, constraints, tech preferences, project conventions). Translate the fact into English before saving. scope: 'project' (default) or 'global'",
156
+ description:
157
+ "Save an important, durable fact to memory. Only use for high-signal information " +
158
+ "(name, goals, constraints, tech preferences, project conventions). " +
159
+ "Optionally link the fact to a Knowledge Base document or exact line range (docId, startLine, endLine). " +
160
+ "Translate the fact into English and keep it concise. " +
161
+ "scope: 'project' (default) or 'global'",
157
162
  args: {
158
163
  fact: { type: "string", description: "The fact to remember, written in English" },
159
164
  scope: {
@@ -161,25 +166,52 @@ export const MemoryPlugin = async ({ directory, worktree, client }) => {
161
166
  description: "'project' (default) or 'global'",
162
167
  default: "project",
163
168
  },
169
+ docId: { type: "string", description: "Optional document ID, title, or path to link this fact to" },
170
+ startLine: { type: "number", description: "Optional starting line number in target document" },
171
+ endLine: { type: "number", description: "Optional ending line number in target document" },
172
+ relationType: {
173
+ type: "string",
174
+ description: "Relation type (e.g. 'RULES_FOR', 'IMPLEMENTS', 'REFERENCES')",
175
+ default: "LINKS_TO",
176
+ },
164
177
  },
165
- async execute({ fact, scope }, { worktree, directory }) {
178
+ async execute({ fact, scope, docId, startLine, endLine, relationType }, { worktree, directory }) {
166
179
  const key = scopeKey(scope || "project", worktree, directory);
167
180
  const entries = await readMemory(key);
168
181
  const factNormalized = fact.toLowerCase().trim();
169
- if (entries.some((e) => {
182
+ if (!entries.some((e) => {
170
183
  const idx = e.indexOf("] ");
171
184
  return idx !== -1 && e.slice(idx + 2).toLowerCase().trim() === factNormalized;
172
185
  })) {
173
- return "Already saved";
186
+ entries.push(`- [${today()}] ${fact}`);
187
+ await writeMemory(key, entries);
174
188
  }
175
- entries.push(`- [${today()}] ${fact}`);
176
- await writeMemory(key, entries);
177
- await notify(client, "Memory updated");
178
- return "Memory updated";
189
+
190
+ let linkInfo = "";
191
+ if (docId) {
192
+ try {
193
+ const { linkFactToDocument } = await import("../mcp-server/graph/knowledge_linker.js");
194
+ const linkRes = linkFactToDocument({
195
+ factKey: key,
196
+ factText: fact,
197
+ docId,
198
+ startLine,
199
+ endLine,
200
+ relationType: relationType || "LINKS_TO",
201
+ });
202
+ const linesStr = startLine ? `:L${startLine}${endLine ? `-${endLine}` : ""}` : "";
203
+ linkInfo = ` [Linked to Doc: "${linkRes.docTitle}"${linesStr}]`;
204
+ } catch (err) {
205
+ linkInfo = ` (Note: Fact saved, but document link failed: ${err.message})`;
206
+ }
207
+ }
208
+
209
+ await notify(client, "Memory updated" + linkInfo);
210
+ return "Memory updated" + linkInfo;
179
211
  },
180
212
  },
181
213
  "recall": {
182
- description: "Показать запомненные факты (scope: project | global | all, по умолчанию все)",
214
+ description: "Show saved facts with any Agent-linked Knowledge Base documents/lines. scope: 'project', 'global', or 'all' (default)",
183
215
  args: {
184
216
  scope: {
185
217
  type: "string",
@@ -190,11 +222,37 @@ export const MemoryPlugin = async ({ directory, worktree, client }) => {
190
222
  async execute({ scope }, { worktree, directory }) {
191
223
  const project = projectName(worktree, directory);
192
224
  const results = [];
225
+
226
+ let getLinksForFact;
227
+ try {
228
+ const linker = await import("../mcp-server/graph/knowledge_linker.js");
229
+ getLinksForFact = linker.getLinksForFact;
230
+ } catch (e) {}
231
+
232
+ const formatFactWithLinks = (factText, key) => {
233
+ let line = factText;
234
+ if (getLinksForFact) {
235
+ try {
236
+ const links = getLinksForFact(key, factText);
237
+ if (links && links.length > 0) {
238
+ const docStr = links
239
+ .map((l) => {
240
+ const range = l.start_line ? `:L${l.start_line}${l.end_line ? `-${l.end_line}` : ""}` : "";
241
+ return `${l.doc_title || l.doc_path}${range}`;
242
+ })
243
+ .join(", ");
244
+ line += ` 🔗 [Linked Docs: ${docStr}]`;
245
+ }
246
+ } catch (e) {}
247
+ }
248
+ return line;
249
+ };
250
+
193
251
  if (scope !== "project") {
194
252
  const global = await readMemoryRaw(GLOBAL_KEY);
195
253
  if (global.length) {
196
254
  results.push("--- Global ---");
197
- global.forEach((e, i) => results.push(`${i + 1}. ${e}`));
255
+ global.forEach((e, i) => results.push(`${i + 1}. ${formatFactWithLinks(e, GLOBAL_KEY)}`));
198
256
  }
199
257
  }
200
258
  if (scope !== "global") {
@@ -202,7 +260,7 @@ export const MemoryPlugin = async ({ directory, worktree, client }) => {
202
260
  if (local.length) {
203
261
  if (results.length) results.push("");
204
262
  results.push(`--- ${project} ---`);
205
- local.forEach((e, i) => results.push(`${i + 1}. ${e}`));
263
+ local.forEach((e, i) => results.push(`${i + 1}. ${formatFactWithLinks(e, project)}`));
206
264
  }
207
265
  }
208
266
  return results.length ? results.join("\n") : "Memory is empty.";
@@ -237,6 +295,232 @@ export const MemoryPlugin = async ({ directory, worktree, client }) => {
237
295
  return result;
238
296
  },
239
297
  },
298
+ "link_knowledge": {
299
+ description:
300
+ "Explicitly link a Notebook memory fact to a Knowledge Base document, section, or line range. " +
301
+ "Creates Agent-driven Graph Edges connecting memory to RAG documents.",
302
+ args: {
303
+ action: {
304
+ type: "string",
305
+ description: "Action type: 'link' (default), 'list_links', 'get_doc_links'",
306
+ default: "link",
307
+ },
308
+ factText: { type: "string", description: "Memory fact text or keyword" },
309
+ docId: { type: "string", description: "Document ID, title, or file path" },
310
+ scope: { type: "string", description: "'project' (default) or 'global'", default: "project" },
311
+ startLine: { type: "number", description: "Starting line number in target document" },
312
+ endLine: { type: "number", description: "Ending line number in target document" },
313
+ relationType: {
314
+ type: "string",
315
+ description: "Relation type (e.g. 'RULES_FOR', 'IMPLEMENTS', 'EXPLAINS')",
316
+ default: "LINKS_TO",
317
+ },
318
+ },
319
+ async execute({ action, factText, docId, scope, startLine, endLine, relationType }, { worktree, directory }) {
320
+ const { linkFactToDocument, getLinksForDoc, listAllLinks } = await import("../mcp-server/graph/knowledge_linker.js");
321
+ const key = scopeKey(scope || "project", worktree, directory);
322
+ const act = action || "link";
323
+
324
+ if (act === "link") {
325
+ if (!factText || !docId) {
326
+ throw new Error("factText and docId are required parameters for link action");
327
+ }
328
+ const res = linkFactToDocument({
329
+ factKey: key,
330
+ factText,
331
+ docId,
332
+ startLine,
333
+ endLine,
334
+ relationType: relationType || "LINKS_TO",
335
+ });
336
+ return JSON.stringify(res, null, 2);
337
+ }
338
+
339
+ if (act === "get_doc_links") {
340
+ if (!docId) throw new Error("docId parameter is required for get_doc_links action");
341
+ const links = getLinksForDoc(docId);
342
+ return JSON.stringify(links, null, 2);
343
+ }
344
+
345
+ if (act === "list_links") {
346
+ const links = listAllLinks(key);
347
+ return JSON.stringify(links, null, 2);
348
+ }
349
+
350
+ throw new Error(`Unknown action: ${act}`);
351
+ },
352
+ },
353
+ "ingest_document": {
354
+ description:
355
+ "Ingest a document into the RAG knowledge base. " +
356
+ "Accepts local file paths, web URLs, or raw Markdown/text content. " +
357
+ "Processes document through 3-tier hierarchy chunking (Big/Medium/Small), " +
358
+ "computes dense vectors, and extracts GraphRAG code symbols.",
359
+ args: {
360
+ content: { type: "string", description: "Raw text content, file path, or web URL" },
361
+ type: { type: "string", description: "Input content type: 'text', 'file', 'url'", default: "text" },
362
+ title: { type: "string", description: "Document title" },
363
+ path: { type: "string", description: "Original document file path" },
364
+ generateEmbeddings: { type: "boolean", description: "Compute dense vector embeddings", default: true },
365
+ },
366
+ async execute({ content, type, title, path, generateEmbeddings }) {
367
+ const { ingestDocument } = await import("../mcp-server/ingest/pipeline.js");
368
+ const result = await ingestDocument({
369
+ content,
370
+ type: type || "text",
371
+ title: title || null,
372
+ path: path || null,
373
+ generateEmbeddings: generateEmbeddings !== false,
374
+ });
375
+ return JSON.stringify(
376
+ {
377
+ status: "success",
378
+ docId: result.docId,
379
+ title: result.title,
380
+ sectionsCount: result.sectionsCount,
381
+ microChunksCount: result.microChunksCount,
382
+ deduplicated: result.deduplicated,
383
+ },
384
+ null,
385
+ 2
386
+ );
387
+ },
388
+ },
389
+ "query_knowledge_base": {
390
+ description:
391
+ "Perform hybrid search (RSF/RRF BM25 full-text + dense vector similarity) across the RAG knowledge base. " +
392
+ "Returns top-ranked candidate document sections with breadcrumbs, GraphRAG defined code symbols, and relevance scores.",
393
+ args: {
394
+ query: { type: "string", description: "Search query in natural language or symbol name" },
395
+ limit: { type: "number", description: "Maximum number of sections to return", default: 5 },
396
+ instruction: {
397
+ type: "string",
398
+ description: "Optional task-specific retrieval instruction shaping embedding focus",
399
+ },
400
+ generateEmbeddings: { type: "boolean", description: "Use vector search alongside BM25", default: true },
401
+ },
402
+ async execute({ query, limit, instruction, generateEmbeddings }) {
403
+ const { hybridQuery } = await import("../mcp-server/retrieval/retriever.js");
404
+ const { getConfig } = await import("../mcp-server/config/config_manager.js");
405
+ const activeConfig = getConfig();
406
+
407
+ const results = await hybridQuery({
408
+ query,
409
+ limit: limit || 5,
410
+ generateEmbeddings: generateEmbeddings !== false,
411
+ instruction: instruction || null,
412
+ });
413
+
414
+ if (!results || results.length === 0) {
415
+ return `[Active Model: ${activeConfig.embeddingModel}]\nNo matching knowledge found for query.`;
416
+ }
417
+
418
+ const headerNote = `[Active Model: ${activeConfig.embeddingModel} | Fusion: ${activeConfig.fusionAlgorithm.toUpperCase()}]\n\n`;
419
+
420
+ const formatted = results
421
+ .map((r, i) => {
422
+ let header = `### [${i + 1}] ${r.doc_title || "Untitled"}`;
423
+ if (r.heading) header += ` > ${r.heading}`;
424
+ if (r.breadcrumbs) header += ` (${r.breadcrumbs})`;
425
+ let body = `Score: ${(r.score || 0).toFixed(4)}\n`;
426
+ if (r.defined_symbols && r.defined_symbols.length > 0) {
427
+ body += `Defined Symbols: ${r.defined_symbols.join(", ")}\n`;
428
+ }
429
+ body += `\n${r.snippet || r.full_section_content || ""}`;
430
+ return `${header}\n${body}`;
431
+ })
432
+ .join("\n\n---\n\n");
433
+
434
+ return headerNote + formatted;
435
+ },
436
+ },
437
+ "manage_knowledge_base": {
438
+ description:
439
+ "Manage the RAG knowledge base: inspect stats, list documents, read full raw document, delete documents, or export/import snapshots.",
440
+ args: {
441
+ action: {
442
+ type: "string",
443
+ description: "Management action: 'stats', 'list', 'read_document', 'delete', 'export_snapshot', 'import_snapshot'",
444
+ },
445
+ docId: { type: "string", description: "Document ID, title, or path (required for read_document and delete)" },
446
+ snapshotPath: { type: "string", description: "File path for snapshot export/import" },
447
+ },
448
+ async execute({ action, docId, snapshotPath }) {
449
+ const { getDatabase } = await import("../mcp-server/db/database.js");
450
+ const db = getDatabase();
451
+
452
+ if (action === "stats") {
453
+ const docCount = db.prepare("SELECT COUNT(*) as cnt FROM documents").get().cnt;
454
+ const secCount = db.prepare("SELECT COUNT(*) as cnt FROM sections").get().cnt;
455
+ const chunkCount = db.prepare("SELECT COUNT(*) as cnt FROM micro_chunks").get().cnt;
456
+ const edgeCount = db.prepare("SELECT COUNT(*) as cnt FROM graph_edges").get().cnt;
457
+ return JSON.stringify(
458
+ {
459
+ documents: docCount,
460
+ sections: secCount,
461
+ micro_chunks: chunkCount,
462
+ graph_edges: edgeCount,
463
+ },
464
+ null,
465
+ 2
466
+ );
467
+ }
468
+
469
+ if (action === "list") {
470
+ const docs = db
471
+ .prepare("SELECT id, title, path, blob_hash, created_at FROM documents ORDER BY created_at DESC")
472
+ .all();
473
+ return JSON.stringify(docs, null, 2);
474
+ }
475
+
476
+ if (action === "read_document") {
477
+ if (!docId) throw new Error("docId parameter is required for read_document action");
478
+ const doc = db
479
+ .prepare("SELECT id, title, path, blob_hash, created_at FROM documents WHERE id = ? OR path = ? OR title = ?")
480
+ .get(docId, docId, docId);
481
+ if (!doc) {
482
+ throw new Error(`Document not found in knowledge base for docId: ${docId}`);
483
+ }
484
+ const { readBlob } = await import("../mcp-server/storage/blob_store.js");
485
+ const rawContent = await readBlob(doc.blob_hash);
486
+ return JSON.stringify(
487
+ {
488
+ id: doc.id,
489
+ title: doc.title,
490
+ path: doc.path,
491
+ created_at: doc.created_at,
492
+ content: rawContent,
493
+ },
494
+ null,
495
+ 2
496
+ );
497
+ }
498
+
499
+ if (action === "delete") {
500
+ if (!docId) throw new Error("docId parameter is required for delete action");
501
+ const { deleteDocument } = await import("../mcp-server/ingest/pipeline.js");
502
+ const result = await deleteDocument(docId, db);
503
+ return JSON.stringify(result, null, 2);
504
+ }
505
+
506
+ if (action === "export_snapshot") {
507
+ const { exportSnapshot } = await import("../mcp-server/admin/snapshot.js");
508
+ const result = await exportSnapshot({ customDb: db, outputPath: snapshotPath || null });
509
+ return snapshotPath
510
+ ? `Snapshot exported successfully to ${snapshotPath}`
511
+ : JSON.stringify(result, null, 2);
512
+ }
513
+
514
+ if (action === "import_snapshot") {
515
+ if (!snapshotPath) throw new Error("snapshotPath parameter is required for import_snapshot action");
516
+ const { importSnapshot } = await import("../mcp-server/admin/snapshot.js");
517
+ const result = await importSnapshot({ customDb: db, snapshotPathOrData: snapshotPath });
518
+ return JSON.stringify(result, null, 2);
519
+ }
520
+
521
+ throw new Error(`Unknown action: ${action}`);
522
+ },
523
+ },
240
524
  },
241
525
  };
242
526
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lotargo/memory_plugin",
3
- "version": "1.1.6",
3
+ "version": "1.1.8",
4
4
  "description": "Persistent memory agent for coding AI tools — remembers user preferences and project context across sessions. Works with Antigravity, OpenCode, Claude Code, and Codex.",
5
5
  "type": "module",
6
6
  "main": "opencode-plugin/index.js",
@@ -28,6 +28,7 @@
28
28
  "mcp-server/memory.js",
29
29
  "mcp-server/setup.js",
30
30
  "mcp-server/preinstall.js",
31
+ "mcp-server/prompt_manager.js",
31
32
  "skills"
32
33
  ],
33
34
  "keywords": [