@aeriondyseti/vector-memory-mcp 2.4.4-dev.1 → 2.5.0-dev.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 +42 -1
- package/package.json +1 -1
- package/server/config/index.ts +11 -2
- package/server/core/connection.ts +110 -4
- package/server/core/consolidation.service.ts +652 -0
- package/server/core/conversation.repository.ts +115 -16
- package/server/core/conversation.service.ts +51 -51
- package/server/core/conversation.ts +12 -0
- package/server/core/memory.repository.ts +65 -16
- package/server/core/memory.service.ts +162 -44
- package/server/core/memory.ts +3 -0
- package/server/core/migrations.ts +197 -16
- package/server/core/parsers/claude-code.parser.ts +18 -4
- package/server/core/project.ts +25 -0
- package/server/core/sqlite-utils.ts +11 -3
- package/server/core/time-expr.ts +77 -0
- package/server/index.ts +92 -2
- package/server/transports/http/server.ts +81 -32
- package/server/transports/mcp/handlers.ts +71 -26
- package/server/transports/mcp/tools.ts +40 -4
package/README.md
CHANGED
|
@@ -117,13 +117,54 @@ Assistant: [calls search_memories with history_only: true, history_before/after
|
|
|
117
117
|
|
|
118
118
|
---
|
|
119
119
|
|
|
120
|
+
## Storage Model
|
|
121
|
+
|
|
122
|
+
All memories live in a single global database (`~/.vector-memory/memories.db`)
|
|
123
|
+
shared by every project. Each memory is tagged with the project (working
|
|
124
|
+
directory) it was stored from:
|
|
125
|
+
|
|
126
|
+
- **Searches default to all projects** — results carry their project path, and
|
|
127
|
+
hits from the current project rank slightly higher. Use `scope: "project"`
|
|
128
|
+
to restrict a search to the current repo.
|
|
129
|
+
- **Waypoints are per-project** and resolved automatically from the working
|
|
130
|
+
directory.
|
|
131
|
+
- A repo-local database is still available via `--db-file` or
|
|
132
|
+
`VECTOR_MEMORY_DB_PATH` (note: keep the db on local disk — WAL mode
|
|
133
|
+
misbehaves on network filesystems like NFS home directories).
|
|
134
|
+
|
|
135
|
+
### Migrating repo-local databases
|
|
136
|
+
|
|
137
|
+
Projects that used the old per-repo `.vector-memory/` layout can be imported
|
|
138
|
+
into the global store:
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
# Import the current repo's .vector-memory/memories.db
|
|
142
|
+
bunx @aeriondyseti/vector-memory-mcp consolidate
|
|
143
|
+
|
|
144
|
+
# Scan a whole directory tree and import every repo-local db found
|
|
145
|
+
bunx @aeriondyseti/vector-memory-mcp consolidate ~/Development --recursive
|
|
146
|
+
|
|
147
|
+
# Preview without writing (prints planned imports and ID re-keys)
|
|
148
|
+
bunx @aeriondyseti/vector-memory-mcp consolidate --dry-run
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Consolidation tags every imported memory with its repo's path, preserves
|
|
152
|
+
embeddings and usefulness stats, deduplicates by ID, re-keys waypoints to
|
|
153
|
+
their per-project IDs (remapping references), and backs up the global db
|
|
154
|
+
first. `--archive` renames the source `.vector-memory/` to
|
|
155
|
+
`.vector-memory.migrated/` after a successful import; `--force` skips the
|
|
156
|
+
live-server check.
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
120
160
|
## Configuration
|
|
121
161
|
|
|
122
162
|
CLI flags:
|
|
123
163
|
|
|
124
164
|
| Flag | Alias | Default | Description |
|
|
125
165
|
|------|-------|---------|-------------|
|
|
126
|
-
| `--db-file <path>` | `-d` |
|
|
166
|
+
| `--db-file <path>` | `-d` | `~/.vector-memory/memories.db` | Database location (global store) |
|
|
167
|
+
| `--project <path>` | | *(cwd)* | Project identity used to tag memories |
|
|
127
168
|
| `--port <number>` | `-p` | `3271` | HTTP server port |
|
|
128
169
|
| `--no-http` | | *(HTTP enabled)* | Disable HTTP/SSE transport |
|
|
129
170
|
| `--enable-history` | | *(disabled)* | Enable conversation history indexing |
|
package/package.json
CHANGED
package/server/config/index.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import arg from "arg";
|
|
2
2
|
import { homedir } from "os";
|
|
3
3
|
import { isAbsolute, join } from "path";
|
|
4
|
+
import { normalizeProject } from "../core/project";
|
|
4
5
|
import packageJson from "../../package.json" with { type: "json" };
|
|
5
6
|
|
|
6
7
|
export const VERSION = packageJson.version;
|
|
@@ -23,6 +24,8 @@ export interface ConversationHistoryConfig {
|
|
|
23
24
|
|
|
24
25
|
export interface Config {
|
|
25
26
|
dbPath: string;
|
|
27
|
+
/** Canonical project identifier — normalized absolute path of the project root. */
|
|
28
|
+
project: string;
|
|
26
29
|
embeddingModel: string;
|
|
27
30
|
embeddingDimension: number;
|
|
28
31
|
httpPort: number;
|
|
@@ -35,6 +38,7 @@ export interface Config {
|
|
|
35
38
|
|
|
36
39
|
export interface ConfigOverrides {
|
|
37
40
|
dbPath?: string;
|
|
41
|
+
project?: string;
|
|
38
42
|
httpPort?: number;
|
|
39
43
|
enableHttp?: boolean;
|
|
40
44
|
pluginMode?: boolean;
|
|
@@ -44,8 +48,10 @@ export interface ConfigOverrides {
|
|
|
44
48
|
historyWeight?: number;
|
|
45
49
|
}
|
|
46
50
|
|
|
47
|
-
// Defaults
|
|
48
|
-
|
|
51
|
+
// Defaults — single global store shared by all projects. Memories are tagged
|
|
52
|
+
// with the project (cwd) they came from. Use --db-file / VECTOR_MEMORY_DB_PATH
|
|
53
|
+
// for a repo-local database.
|
|
54
|
+
const DEFAULT_DB_PATH = join(homedir(), ".vector-memory", "memories.db");
|
|
49
55
|
const DEFAULT_EMBEDDING_MODEL = "Xenova/all-MiniLM-L6-v2";
|
|
50
56
|
const DEFAULT_EMBEDDING_DIMENSION = 384;
|
|
51
57
|
const DEFAULT_HTTP_PORT = 3271;
|
|
@@ -69,6 +75,7 @@ export function loadConfig(overrides: ConfigOverrides = {}): Config {
|
|
|
69
75
|
?? process.env.VECTOR_MEMORY_DB_PATH
|
|
70
76
|
?? DEFAULT_DB_PATH
|
|
71
77
|
),
|
|
78
|
+
project: normalizeProject(overrides.project ?? process.cwd()),
|
|
72
79
|
embeddingModel: DEFAULT_EMBEDDING_MODEL,
|
|
73
80
|
embeddingDimension: DEFAULT_EMBEDDING_DIMENSION,
|
|
74
81
|
httpPort:
|
|
@@ -99,6 +106,7 @@ export function parseCliArgs(argv: string[]): ConfigOverrides {
|
|
|
99
106
|
const args = arg(
|
|
100
107
|
{
|
|
101
108
|
"--db-file": String,
|
|
109
|
+
"--project": String,
|
|
102
110
|
"--port": Number,
|
|
103
111
|
"--no-http": Boolean,
|
|
104
112
|
"--plugin": Boolean,
|
|
@@ -115,6 +123,7 @@ export function parseCliArgs(argv: string[]): ConfigOverrides {
|
|
|
115
123
|
|
|
116
124
|
return {
|
|
117
125
|
dbPath: args["--db-file"],
|
|
126
|
+
project: args["--project"],
|
|
118
127
|
httpPort: args["--port"],
|
|
119
128
|
enableHttp: args["--no-http"] ? false : undefined,
|
|
120
129
|
pluginMode: args["--plugin"] ?? undefined,
|
|
@@ -1,24 +1,130 @@
|
|
|
1
1
|
import { Database } from "bun:sqlite";
|
|
2
|
-
import { existsSync, mkdirSync } from "fs";
|
|
2
|
+
import { closeSync, existsSync, mkdirSync, openSync, readFileSync, unlinkSync, writeSync } from "fs";
|
|
3
3
|
import { dirname } from "path";
|
|
4
4
|
import { removeVec0Tables, runMigrations } from "./migrations";
|
|
5
5
|
|
|
6
|
+
/** How long a starting process will wait for a concurrent vec0 cleanup to finish. */
|
|
7
|
+
const CLEANUP_LOCK_WAIT_MS = 15_000;
|
|
8
|
+
const CLEANUP_LOCK_POLL_MS = 250;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Check (read-only) whether the database still contains legacy vec0 virtual
|
|
12
|
+
* table entries. The destructive cleanup must only run when this is true —
|
|
13
|
+
* it rewrites sqlite_master via the sqlite3 CLI, which is unsafe while other
|
|
14
|
+
* connections hold the database open.
|
|
15
|
+
*/
|
|
16
|
+
function hasVec0Tables(dbPath: string): boolean {
|
|
17
|
+
let db: Database | null = null;
|
|
18
|
+
try {
|
|
19
|
+
db = new Database(dbPath, { readonly: true });
|
|
20
|
+
const row = db
|
|
21
|
+
.prepare("SELECT 1 FROM sqlite_master WHERE sql LIKE '%vec0%' LIMIT 1")
|
|
22
|
+
.get();
|
|
23
|
+
return row != null;
|
|
24
|
+
} catch {
|
|
25
|
+
// Unreadable / empty file — let the normal open path surface real errors
|
|
26
|
+
return false;
|
|
27
|
+
} finally {
|
|
28
|
+
db?.close();
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function lockPid(lockPath: string): number | null {
|
|
33
|
+
try {
|
|
34
|
+
const pid = parseInt(readFileSync(lockPath, "utf8"), 10);
|
|
35
|
+
return Number.isFinite(pid) ? pid : null;
|
|
36
|
+
} catch {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function isProcessAlive(pid: number): boolean {
|
|
42
|
+
try {
|
|
43
|
+
process.kill(pid, 0);
|
|
44
|
+
return true;
|
|
45
|
+
} catch {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Run the vec0 cleanup under an exclusive advisory lock so that N processes
|
|
52
|
+
* starting against the same database never run the sqlite_master rewrite
|
|
53
|
+
* concurrently. Losers wait for the winner, then re-probe (the winner's
|
|
54
|
+
* cleanup makes the probe false).
|
|
55
|
+
*/
|
|
56
|
+
function guardedVec0Cleanup(dbPath: string): void {
|
|
57
|
+
const lockPath = `${dbPath}.vec0-cleanup.lock`;
|
|
58
|
+
const deadline = Date.now() + CLEANUP_LOCK_WAIT_MS;
|
|
59
|
+
|
|
60
|
+
while (true) {
|
|
61
|
+
try {
|
|
62
|
+
const fd = openSync(lockPath, "wx");
|
|
63
|
+
try {
|
|
64
|
+
writeSync(fd, String(process.pid));
|
|
65
|
+
// Re-probe under the lock — another process may have cleaned up
|
|
66
|
+
// between our first probe and lock acquisition.
|
|
67
|
+
if (hasVec0Tables(dbPath)) {
|
|
68
|
+
removeVec0Tables(dbPath);
|
|
69
|
+
}
|
|
70
|
+
} finally {
|
|
71
|
+
closeSync(fd);
|
|
72
|
+
try {
|
|
73
|
+
unlinkSync(lockPath);
|
|
74
|
+
} catch {
|
|
75
|
+
// already gone — fine
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return;
|
|
79
|
+
} catch (err) {
|
|
80
|
+
if ((err as NodeJS.ErrnoException).code !== "EEXIST") throw err;
|
|
81
|
+
|
|
82
|
+
// Lock held — if the holder died, clear the stale lock and retry.
|
|
83
|
+
const pid = lockPid(lockPath);
|
|
84
|
+
if (pid !== null && !isProcessAlive(pid)) {
|
|
85
|
+
try {
|
|
86
|
+
unlinkSync(lockPath);
|
|
87
|
+
} catch {
|
|
88
|
+
// raced with another process clearing it — fine
|
|
89
|
+
}
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (Date.now() > deadline) {
|
|
94
|
+
throw new Error(
|
|
95
|
+
`vec0 cleanup lock held too long (${lockPath}) — remove it manually if no other server is starting`,
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
Bun.sleepSync(CLEANUP_LOCK_POLL_MS);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
6
103
|
/**
|
|
7
104
|
* Open (or create) a SQLite database at the given path
|
|
8
105
|
* and run schema migrations.
|
|
106
|
+
*
|
|
107
|
+
* Safe for multiple concurrent processes sharing one database file:
|
|
108
|
+
* the legacy vec0 cleanup only runs when a read-only probe finds vec0
|
|
109
|
+
* entries (never for healthy databases) and is serialized by an exclusive
|
|
110
|
+
* lock; migrations are user_version-gated inside an immediate transaction.
|
|
9
111
|
*/
|
|
10
112
|
export function connectToDatabase(dbPath: string): Database {
|
|
11
113
|
mkdirSync(dirname(dbPath), { recursive: true });
|
|
12
114
|
|
|
13
115
|
// Remove orphaned vec0 virtual table entries before bun:sqlite opens the
|
|
14
116
|
// database. bun:sqlite cannot modify sqlite_master, so this uses the
|
|
15
|
-
// sqlite3 CLI
|
|
16
|
-
if (existsSync(dbPath)) {
|
|
17
|
-
|
|
117
|
+
// sqlite3 CLI — gated behind a read-only probe and an exclusive lock.
|
|
118
|
+
if (existsSync(dbPath) && hasVec0Tables(dbPath)) {
|
|
119
|
+
guardedVec0Cleanup(dbPath);
|
|
18
120
|
}
|
|
19
121
|
|
|
20
122
|
const db = new Database(dbPath);
|
|
21
123
|
|
|
124
|
+
// busy_timeout FIRST: it is per-connection and needs no lock, while the
|
|
125
|
+
// WAL switch takes an exclusive lock — without the timeout, concurrent
|
|
126
|
+
// processes opening a fresh db race it and fail with SQLITE_BUSY.
|
|
127
|
+
db.exec("PRAGMA busy_timeout=5000");
|
|
22
128
|
// WAL mode for concurrent read performance
|
|
23
129
|
db.exec("PRAGMA journal_mode=WAL");
|
|
24
130
|
|