@wrongstack/vector-memory 0.308.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ECOSTACK TECHNOLOGY OÜ
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,189 @@
1
+ # @wrongstack/vector-memory
2
+
3
+ An **additional vector-search memory** that sits alongside the existing
4
+ SAGE lexical memory system. Embeddings are computed locally via
5
+ [@huggingface/transformers](https://github.com/huggingface/transformers.js)
6
+ using the `Xenova/all-MiniLM-L6-v2` ONNX model (384 dimensions, ~25 MB
7
+ quantized). No project text leaves the machine.
8
+
9
+ The store is deliberately separate from SAGE's SQLite database — its own
10
+ file under `.wrongstack/vector-memory/vector-memory.db` — so the two
11
+ stores cannot contend on the same file lock. A built-in `syncFromSage`
12
+ bridge indexes active SAGE memories into the vector store, giving
13
+ semantic search over your existing knowledge.
14
+
15
+ ## Installation
16
+
17
+ `@huggingface/transformers` is listed as an **optional** dependency so
18
+ the package installs and typechecks even without it. In this monorepo
19
+ it's installed by default; in downstream packages, ensure it's present:
20
+
21
+ ```sh
22
+ pnpm add @huggingface/transformers
23
+ ```
24
+
25
+ The package itself is a workspace member; no extra install step is
26
+ needed inside the monorepo.
27
+
28
+ ## Quick start
29
+
30
+ ```ts
31
+ import {
32
+ TransformersEmbeddingProvider,
33
+ VectorMemoryStore,
34
+ createVectorMemoryTools,
35
+ } from '@wrongstack/vector-memory';
36
+ import path from 'node:path';
37
+
38
+ const provider = new TransformersEmbeddingProvider({
39
+ // Optional overrides (these are the defaults):
40
+ modelId: 'Xenova/all-MiniLM-L6-v2',
41
+ cacheDir: path.join(projectRoot, '.wrongstack/vector-memory/models'),
42
+ dtype: 'q8',
43
+ device: 'cpu',
44
+ allowRemoteModels: true, // set false to require a pre-cached model
45
+ });
46
+
47
+ const store = new VectorMemoryStore({ provider, projectRoot });
48
+
49
+ await store.remember({ text: 'pnpm is the package manager', tags: ['build'] });
50
+
51
+ const hits = await store.search('how do we install dependencies', { limit: 5 });
52
+ // → hits[i].entry.text, hits[i].score (cosine similarity in [0, 1])
53
+
54
+ store.close();
55
+ ```
56
+
57
+ ## Tools
58
+
59
+ `createVectorMemoryTools(store)` returns four `@wrongstack/core` `Tool`
60
+ definitions that surface the store to agents:
61
+
62
+ | Tool | Permission | Mutating | Purpose |
63
+ |------|------------|----------|---------|
64
+ | `vector_memory_remember` | `confirm` | yes | Store text + embed it |
65
+ | `vector_memory_search` | `auto` | no | Semantic top-k search |
66
+ | `vector_memory_stats` | `auto` | no | Entry/vector/provider counts |
67
+ | `vector_memory_forget` | `confirm` | yes | Hard-delete an entry by id |
68
+
69
+ Wire them alongside `createSageTools` if you want agents to have both
70
+ the lexical and the semantic memory surface.
71
+
72
+ ## Graceful fallback
73
+
74
+ `TransformersEmbeddingProvider` is lazy — nothing is imported until the
75
+ first `embed()` call. If `@huggingface/transformers` is not installed
76
+ or the model fails to load (offline, corrupt cache, etc.):
77
+
78
+ - `isAvailable()` returns `false`.
79
+ - `embed()` throws `VectorMemoryProviderUnavailableError` (or a wrapped
80
+ underlying error from the pipeline).
81
+ - The store's `remember()` **swallows** the embedding failure and
82
+ persists the entry without a vector. Search simply skips entries
83
+ without a matching vector row. Writes never disappear.
84
+ - `search()` returns `[]` when embedding fails, so callers can fall
85
+ back to lexical search at a higher layer.
86
+
87
+ For an offline-only store, wire the sage `HashingEmbeddingProvider`
88
+ instead — it's deterministic, zero-dependency, and satisfies the same
89
+ `EmbeddingProvider` contract:
90
+
91
+ ```ts
92
+ import { HashingEmbeddingProvider, type EmbeddingProvider } from '@wrongstack/sage';
93
+
94
+ const provider: EmbeddingProvider = new HashingEmbeddingProvider({ dimensions: 384 });
95
+ ```
96
+
97
+ The store accepts any `EmbeddingProvider`; you can mix and match
98
+ between providers across instances or for testing.
99
+
100
+ ## Syncing with SAGE
101
+
102
+ ```ts
103
+ const sagePort = /* a MemoryPort or any object satisfying SageSyncSource */;
104
+ const report = await store.syncFromSage({
105
+ listActiveMemories: async ({ limit }) => {
106
+ const page = await sagePort.listSagePage({ statuses: ['active'], limit });
107
+ return (page.memories ?? []).map((m) => ({
108
+ id: m.id,
109
+ text: m.text,
110
+ tags: m.tags,
111
+ }));
112
+ },
113
+ });
114
+ // { scanned, indexed, skipped, failed, errors }
115
+ ```
116
+
117
+ The bridge deduplicates on `contentHash` (SHA-256 of NFKC-normalized
118
+ text) so repeated calls are idempotent.
119
+
120
+ ## Storage layout
121
+
122
+ ```
123
+ <projectRoot>/.wrongstack/vector-memory/
124
+ ├── vector-memory.db — SQLite database (WAL mode)
125
+ ├── vector-memory.db-wal — WAL frame file
126
+ └── models/ — transformers.js model cache (when configured)
127
+ ```
128
+
129
+ The model cache defaults to `.wrongstack/vector-memory/models` under
130
+ the project root; override via `TransformersEmbeddingProvider({ cacheDir })`.
131
+ Set `allowRemoteModels: false` to refuse downloads and require a
132
+ pre-populated cache (useful in CI / air-gapped runs).
133
+
134
+ ## Schema
135
+
136
+ - `entries` — text, summary, metadata JSON, tags JSON, scope, kind,
137
+ content_hash, timestamps. Indexed on scope/kind/hash/updated_at.
138
+ - `vectors` — `(entry_id, provider_id)` PK, dimensions, raw float32
139
+ BLOB, timestamp. Foreign-key cascades on entry delete. Provider-id
140
+ keying means a model change triggers reindexing rather than mixed
141
+ vectors.
142
+ - `entries_fts` — FTS5 mirror of `text` + `tags` for the optional
143
+ lexical fallback path. Kept in sync via triggers.
144
+ - `schema_meta` — active provider id and dimensions.
145
+
146
+ ## API reference
147
+
148
+ - `VectorMemoryStore` — constructor `({ provider, projectRoot, directory?, filename? })`.
149
+ - `remember(input)` → `VectorEntryWithVector`
150
+ - `get(id)` → `VectorEntryWithVector | undefined`
151
+ - `forget(id)` → `boolean`
152
+ - `search(query, opts?)` → `VectorSearchHit[]`
153
+ - `list(opts?)` → `VectorEntry[]`
154
+ - `stats()` → `VectorStoreStats`
155
+ - `reindexAll()` → `{ processed, errors }`
156
+ - `syncFromSage(source)` → `SageSyncReport`
157
+ - `activeProviderId` → `string`
158
+ - `close()`
159
+
160
+ See `src/types.ts` for the full type surface.
161
+
162
+ ## Testing
163
+
164
+ Tests use a deterministic `FakeEmbeddingProvider` — no network, no
165
+ model download. Run:
166
+
167
+ ```sh
168
+ pnpm --filter @wrongstack/vector-memory test
169
+ ```
170
+
171
+ The integration path that exercises the real transformers.js pipeline
172
+ is not included in the default test suite because it requires model
173
+ download. To exercise it manually:
174
+
175
+ ```ts
176
+ import { TransformersEmbeddingProvider } from '@wrongstack/vector-memory';
177
+ const p = new TransformersEmbeddingProvider();
178
+ console.log(await p.isAvailable()); // true when @huggingface/transformers is installed
179
+ const [vec] = await p.embed(['hello world']);
180
+ console.log(vec.length); // 384
181
+ ```
182
+
183
+ ## Related
184
+
185
+ - `@wrongstack/sage` — lexical/FTS/graph memory. `EmbeddingProvider` and
186
+ `cosineSimilarity` are exported from sage so any package can implement
187
+ or compose with them.
188
+ - `docs/competitive-roadmap-2026-2027/13-semantic-sage-retrieval.md` —
189
+ the roadmap doc this package implements a subset of.
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Error types for the vector memory package.
3
+ *
4
+ * Kept in their own module so callers can `instanceof`-check without
5
+ * pulling the full store implementation.
6
+ */
7
+ export declare class VectorMemoryError extends Error {
8
+ constructor(message: string);
9
+ }
10
+ /**
11
+ * Raised when the configured embedding provider is unavailable — typically
12
+ * because `@huggingface/transformers` was not installed or the model
13
+ * failed to load. The original cause (if any) is attached for diagnostics.
14
+ */
15
+ export declare class VectorMemoryProviderUnavailableError extends VectorMemoryError {
16
+ readonly cause: unknown;
17
+ constructor(message: string, cause?: unknown);
18
+ }
19
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1,10 @@
1
+ /**
2
+ * @wrongstack/vector-memory — public exports.
3
+ */
4
+ export { DEFAULT_VECTOR_DIMENSIONS, DEFAULT_VECTOR_DTYPE, DEFAULT_VECTOR_MODEL_ID, TransformersEmbeddingProvider, type TransformersEmbeddingProviderOptions, } from './transformers-provider.js';
5
+ export { VECTOR_DIMENSIONS_KEY, VECTOR_PROVIDER_KEY, VECTOR_SCHEMA_VERSION, decodeVector, encodeVector, initVectorSchema, } from './schema.js';
6
+ export { VectorMemoryStore, fallbackHashingProvider, type SageSyncSource, } from './store.js';
7
+ export { createVectorMemoryTools } from './tools.js';
8
+ export type { SageSyncReport, VectorEntry, VectorEntryInput, VectorEntryWithVector, VectorKind, VectorMemoryStoreOptions, VectorScope, VectorSearchHit, VectorSearchOptions, VectorStoreStats, } from './types.js';
9
+ export { VectorMemoryError, VectorMemoryProviderUnavailableError, } from './errors.js';
10
+ //# sourceMappingURL=index.d.ts.map
package/dist/index.js ADDED
@@ -0,0 +1,729 @@
1
+ // src/errors.ts
2
+ var VectorMemoryError = class extends Error {
3
+ constructor(message) {
4
+ super(message);
5
+ this.name = "VectorMemoryError";
6
+ }
7
+ };
8
+ var VectorMemoryProviderUnavailableError = class extends VectorMemoryError {
9
+ cause;
10
+ constructor(message, cause) {
11
+ super(message);
12
+ this.name = "VectorMemoryProviderUnavailableError";
13
+ this.cause = cause;
14
+ }
15
+ };
16
+
17
+ // src/transformers-provider.ts
18
+ var DEFAULT_VECTOR_MODEL_ID = "Xenova/all-MiniLM-L6-v2";
19
+ var DEFAULT_VECTOR_DIMENSIONS = 384;
20
+ var DEFAULT_VECTOR_DTYPE = "q8";
21
+ var TransformersEmbeddingProvider = class {
22
+ id;
23
+ dimensions;
24
+ modelId;
25
+ cacheDir;
26
+ dtype;
27
+ device;
28
+ batchSize;
29
+ maxChars;
30
+ allowRemote;
31
+ extractor;
32
+ loadPromise;
33
+ constructor(opts = {}) {
34
+ this.modelId = opts.modelId ?? DEFAULT_VECTOR_MODEL_ID;
35
+ this.cacheDir = opts.cacheDir;
36
+ this.dtype = opts.dtype ?? DEFAULT_VECTOR_DTYPE;
37
+ this.device = opts.device ?? "cpu";
38
+ this.batchSize = opts.batchSize ?? 16;
39
+ this.maxChars = opts.maxChars ?? 2048;
40
+ this.allowRemote = opts.allowRemoteModels ?? true;
41
+ this.dimensions = DEFAULT_VECTOR_DIMENSIONS;
42
+ this.id = `transformers-js:${this.modelId}:${this.dtype}`;
43
+ }
44
+ /**
45
+ * Synchronous capability check. Returns false when the optional
46
+ * `@huggingface/transformers` dependency is not installed.
47
+ *
48
+ * NOTE: this probes via dynamic import and caches the result, but does
49
+ * NOT load the model itself — model loading is deferred to `embed()`.
50
+ */
51
+ async isAvailable() {
52
+ try {
53
+ await this.loadModule();
54
+ return true;
55
+ } catch {
56
+ return false;
57
+ }
58
+ }
59
+ async embed(texts) {
60
+ if (texts.length === 0) return [];
61
+ const extractor = await this.getExtractor();
62
+ const prepared = texts.map((t) => this.prepare(t));
63
+ const batches = [];
64
+ for (let i = 0; i < prepared.length; i += this.batchSize) {
65
+ batches.push(prepared.slice(i, i + this.batchSize));
66
+ }
67
+ const results = [];
68
+ for (const batch of batches) {
69
+ const out = await extractor(batch, { pooling: "mean", normalize: true });
70
+ results.push(...this.tensorToVectors(out, batch.length));
71
+ }
72
+ return results;
73
+ }
74
+ /** Truncate + normalize text before embedding. */
75
+ prepare(text) {
76
+ if (!text) return "";
77
+ const normalized = text.normalize("NFKC").trim();
78
+ return normalized.length > this.maxChars ? normalized.slice(0, this.maxChars) : normalized;
79
+ }
80
+ tensorToVectors(out, batchSize) {
81
+ if (typeof out.tolist === "function") {
82
+ const nested = out.tolist();
83
+ if (Array.isArray(nested) && Array.isArray(nested[0])) {
84
+ return nested.map((row) => Float32Array.from(row));
85
+ }
86
+ return [Float32Array.from(nested)];
87
+ }
88
+ const flat = out.data;
89
+ if (flat instanceof Float32Array) {
90
+ if (batchSize === 1) return [flat];
91
+ const dim = flat.length / batchSize;
92
+ const vectors = [];
93
+ for (let i = 0; i < batchSize; i++) {
94
+ vectors.push(Float32Array.from(flat.subarray(i * dim, (i + 1) * dim)));
95
+ }
96
+ return vectors;
97
+ }
98
+ if (Array.isArray(flat) && Array.isArray(flat[0])) {
99
+ return flat.map((row) => Float32Array.from(row));
100
+ }
101
+ if (Array.isArray(flat)) {
102
+ return [Float32Array.from(flat)];
103
+ }
104
+ throw new Error("TransformersEmbeddingProvider: unexpected pipeline output shape");
105
+ }
106
+ async getExtractor() {
107
+ if (this.extractor) return this.extractor;
108
+ if (!this.loadPromise) this.loadPromise = this.loadExtractor();
109
+ this.extractor = await this.loadPromise;
110
+ return this.extractor;
111
+ }
112
+ async loadExtractor() {
113
+ const mod = await this.loadModule();
114
+ if (this.cacheDir) mod.env.cacheDir = this.cacheDir;
115
+ mod.env.allowRemoteModels = this.allowRemote;
116
+ if (!this.allowRemote) mod.env.localModelPath = this.cacheDir ?? "";
117
+ const pipe = await mod.pipeline("feature-extraction", this.modelId, {
118
+ dtype: this.dtype,
119
+ device: this.device
120
+ });
121
+ return pipe;
122
+ }
123
+ async loadModule() {
124
+ try {
125
+ return await import("@huggingface/transformers");
126
+ } catch (err) {
127
+ throw new VectorMemoryProviderUnavailableError(
128
+ "@huggingface/transformers is not installed. Install it (pnpm add @huggingface/transformers) or wire a fallback EmbeddingProvider.",
129
+ err
130
+ );
131
+ }
132
+ }
133
+ };
134
+
135
+ // src/schema.ts
136
+ var VECTOR_SCHEMA_VERSION = 1;
137
+ var VECTOR_PROVIDER_KEY = "active_provider_id";
138
+ var VECTOR_DIMENSIONS_KEY = "active_provider_dimensions";
139
+ function initVectorSchema(db) {
140
+ db.exec("PRAGMA journal_mode = WAL");
141
+ db.exec("PRAGMA synchronous = NORMAL");
142
+ db.exec("PRAGMA busy_timeout = 30000");
143
+ db.exec("PRAGMA temp_store = MEMORY");
144
+ db.exec("PRAGMA foreign_keys = ON");
145
+ db.exec(`
146
+ CREATE TABLE IF NOT EXISTS schema_meta (
147
+ key TEXT PRIMARY KEY,
148
+ value TEXT NOT NULL
149
+ );
150
+ `);
151
+ db.exec(`
152
+ CREATE TABLE IF NOT EXISTS entries (
153
+ id TEXT PRIMARY KEY,
154
+ text TEXT NOT NULL,
155
+ summary TEXT,
156
+ metadata TEXT NOT NULL DEFAULT '{}',
157
+ tags TEXT NOT NULL DEFAULT '[]',
158
+ scope TEXT NOT NULL DEFAULT 'project',
159
+ kind TEXT NOT NULL DEFAULT 'note',
160
+ content_hash TEXT NOT NULL,
161
+ created_at TEXT NOT NULL,
162
+ updated_at TEXT NOT NULL
163
+ );
164
+ `);
165
+ db.exec("CREATE INDEX IF NOT EXISTS idx_entries_scope ON entries(scope)");
166
+ db.exec("CREATE INDEX IF NOT EXISTS idx_entries_kind ON entries(kind)");
167
+ db.exec("CREATE INDEX IF NOT EXISTS idx_entries_hash ON entries(content_hash)");
168
+ db.exec(
169
+ "CREATE INDEX IF NOT EXISTS idx_entries_updated ON entries(updated_at DESC)"
170
+ );
171
+ db.exec(`
172
+ CREATE TABLE IF NOT EXISTS vectors (
173
+ entry_id TEXT NOT NULL,
174
+ provider_id TEXT NOT NULL,
175
+ dimensions INTEGER NOT NULL,
176
+ vector BLOB NOT NULL,
177
+ created_at TEXT NOT NULL,
178
+ PRIMARY KEY (entry_id, provider_id),
179
+ FOREIGN KEY (entry_id) REFERENCES entries(id) ON DELETE CASCADE
180
+ );
181
+ `);
182
+ db.exec("CREATE INDEX IF NOT EXISTS idx_vectors_provider ON vectors(provider_id)");
183
+ db.exec(`
184
+ CREATE VIRTUAL TABLE IF NOT EXISTS entries_fts USING fts5(
185
+ id UNINDEXED, text, tags, content='entries', content_rowid='rowid'
186
+ );
187
+ `);
188
+ db.exec(`
189
+ CREATE TRIGGER IF NOT EXISTS entries_ai AFTER INSERT ON entries BEGIN
190
+ INSERT INTO entries_fts(rowid, text, tags)
191
+ VALUES (new.rowid, new.text, new.tags);
192
+ END;
193
+ `);
194
+ db.exec(`
195
+ CREATE TRIGGER IF NOT EXISTS entries_ad AFTER DELETE ON entries BEGIN
196
+ INSERT INTO entries_fts(entries_fts, rowid, text, tags)
197
+ VALUES('delete', old.rowid, old.text, old.tags);
198
+ END;
199
+ `);
200
+ db.exec(`
201
+ CREATE TRIGGER IF NOT EXISTS entries_au AFTER UPDATE ON entries BEGIN
202
+ INSERT INTO entries_fts(entries_fts, rowid, text, tags)
203
+ VALUES('delete', old.rowid, old.text, old.tags);
204
+ INSERT INTO entries_fts(rowid, text, tags)
205
+ VALUES (new.rowid, new.text, new.tags);
206
+ END;
207
+ `);
208
+ }
209
+ function encodeVector(vec) {
210
+ return Buffer.from(vec.buffer, vec.byteOffset, vec.byteLength);
211
+ }
212
+ function decodeVector(buf) {
213
+ const view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
214
+ const copy = new Float32Array(buf.byteLength / 4);
215
+ for (let i = 0; i < copy.length; i++) {
216
+ copy[i] = view.getFloat32(i * 4, true);
217
+ }
218
+ return copy;
219
+ }
220
+
221
+ // src/store.ts
222
+ import { createHash, randomUUID } from "node:crypto";
223
+ import * as fs from "node:fs";
224
+ import * as path from "node:path";
225
+ import { DatabaseSync } from "node:sqlite";
226
+ import { HashingEmbeddingProvider, cosineSimilarity } from "@wrongstack/sage";
227
+ var DEFAULT_DIRECTORY = ".wrongstack/vector-memory";
228
+ var DEFAULT_FILENAME = "vector-memory.db";
229
+ var VectorMemoryStore = class _VectorMemoryStore {
230
+ db;
231
+ dbPath;
232
+ provider;
233
+ closed = false;
234
+ constructor(opts) {
235
+ if (!opts.provider) throw new Error("VectorMemoryStore: provider is required");
236
+ if (!opts.projectRoot) throw new Error("VectorMemoryStore: projectRoot is required");
237
+ this.provider = opts.provider;
238
+ const dir = opts.directory ?? DEFAULT_DIRECTORY;
239
+ const filename = opts.filename ?? DEFAULT_FILENAME;
240
+ if (path.isAbsolute(dir)) {
241
+ throw new Error("Vector memory directory must be project-relative.");
242
+ }
243
+ const rootDir = path.resolve(opts.projectRoot, dir);
244
+ const rel = path.relative(path.resolve(opts.projectRoot), rootDir);
245
+ if (rel.startsWith("..") || path.isAbsolute(rel)) {
246
+ throw new Error("Vector memory directory must stay inside the project root.");
247
+ }
248
+ fs.mkdirSync(rootDir, { recursive: true });
249
+ this.dbPath = path.join(rootDir, filename);
250
+ this.db = new DatabaseSync(this.dbPath);
251
+ initVectorSchema(this.db);
252
+ this.recordActiveProvider();
253
+ }
254
+ get activeProviderId() {
255
+ const row = this.db.prepare("SELECT value FROM schema_meta WHERE key = ?").get(VECTOR_PROVIDER_KEY);
256
+ return row?.value ?? this.provider.id;
257
+ }
258
+ recordActiveProvider() {
259
+ this.db.exec("BEGIN");
260
+ try {
261
+ this.db.prepare(
262
+ `INSERT INTO schema_meta (key, value) VALUES (?, ?)
263
+ ON CONFLICT(key) DO UPDATE SET value = excluded.value`
264
+ ).run(VECTOR_PROVIDER_KEY, this.provider.id);
265
+ this.db.prepare(
266
+ `INSERT INTO schema_meta (key, value) VALUES (?, ?)
267
+ ON CONFLICT(key) DO UPDATE SET value = excluded.value`
268
+ ).run(VECTOR_DIMENSIONS_KEY, String(this.provider.dimensions));
269
+ this.db.exec("COMMIT");
270
+ } catch (e) {
271
+ this.db.exec("ROLLBACK");
272
+ throw e;
273
+ }
274
+ }
275
+ static contentHash(text) {
276
+ return createHash("sha256").update(text.normalize("NFKC").trim()).digest("hex");
277
+ }
278
+ async remember(input) {
279
+ this.assertOpen();
280
+ if (!input.text || input.text.trim().length === 0) {
281
+ throw new Error("VectorMemoryStore.remember: text must be non-empty");
282
+ }
283
+ const now = (/* @__PURE__ */ new Date()).toISOString();
284
+ const id = randomUUID();
285
+ const contentHash = _VectorMemoryStore.contentHash(input.text);
286
+ const metadata = input.metadata ?? {};
287
+ const tags = input.tags ?? [];
288
+ const scope = input.scope ?? "project";
289
+ const kind = input.kind ?? "note";
290
+ let vector;
291
+ let providerId;
292
+ try {
293
+ const result2 = await this.provider.embed([input.text]);
294
+ vector = result2[0];
295
+ providerId = this.provider.id;
296
+ } catch {
297
+ providerId = void 0;
298
+ vector = void 0;
299
+ }
300
+ this.db.exec("BEGIN");
301
+ try {
302
+ this.db.prepare(
303
+ `INSERT INTO entries
304
+ (id, text, summary, metadata, tags, scope, kind, content_hash, created_at, updated_at)
305
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
306
+ ).run(
307
+ id,
308
+ input.text,
309
+ input.summary ?? null,
310
+ JSON.stringify(metadata),
311
+ JSON.stringify(tags),
312
+ scope,
313
+ kind,
314
+ contentHash,
315
+ now,
316
+ now
317
+ );
318
+ if (vector && providerId) {
319
+ this.db.prepare(
320
+ `INSERT INTO vectors (entry_id, provider_id, dimensions, vector, created_at)
321
+ VALUES (?, ?, ?, ?, ?)
322
+ ON CONFLICT(entry_id, provider_id) DO UPDATE SET
323
+ vector = excluded.vector,
324
+ dimensions = excluded.dimensions,
325
+ created_at = excluded.created_at`
326
+ ).run(id, providerId, vector.length, encodeVector(vector), now);
327
+ }
328
+ this.db.exec("COMMIT");
329
+ } catch (e) {
330
+ this.db.exec("ROLLBACK");
331
+ throw e;
332
+ }
333
+ const result = {
334
+ id,
335
+ text: input.text,
336
+ summary: input.summary ?? void 0,
337
+ metadata,
338
+ tags,
339
+ scope,
340
+ kind,
341
+ contentHash,
342
+ createdAt: now,
343
+ updatedAt: now,
344
+ providerId: providerId ?? "",
345
+ dimensions: vector?.length ?? 0
346
+ };
347
+ if (vector) result.vector = vector;
348
+ return result;
349
+ }
350
+ get(id) {
351
+ this.assertOpen();
352
+ const row = this.db.prepare("SELECT * FROM entries WHERE id = ?").get(id);
353
+ if (!row) return void 0;
354
+ const vectorRow = this.db.prepare("SELECT * FROM vectors WHERE entry_id = ?").get(id);
355
+ return this.rowToEntry(row, vectorRow);
356
+ }
357
+ forget(id) {
358
+ this.assertOpen();
359
+ this.db.exec("BEGIN");
360
+ try {
361
+ const info = this.db.prepare("DELETE FROM entries WHERE id = ?").run(id);
362
+ this.db.exec("COMMIT");
363
+ return info.changes > 0;
364
+ } catch (e) {
365
+ this.db.exec("ROLLBACK");
366
+ throw e;
367
+ }
368
+ }
369
+ async search(query, opts = {}) {
370
+ this.assertOpen();
371
+ const limit = opts.limit ?? 10;
372
+ const threshold = opts.threshold ?? 0;
373
+ if (typeof query !== "string" || query.trim().length === 0) return [];
374
+ let queryVec;
375
+ try {
376
+ const result = await this.provider.embed([query]);
377
+ if (!result[0]) return [];
378
+ queryVec = result[0];
379
+ } catch {
380
+ return [];
381
+ }
382
+ if (!queryVec || queryVec.length === 0) return [];
383
+ const providerId = this.provider.id;
384
+ const dimensions = this.provider.dimensions;
385
+ const filters = ["v.provider_id = ?", "v.dimensions = ?"];
386
+ const params = [providerId, dimensions];
387
+ if (opts.scope !== void 0) {
388
+ filters.push("e.scope = ?");
389
+ params.push(opts.scope);
390
+ }
391
+ if (opts.kind !== void 0) {
392
+ filters.push("e.kind = ?");
393
+ params.push(opts.kind);
394
+ }
395
+ const rows = this.db.prepare(
396
+ `SELECT e.id, e.text, e.summary, e.metadata, e.tags, e.scope, e.kind,
397
+ e.content_hash, e.created_at, e.updated_at,
398
+ v.vector AS vec_blob
399
+ FROM entries e
400
+ JOIN vectors v ON v.entry_id = e.id
401
+ WHERE ${filters.join(" AND ")}`
402
+ ).all(...params);
403
+ const scored = [];
404
+ for (const row of rows) {
405
+ const blob = row.vec_blob;
406
+ const vec = decodeVector(blob);
407
+ const raw = cosineSimilarity(queryVec, vec);
408
+ const score = Math.max(0, Math.min(1, raw));
409
+ if (score < threshold) continue;
410
+ const entry = this.rowToEntry(row);
411
+ scored.push({ entry, score, providerId });
412
+ }
413
+ scored.sort((a, b) => b.score - a.score);
414
+ return scored.slice(0, limit);
415
+ }
416
+ list(opts = {}) {
417
+ this.assertOpen();
418
+ const where = [];
419
+ const params = [];
420
+ if (opts.scope !== void 0) {
421
+ where.push("scope = ?");
422
+ params.push(opts.scope);
423
+ }
424
+ if (opts.kind !== void 0) {
425
+ where.push("kind = ?");
426
+ params.push(opts.kind);
427
+ }
428
+ const sql = `SELECT * FROM entries ${where.length ? `WHERE ${where.join(" AND ")}` : ""}
429
+ ORDER BY updated_at DESC LIMIT ?`;
430
+ params.push(opts.limit ?? 100);
431
+ const rows = this.db.prepare(sql).all(...params);
432
+ return rows.map((r) => this.rowToEntry(r));
433
+ }
434
+ async reindexAll() {
435
+ this.assertOpen();
436
+ const rows = this.db.prepare("SELECT id, text FROM entries").all();
437
+ let processed = 0;
438
+ let errors = 0;
439
+ for (const row of rows) {
440
+ try {
441
+ const result = await this.provider.embed([row.text]);
442
+ const v = result[0];
443
+ if (!v) {
444
+ errors++;
445
+ continue;
446
+ }
447
+ this.db.prepare(
448
+ `INSERT INTO vectors (entry_id, provider_id, dimensions, vector, created_at)
449
+ VALUES (?, ?, ?, ?, ?)
450
+ ON CONFLICT(entry_id, provider_id) DO UPDATE SET
451
+ vector = excluded.vector,
452
+ dimensions = excluded.dimensions,
453
+ created_at = excluded.created_at`
454
+ ).run(
455
+ row.id,
456
+ this.provider.id,
457
+ v.length,
458
+ encodeVector(v),
459
+ (/* @__PURE__ */ new Date()).toISOString()
460
+ );
461
+ processed++;
462
+ } catch {
463
+ errors++;
464
+ }
465
+ }
466
+ return { processed, errors };
467
+ }
468
+ stats() {
469
+ this.assertOpen();
470
+ const entryCount = this.db.prepare("SELECT COUNT(*) AS n FROM entries").get().n;
471
+ const vectorCount = this.db.prepare("SELECT COUNT(*) AS n FROM vectors").get().n;
472
+ const providerRows = this.db.prepare("SELECT DISTINCT provider_id FROM vectors").all();
473
+ return {
474
+ entries: entryCount,
475
+ vectors: vectorCount,
476
+ providers: providerRows.map((r) => r.provider_id),
477
+ modelAvailable: true,
478
+ modelId: this.provider.id,
479
+ dimensions: this.provider.dimensions
480
+ };
481
+ }
482
+ close() {
483
+ if (this.closed) return;
484
+ this.closed = true;
485
+ this.db.close();
486
+ }
487
+ assertOpen() {
488
+ if (this.closed) throw new Error("VectorMemoryStore is closed");
489
+ }
490
+ rowToEntry(row, vectorRow) {
491
+ const summaryValue = row.summary;
492
+ const entry = {
493
+ id: row.id,
494
+ text: row.text,
495
+ summary: summaryValue ?? void 0,
496
+ metadata: safeParseJson(row.metadata, {}),
497
+ tags: safeParseJson(row.tags, []),
498
+ scope: row.scope,
499
+ kind: row.kind,
500
+ contentHash: row.content_hash,
501
+ createdAt: row.created_at,
502
+ updatedAt: row.updated_at,
503
+ providerId: vectorRow?.provider_id ?? "",
504
+ dimensions: vectorRow?.dimensions ?? 0
505
+ };
506
+ if (vectorRow?.vector) {
507
+ entry.vector = decodeVector(vectorRow.vector);
508
+ }
509
+ return entry;
510
+ }
511
+ async syncFromSage(sage) {
512
+ this.assertOpen();
513
+ const memories = await sage.listActiveMemories({ limit: 5e3 });
514
+ let indexed = 0;
515
+ let skipped = 0;
516
+ let failed = 0;
517
+ const errors = [];
518
+ for (const memory of memories) {
519
+ try {
520
+ const existing = this.db.prepare("SELECT id FROM entries WHERE content_hash = ? LIMIT 1").get(_VectorMemoryStore.contentHash(memory.text));
521
+ if (existing) {
522
+ skipped++;
523
+ continue;
524
+ }
525
+ await this.remember({
526
+ text: memory.text,
527
+ summary: memory.summary ?? void 0,
528
+ metadata: { source: "sage", sageId: memory.id, ...memory.metadata ?? {} },
529
+ tags: memory.tags ?? [],
530
+ scope: "project",
531
+ kind: "note"
532
+ });
533
+ indexed++;
534
+ } catch (err) {
535
+ failed++;
536
+ errors.push({ memoryId: memory.id, message: errMsg(err) });
537
+ }
538
+ }
539
+ return { scanned: memories.length, indexed, skipped, failed, errors };
540
+ }
541
+ };
542
+ function fallbackHashingProvider(dimensions) {
543
+ return new HashingEmbeddingProvider({ dimensions });
544
+ }
545
+ function safeParseJson(value, fallback) {
546
+ if (typeof value !== "string") return fallback;
547
+ try {
548
+ return JSON.parse(value);
549
+ } catch {
550
+ return fallback;
551
+ }
552
+ }
553
+ function errMsg(err) {
554
+ return err instanceof Error ? err.message : String(err);
555
+ }
556
+
557
+ // src/tools.ts
558
+ function createVectorMemoryTools(store) {
559
+ return [
560
+ vectorMemoryRememberTool(store),
561
+ vectorMemorySearchTool(store),
562
+ vectorMemoryStatsTool(store),
563
+ vectorMemoryForgetTool(store)
564
+ ];
565
+ }
566
+ function vectorMemoryRememberTool(store) {
567
+ return {
568
+ name: "vector_memory_remember",
569
+ category: "Memory",
570
+ description: "Persist a piece of knowledge into the local vector memory store. The text is embedded with the active embedding provider (transformers.js when available, otherwise the sage hashing provider). Returns the new entry id and whether an embedding was stored.",
571
+ usageHint: "Store text you want to find later by *meaning*, not just exact keywords. Embeddings happen locally \u2014 no project text leaves the machine.",
572
+ permission: "confirm",
573
+ mutating: true,
574
+ riskTier: "standard",
575
+ timeoutMs: 5e3,
576
+ capabilities: ["memory.write"],
577
+ icon: "settings",
578
+ inputSchema: {
579
+ type: "object",
580
+ properties: {
581
+ text: { type: "string", minLength: 1, description: "The text to embed and store." },
582
+ summary: { type: "string", description: "Optional short label." },
583
+ tags: {
584
+ type: "array",
585
+ items: { type: "string" },
586
+ description: "Optional tags for later filtering."
587
+ },
588
+ scope: {
589
+ type: "string",
590
+ enum: ["project", "user", "session"],
591
+ description: "Visibility scope. Defaults to `project`."
592
+ },
593
+ kind: {
594
+ type: "string",
595
+ enum: ["note", "fact", "summary", "snippet", "link"],
596
+ description: "Entry kind. Defaults to `note`."
597
+ },
598
+ metadata: {
599
+ type: "object",
600
+ additionalProperties: true,
601
+ description: "Free-form metadata persisted as JSON."
602
+ }
603
+ },
604
+ required: ["text"],
605
+ additionalProperties: false
606
+ },
607
+ execute: async (input) => {
608
+ const entry = await store.remember(input);
609
+ return { id: entry.id, hasVector: entry.vector !== void 0 };
610
+ }
611
+ };
612
+ }
613
+ function vectorMemorySearchTool(store) {
614
+ return {
615
+ name: "vector_memory_search",
616
+ category: "Memory",
617
+ description: "Semantic search over the vector memory store. Embeds the query with the active provider and returns the top-k entries ranked by cosine similarity. Returns an empty list when the embedding provider is unavailable \u2014 callers should fall back to lexical search.",
618
+ usageHint: "Use when you want results ranked by meaning. Pairs well with sage `memory_search` for keyword precision.",
619
+ permission: "auto",
620
+ mutating: false,
621
+ riskTier: "safe",
622
+ timeoutMs: 5e3,
623
+ capabilities: ["memory.read"],
624
+ icon: "search",
625
+ inputSchema: {
626
+ type: "object",
627
+ properties: {
628
+ query: { type: "string", minLength: 1, description: "The natural-language query." },
629
+ limit: { type: "number", minimum: 1, maximum: 100, description: "Max results (default 10)." },
630
+ threshold: {
631
+ type: "number",
632
+ minimum: 0,
633
+ maximum: 1,
634
+ description: "Minimum cosine similarity. Results below the floor are dropped."
635
+ },
636
+ scope: {
637
+ type: "string",
638
+ enum: ["project", "user", "session"],
639
+ description: "Restrict to a scope."
640
+ },
641
+ kind: {
642
+ type: "string",
643
+ enum: ["note", "fact", "summary", "snippet", "link"],
644
+ description: "Restrict to a kind."
645
+ }
646
+ },
647
+ required: ["query"],
648
+ additionalProperties: false
649
+ },
650
+ execute: async (input) => {
651
+ const hits = await store.search(input.query, {
652
+ limit: input.limit !== void 0 ? input.limit : void 0,
653
+ threshold: input.threshold !== void 0 ? input.threshold : void 0,
654
+ scope: input.scope,
655
+ kind: input.kind
656
+ });
657
+ return {
658
+ hits: hits.map((h) => ({
659
+ id: h.entry.id,
660
+ score: h.score,
661
+ text: h.entry.text,
662
+ summary: h.entry.summary ?? void 0,
663
+ tags: h.entry.tags
664
+ }))
665
+ };
666
+ }
667
+ };
668
+ }
669
+ function vectorMemoryStatsTool(store) {
670
+ return {
671
+ name: "vector_memory_stats",
672
+ category: "Memory",
673
+ description: "Return counts, providers, and dimensions for the local vector memory store.",
674
+ usageHint: "Cheap diagnostic \u2014 safe to call any time.",
675
+ permission: "auto",
676
+ mutating: false,
677
+ riskTier: "safe",
678
+ timeoutMs: 1e3,
679
+ capabilities: ["memory.read"],
680
+ icon: "search",
681
+ inputSchema: {
682
+ type: "object",
683
+ properties: {},
684
+ additionalProperties: false
685
+ },
686
+ execute: async () => store.stats()
687
+ };
688
+ }
689
+ function vectorMemoryForgetTool(store) {
690
+ return {
691
+ name: "vector_memory_forget",
692
+ category: "Memory",
693
+ description: "Remove an entry (and its vector) from the vector memory store.",
694
+ usageHint: "Hard delete \u2014 no soft-delete tombstone. Use `vector_memory_search` to find the id first if you only have text.",
695
+ permission: "confirm",
696
+ mutating: true,
697
+ riskTier: "standard",
698
+ timeoutMs: 1e3,
699
+ capabilities: ["memory.write"],
700
+ icon: "settings",
701
+ inputSchema: {
702
+ type: "object",
703
+ properties: {
704
+ id: { type: "string", minLength: 1, description: "Entry id returned by `vector_memory_remember`." }
705
+ },
706
+ required: ["id"],
707
+ additionalProperties: false
708
+ },
709
+ execute: async (input) => ({ removed: store.forget(input.id) })
710
+ };
711
+ }
712
+ export {
713
+ DEFAULT_VECTOR_DIMENSIONS,
714
+ DEFAULT_VECTOR_DTYPE,
715
+ DEFAULT_VECTOR_MODEL_ID,
716
+ TransformersEmbeddingProvider,
717
+ VECTOR_DIMENSIONS_KEY,
718
+ VECTOR_PROVIDER_KEY,
719
+ VECTOR_SCHEMA_VERSION,
720
+ VectorMemoryError,
721
+ VectorMemoryProviderUnavailableError,
722
+ VectorMemoryStore,
723
+ createVectorMemoryTools,
724
+ decodeVector,
725
+ encodeVector,
726
+ fallbackHashingProvider,
727
+ initVectorSchema
728
+ };
729
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,21 @@
1
+ /**
2
+ * SQLite schema for the vector memory store.
3
+ *
4
+ * Two tables:
5
+ * - `entries` — text + metadata, no embedding column
6
+ * - `vectors` — (entry_id, provider_id) PK, raw float32 blob
7
+ *
8
+ * The vectors table is keyed by (entry_id, provider_id) so a model swap
9
+ * invalidates only the old provider rows on insert — no mixed-vector search.
10
+ * A companion `schema_meta` table records the active provider id and dims.
11
+ */
12
+ import type { DatabaseSync } from 'node:sqlite';
13
+ export declare const VECTOR_SCHEMA_VERSION = 1;
14
+ export declare const VECTOR_PROVIDER_KEY = "active_provider_id";
15
+ export declare const VECTOR_DIMENSIONS_KEY = "active_provider_dimensions";
16
+ export declare function initVectorSchema(db: DatabaseSync): void;
17
+ /** Encode a Float32Array to a SQLite BLOB (Buffer). */
18
+ export declare function encodeVector(vec: Float32Array): Buffer;
19
+ /** Decode a SQLite BLOB (Buffer or Uint8Array) back to a Float32Array. */
20
+ export declare function decodeVector(buf: Buffer | Uint8Array): Float32Array;
21
+ //# sourceMappingURL=schema.d.ts.map
@@ -0,0 +1,43 @@
1
+ import { HashingEmbeddingProvider } from '@wrongstack/sage';
2
+ import type { SageSyncReport, VectorEntry, VectorEntryInput, VectorEntryWithVector, VectorKind, VectorMemoryStoreOptions, VectorScope, VectorSearchHit, VectorSearchOptions, VectorStoreStats } from './types.js';
3
+ export declare class VectorMemoryStore {
4
+ private readonly db;
5
+ private readonly dbPath;
6
+ private readonly provider;
7
+ private closed;
8
+ constructor(opts: VectorMemoryStoreOptions);
9
+ get activeProviderId(): string;
10
+ private recordActiveProvider;
11
+ static contentHash(text: string): string;
12
+ remember(input: VectorEntryInput): Promise<VectorEntryWithVector>;
13
+ get(id: string): VectorEntryWithVector | undefined;
14
+ forget(id: string): boolean;
15
+ search(query: string, opts?: VectorSearchOptions): Promise<VectorSearchHit[]>;
16
+ list(opts?: {
17
+ limit?: number;
18
+ scope?: VectorScope;
19
+ kind?: VectorKind;
20
+ }): VectorEntry[];
21
+ reindexAll(): Promise<{
22
+ processed: number;
23
+ errors: number;
24
+ }>;
25
+ stats(): VectorStoreStats;
26
+ close(): void;
27
+ private assertOpen;
28
+ private rowToEntry;
29
+ syncFromSage(sage: SageSyncSource): Promise<SageSyncReport>;
30
+ }
31
+ export interface SageSyncSource {
32
+ listActiveMemories(opts: {
33
+ limit: number;
34
+ }): Promise<Array<{
35
+ id: string;
36
+ text: string;
37
+ summary?: string;
38
+ tags?: string[];
39
+ metadata?: Record<string, unknown>;
40
+ }>>;
41
+ }
42
+ export declare function fallbackHashingProvider(dimensions: number): HashingEmbeddingProvider;
43
+ //# sourceMappingURL=store.d.ts.map
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Vector memory tools — agent-facing surface for the vector store.
3
+ *
4
+ * The tools mirror sage's tool patterns:
5
+ * - reads (`vector_memory_search`, `vector_memory_stats`) → `permission: 'auto'`
6
+ * - writes (`vector_memory_remember`, `vector_memory_forget`) → `permission: 'confirm'`
7
+ *
8
+ * All tools accept the store directly so hosts can wire them however they
9
+ * like — there's no implicit dependency on the sage runtime surface.
10
+ */
11
+ import type { Tool } from '@wrongstack/core/types';
12
+ import type { VectorMemoryStore } from './store.js';
13
+ export declare function createVectorMemoryTools(store: VectorMemoryStore): Tool[];
14
+ //# sourceMappingURL=tools.d.ts.map
@@ -0,0 +1,67 @@
1
+ /**
2
+ * TransformersEmbeddingProvider — wraps `@huggingface/transformers`'s
3
+ * feature-extraction pipeline as a sage `EmbeddingProvider`.
4
+ *
5
+ * The provider is lazy: nothing is imported until `embed()` is first called.
6
+ * If `@huggingface/transformers` is not installed (it's listed as an
7
+ * optionalDependency), `isAvailable()` returns false and `embed()` throws
8
+ * a descriptive `VectorMemoryProviderUnavailableError` so callers can
9
+ * fall back to `HashingEmbeddingProvider`.
10
+ *
11
+ * Default model: `Xenova/all-MiniLM-L6-v2` (384 dims, ~25MB quantized).
12
+ * On first use the model is fetched from the Hugging Face Hub and cached
13
+ * under `cacheDir` (default: `.wrongstack/vector-memory/models`).
14
+ */
15
+ import type { EmbeddingProvider } from '@wrongstack/sage';
16
+ export declare const DEFAULT_VECTOR_MODEL_ID = "Xenova/all-MiniLM-L6-v2";
17
+ export declare const DEFAULT_VECTOR_DIMENSIONS = 384;
18
+ export declare const DEFAULT_VECTOR_DTYPE = "q8";
19
+ export interface TransformersEmbeddingProviderOptions {
20
+ /** Hugging Face Hub model id (default `Xenova/all-MiniLM-L6-v2`). */
21
+ modelId?: string;
22
+ /** Local cache directory for downloaded model files. */
23
+ cacheDir?: string;
24
+ /** Inference dtype — `q8` is ~4x smaller and ~2x faster than `fp32`. */
25
+ dtype?: 'q8' | 'fp16' | 'fp32' | 'q4';
26
+ /** Device selector — Node builds default to `cpu` via onnxruntime-node. */
27
+ device?: 'cpu' | 'wasm' | 'webgpu';
28
+ /** Maximum texts per batch when calling the pipeline. */
29
+ batchSize?: number;
30
+ /** Maximum characters per input before the provider truncates. */
31
+ maxChars?: number;
32
+ /**
33
+ * Disable remote model downloads. Useful in offline / air-gapped runs
34
+ * — the provider will surface a clear error when the model isn't cached.
35
+ */
36
+ allowRemoteModels?: boolean;
37
+ }
38
+ export declare class TransformersEmbeddingProvider implements EmbeddingProvider {
39
+ readonly id: string;
40
+ readonly dimensions: number;
41
+ private readonly modelId;
42
+ private readonly cacheDir;
43
+ private readonly dtype;
44
+ private readonly device;
45
+ private readonly batchSize;
46
+ private readonly maxChars;
47
+ private readonly allowRemote;
48
+ private extractor;
49
+ private loadPromise;
50
+ constructor(opts?: TransformersEmbeddingProviderOptions);
51
+ /**
52
+ * Synchronous capability check. Returns false when the optional
53
+ * `@huggingface/transformers` dependency is not installed.
54
+ *
55
+ * NOTE: this probes via dynamic import and caches the result, but does
56
+ * NOT load the model itself — model loading is deferred to `embed()`.
57
+ */
58
+ isAvailable(): Promise<boolean>;
59
+ embed(texts: string[]): Promise<Float32Array[]>;
60
+ /** Truncate + normalize text before embedding. */
61
+ private prepare;
62
+ private tensorToVectors;
63
+ private getExtractor;
64
+ private loadExtractor;
65
+ private loadModule;
66
+ }
67
+ //# sourceMappingURL=transformers-provider.d.ts.map
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Vector Memory — public type surface.
3
+ *
4
+ * The store persists entries (text + metadata) alongside their vector
5
+ * embeddings, keyed by provider id so a model change triggers re-indexing.
6
+ */
7
+ import type { EmbeddingProvider } from '@wrongstack/sage';
8
+ export type VectorScope = 'project' | 'user' | 'session';
9
+ export type VectorKind = 'note' | 'fact' | 'summary' | 'snippet' | 'link';
10
+ export interface VectorEntryInput {
11
+ text: string;
12
+ /** Optional short label — stored as-is, returned in search results. */
13
+ summary?: string | undefined;
14
+ /** Free-form metadata persisted as JSON. Use for source URLs, anchors, etc. */
15
+ metadata?: Record<string, unknown> | undefined;
16
+ tags?: string[] | undefined;
17
+ scope?: VectorScope | undefined;
18
+ kind?: VectorKind | undefined;
19
+ /** Embedding provider id override for this write. Defaults to the store's provider. */
20
+ providerId?: string | undefined;
21
+ }
22
+ export interface VectorEntry {
23
+ id: string;
24
+ text: string;
25
+ summary?: string | undefined;
26
+ metadata: Record<string, unknown>;
27
+ tags: string[];
28
+ scope: VectorScope;
29
+ kind: VectorKind;
30
+ /** Stable hash of the entry text — used to detect duplicate re-writes. */
31
+ contentHash: string;
32
+ createdAt: string;
33
+ updatedAt: string;
34
+ }
35
+ export interface VectorEntryWithVector extends VectorEntry {
36
+ /** Provider id that produced the stored vector. */
37
+ providerId: string;
38
+ /** Vector dimensions (matches `EmbeddingProvider.dimensions`). */
39
+ dimensions: number;
40
+ /** Decoded vector; absent when no embedding has been computed yet. */
41
+ vector?: Float32Array | undefined;
42
+ }
43
+ export interface VectorSearchOptions {
44
+ limit?: number | undefined;
45
+ /** Minimum cosine similarity [0, 1]. Results below the floor are dropped. */
46
+ threshold?: number | undefined;
47
+ scope?: VectorScope | undefined;
48
+ kind?: VectorKind | undefined;
49
+ /** Provider id override for the query embedding. Defaults to the store's provider. */
50
+ providerId?: string | undefined;
51
+ }
52
+ export interface VectorSearchHit {
53
+ entry: VectorEntry;
54
+ /** Cosine similarity in [-1, 1]. The store clamps to [0, 1] before returning. */
55
+ score: number;
56
+ providerId: string;
57
+ }
58
+ export interface VectorStoreStats {
59
+ entries: number;
60
+ vectors: number;
61
+ providers: string[];
62
+ modelAvailable: boolean;
63
+ modelId: string;
64
+ dimensions: number;
65
+ }
66
+ /**
67
+ * The store accepts any `EmbeddingProvider` from `@wrongstack/sage`. In
68
+ * production the caller wires a `TransformersEmbeddingProvider`; tests and
69
+ * offline runs can substitute a `HashingEmbeddingProvider` or a fake.
70
+ */
71
+ export interface VectorMemoryStoreOptions {
72
+ /** Embedding provider used for both write-side and query-side embedding. */
73
+ provider: EmbeddingProvider;
74
+ /** Absolute project root whose `.wrongstack/vector-memory/` directory owns the SQLite db. */
75
+ projectRoot: string;
76
+ /** Override the storage subdirectory (default `.wrongstack/vector-memory`). */
77
+ directory?: string;
78
+ /** Override the SQLite file name (default `vector-memory.db`). */
79
+ filename?: string;
80
+ }
81
+ /**
82
+ * Returned by `syncFromSage` — surfaces what was indexed, what was skipped,
83
+ * and any failures so the caller can log or retry.
84
+ */
85
+ export interface SageSyncReport {
86
+ scanned: number;
87
+ indexed: number;
88
+ skipped: number;
89
+ failed: number;
90
+ errors: Array<{
91
+ memoryId: string;
92
+ message: string;
93
+ }>;
94
+ }
95
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Float32Array <-> SQLite BLOB codec.
3
+ *
4
+ * Mirrors the pattern used by `packages/tools/src/codebase-index/vector-search.ts`
5
+ * so that the on-disk byte layout is portable between the codebase-index
6
+ * and vector-memory stores. node:sqlite returns `Uint8Array` for BLOB columns,
7
+ * so we read through `DataView` regardless of the runtime type.
8
+ */
9
+ export declare function encodeVector(vec: Float32Array): Buffer;
10
+ export declare function decodeVector(buf: Buffer | Uint8Array): Float32Array;
11
+ //# sourceMappingURL=vector-codec.d.ts.map
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "@wrongstack/vector-memory",
3
+ "version": "0.308.0",
4
+ "license": "MIT",
5
+ "description": "WrongStack Vector Memory — an additional vector-search memory store powered by @huggingface/transformers (local ONNX embeddings), alongside the SAGE lexical memory system.",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/WrongStack/WrongStack.git",
9
+ "directory": "packages/vector-memory"
10
+ },
11
+ "homepage": "https://github.com/WrongStack/WrongStack#readme",
12
+ "bugs": "https://github.com/WrongStack/WrongStack/issues",
13
+ "author": "ECOSTACK TECHNOLOGY OÜ",
14
+ "type": "module",
15
+ "main": "./dist/index.js",
16
+ "types": "./dist/index.d.ts",
17
+ "sideEffects": false,
18
+ "exports": {
19
+ ".": {
20
+ "types": "./dist/index.d.ts",
21
+ "import": "./dist/index.js"
22
+ }
23
+ },
24
+ "files": [
25
+ "dist",
26
+ "!dist/**/*.map",
27
+ "README.md"
28
+ ],
29
+ "dependencies": {
30
+ "@wrongstack/core": "0.308.0",
31
+ "@wrongstack/sage": "0.308.0"
32
+ },
33
+ "optionalDependencies": {
34
+ "@huggingface/transformers": "^4.2.0"
35
+ },
36
+ "devDependencies": {
37
+ "@types/node": "^26.2.0",
38
+ "typescript": "^7.0.2",
39
+ "vitest": "^4.1.10"
40
+ },
41
+ "publishConfig": {
42
+ "access": "public",
43
+ "provenance": true
44
+ },
45
+ "scripts": {
46
+ "build": "node ../../scripts/build-package.mjs",
47
+ "typecheck": "tsc --noEmit",
48
+ "test": "vitest run",
49
+ "test:integration": "WRONGSTACK_VECTOR_INTEGRATION=1 vitest run tests/integration.test.ts",
50
+ "clean": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\""
51
+ }
52
+ }