@wrongstack/vector-memory 0.320.1 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -249,6 +249,11 @@ function encodeVector(vec) {
249
249
  return Buffer.from(vec.buffer, vec.byteOffset, vec.byteLength);
250
250
  }
251
251
  function decodeVector(buf) {
252
+ if (buf.byteLength % 4 !== 0) {
253
+ throw new Error(
254
+ `decodeVector: invalid vector byteLength ${buf.byteLength} (must be a multiple of 4)`
255
+ );
256
+ }
252
257
  const view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
253
258
  const copy = new Float32Array(buf.byteLength / 4);
254
259
  for (let i = 0; i < copy.length; i++) {
@@ -264,6 +269,12 @@ import * as path from "node:path";
264
269
  import { withFileLock } from "@wrongstack/core/utils";
265
270
  import { loadRuntimeDatabaseSync } from "@wrongstack/persistence";
266
271
  import { cosineSimilarity, HashingEmbeddingProvider } from "@wrongstack/sage";
272
+ var DEFAULT_SEARCH_LIMIT = 10;
273
+ function normalizeLimit(limit) {
274
+ if (limit === void 0 || !Number.isFinite(limit)) return DEFAULT_SEARCH_LIMIT;
275
+ const floored = Math.floor(limit);
276
+ return floored < 1 ? DEFAULT_SEARCH_LIMIT : floored;
277
+ }
267
278
  var DEFAULT_DIRECTORY = ".wrongstack/vector-memory";
268
279
  var DEFAULT_FILENAME = "vector-memory.db";
269
280
  var DEFAULT_LOCK_TIMEOUT_MS = 5e3;
@@ -525,7 +536,7 @@ var VectorMemoryStore = class _VectorMemoryStore {
525
536
  }
526
537
  async search(query, opts = {}) {
527
538
  this.assertOpen();
528
- const limit = opts.limit ?? 10;
539
+ const limit = normalizeLimit(opts.limit);
529
540
  const threshold = opts.threshold ?? 0;
530
541
  const includeVectors = opts.includeVectors === true;
531
542
  if (typeof query !== "string" || query.trim().length === 0) return [];
@@ -1214,12 +1225,6 @@ async function fuseWithVectorMemory(query, lexical, options = {}) {
1214
1225
  }
1215
1226
  const sageById = /* @__PURE__ */ new Map();
1216
1227
  for (const memory of lexical) sageById.set(memory.id, memory);
1217
- for (const hit of vectorHits) {
1218
- const sageId = hit.entry.metadata?.["sageId"];
1219
- if (typeof sageId === "string" && !sageById.has(sageId)) {
1220
- continue;
1221
- }
1222
- }
1223
1228
  const lexicalRanked = lexical.map((memory, index) => ({
1224
1229
  memory,
1225
1230
  rankScore: lexicalRankScore(index, lexical.length),
@@ -1237,7 +1242,7 @@ async function fuseWithVectorMemory(query, lexical, options = {}) {
1237
1242
  const fused = /* @__PURE__ */ new Map();
1238
1243
  for (let i = 0; i < lexicalRanked.length; i++) {
1239
1244
  const c = lexicalRanked[i];
1240
- const rrf = weight * 0 + (1 - weight) * (1 / (k + i + 1));
1245
+ const rrf = (1 - weight) * (1 / (k + i + 1));
1241
1246
  fused.set(c.memory.id, {
1242
1247
  memory: c.memory,
1243
1248
  vectorScore: null,
@@ -6,8 +6,15 @@
6
6
  *
7
7
  * The fusion is a Reciprocal Rank Fusion (RRF)-style blend rather than a
8
8
  * raw cosine-blend, so it stays robust when one side returns nothing
9
- * (e.g. embedding provider unavailable). All inputs are optional:
10
- * - `lexical` may be `[]` → fusion falls back to pure vector ranking.
9
+ * (e.g. embedding provider unavailable).
10
+ *
11
+ * IMPORTANT — the vector channel can only RE-RANK the lexical list, never
12
+ * extend it. A vector hit is kept only when its `metadata.sageId` resolves to
13
+ * a memory already present in `lexical`, because the caller's lexical list is
14
+ * the authoritative source of `Sage` objects and this function will not
15
+ * fabricate one (pinned by tests/sage-fusion.test.ts). Therefore:
16
+ * - `lexical` may be `[]` → the result is `[]`, whatever the vector channel
17
+ * found. This is NOT a fallback to pure vector ranking.
11
18
  * - `vector` may be `[]` → fusion falls back to pure lexical ranking.
12
19
  * - either may be `undefined` → fusion silently drops that channel.
13
20
  *
@@ -40,6 +47,10 @@ export interface SageFusionOptions {
40
47
  /**
41
48
  * Cosine threshold below which a vector-only hit is dropped (no lexical
42
49
  * counterpart to lift it). Default 0 — keep all, let RRF decide.
50
+ *
51
+ * Currently inert: vector-only hits cannot occur (see the module docstring),
52
+ * so nothing ever reaches this threshold. Kept for API stability and for
53
+ * the day the vector channel is allowed to extend the candidate set.
43
54
  */
44
55
  vectorOnlyThreshold?: number | undefined;
45
56
  }
@@ -51,7 +62,11 @@ export interface SageFusionHit {
51
62
  lexicalScore: number | null;
52
63
  /** 0..1, monotonically higher = better. */
53
64
  finalScore: number;
54
- /** Where the candidate came from. */
65
+ /**
66
+ * Where the candidate came from. `'vector'` is currently unreachable —
67
+ * every candidate originates in the lexical list (see the module
68
+ * docstring); the vector channel only upgrades one to `'both'`.
69
+ */
55
70
  source: 'lexical' | 'vector' | 'both';
56
71
  }
57
72
  declare function lexicalRankScore(index: number, total: number): number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wrongstack/vector-memory",
3
- "version": "0.320.1",
3
+ "version": "1.0.0",
4
4
  "license": "MIT",
5
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
6
  "repository": {
@@ -27,9 +27,9 @@
27
27
  "README.md"
28
28
  ],
29
29
  "dependencies": {
30
- "@wrongstack/core": "0.320.1",
31
- "@wrongstack/persistence": "0.320.1",
32
- "@wrongstack/sage": "0.320.1"
30
+ "@wrongstack/core": "1.0.0",
31
+ "@wrongstack/persistence": "1.0.0",
32
+ "@wrongstack/sage": "1.0.0"
33
33
  },
34
34
  "optionalDependencies": {
35
35
  "@huggingface/transformers": "^4.2.0"