mumpix 1.0.19 → 1.1.3

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.
Files changed (38) hide show
  1. package/CHANGELOG.md +82 -11
  2. package/README.md +278 -8
  3. package/bin/mumpix.js +1 -405
  4. package/examples/agent-memory.js +1 -1
  5. package/examples/basic.js +1 -1
  6. package/examples/behavioral-primitives.js +50 -0
  7. package/examples/recall-quality-suite.js +156 -0
  8. package/examples/verified-mode.js +1 -1
  9. package/package.json +24 -13
  10. package/scripts/bench-semantic.cjs +76 -0
  11. package/scripts/test-license-modes.cjs +87 -0
  12. package/scripts/test-phase0.cjs +137 -0
  13. package/scripts/test-semantic.cjs +411 -0
  14. package/src/brp/index.js +1 -0
  15. package/src/collapse/index.js +1 -0
  16. package/src/core/MumpixDB.js +264 -323
  17. package/src/core/audit.js +1 -173
  18. package/src/core/auth.js +1 -232
  19. package/src/core/inverted-index.js +249 -0
  20. package/src/core/license.js +1 -267
  21. package/src/core/ml-dsa.mjs +1 -25
  22. package/src/core/ml-kem.mjs +1 -32
  23. package/src/core/recall.js +226 -112
  24. package/src/core/semantic-sidecar.js +312 -0
  25. package/src/core/store.js +365 -288
  26. package/src/core/wal-writer.js +83 -0
  27. package/src/index.js +20 -34
  28. package/src/integrations/developer-sdk.js +1 -165
  29. package/src/integrations/langchain-official.js +1 -0
  30. package/src/integrations/langchain.js +1 -131
  31. package/src/integrations/llamaindex-official.js +1 -0
  32. package/src/integrations/llamaindex.js +1 -86
  33. package/src/integrations/vector-sidecar.js +325 -0
  34. package/src/rlp/index.js +1 -0
  35. package/src/temporal/engine.js +1 -1894
  36. package/src/temporal/indexes.js +1 -178
  37. package/src/temporal/operators.js +1 -186
  38. package/scripts/postinstall-auth.js +0 -101
@@ -1,176 +1,290 @@
1
- 'use strict';
2
-
3
- /**
4
- * MumpixRecall — hybrid retrieval engine
5
- *
6
- * Strategy (in order):
7
- * 1. Exact substring match (zero-latency)
8
- * 2. TF-IDF cosine similarity (local semantic approximation, no API needed)
9
- * 3. Token overlap fallback (always produces a result)
10
- *
11
- * Optional: pass embedFn to use your own embeddings (OpenAI, Cohere, etc.)
12
- */
13
-
14
- // ── Stopwords ────────────────────────────────────
1
+ "use strict";
2
+
3
+ const DEFAULT_MIN_SCORE = 0.15;
4
+
15
5
  const STOPWORDS = new Set([
16
- 'a','an','the','is','are','was','were','be','been','being',
17
- 'have','has','had','do','does','did','will','would','could',
18
- 'should','may','might','i','you','he','she','it','we','they',
19
- 'my','your','his','her','its','our','their','what','which',
20
- 'who','whom','that','this','these','those','and','but','or',
21
- 'nor','for','so','yet','in','on','at','to','of','up','by',
22
- 'with','about','into','through','during','before','after',
23
- 'above','below','from','out','off','over','under','again',
24
- 'then','once','here','there','when','where','why','how','all',
25
- 'both','each','few','more','most','other','some','such','no',
26
- 'not','only','own','same','than','too','very','just','can',
27
- 'me','him','us','them','am','get','got','put','set','let',
28
- 'if','as','also','even','still','already','now', 'do', 'did',
6
+ "a",
7
+ "an",
8
+ "the",
9
+ "is",
10
+ "are",
11
+ "was",
12
+ "were",
13
+ "be",
14
+ "been",
15
+ "being",
16
+ "have",
17
+ "has",
18
+ "had",
19
+ "do",
20
+ "does",
21
+ "did",
22
+ "will",
23
+ "would",
24
+ "could",
25
+ "should",
26
+ "may",
27
+ "might",
28
+ "i",
29
+ "you",
30
+ "he",
31
+ "she",
32
+ "it",
33
+ "we",
34
+ "they",
35
+ "my",
36
+ "your",
37
+ "his",
38
+ "her",
39
+ "its",
40
+ "our",
41
+ "their",
42
+ "what",
43
+ "which",
44
+ "who",
45
+ "whom",
46
+ "that",
47
+ "this",
48
+ "these",
49
+ "those",
50
+ "and",
51
+ "but",
52
+ "or",
53
+ "nor",
54
+ "for",
55
+ "so",
56
+ "yet",
57
+ "in",
58
+ "on",
59
+ "at",
60
+ "to",
61
+ "of",
62
+ "up",
63
+ "by",
64
+ "with",
65
+ "about",
66
+ "into",
67
+ "through",
68
+ "during",
69
+ "before",
70
+ "after",
71
+ "above",
72
+ "below",
73
+ "from",
74
+ "out",
75
+ "off",
76
+ "over",
77
+ "under",
78
+ "again",
79
+ "then",
80
+ "once",
81
+ "here",
82
+ "there",
83
+ "when",
84
+ "where",
85
+ "why",
86
+ "how",
87
+ "all",
88
+ "both",
89
+ "each",
90
+ "few",
91
+ "more",
92
+ "most",
93
+ "other",
94
+ "some",
95
+ "such",
96
+ "no",
97
+ "not",
98
+ "only",
99
+ "own",
100
+ "same",
101
+ "than",
102
+ "too",
103
+ "very",
104
+ "just",
105
+ "can",
106
+ "me",
107
+ "him",
108
+ "us",
109
+ "them",
110
+ "am",
111
+ "get",
112
+ "got",
113
+ "put",
114
+ "set",
115
+ "let",
116
+ "if",
117
+ "as",
118
+ "also",
119
+ "even",
120
+ "still",
121
+ "already",
122
+ "now",
29
123
  ]);
30
124
 
31
- // ── TF-IDF utilities ─────────────────────────────
32
-
33
125
  function tokenize(text) {
34
- return text
126
+ return String(text || "")
35
127
  .toLowerCase()
36
- .replace(/[^a-z0-9\s'-]/g, ' ')
128
+ .replace(/[^a-z0-9\s'-]/g, " ")
37
129
  .split(/\s+/)
38
- .filter(t => t.length > 1 && !STOPWORDS.has(t));
130
+ .filter((token) => token.length > 1 && !STOPWORDS.has(token));
39
131
  }
40
132
 
41
133
  function tf(tokens) {
42
134
  const freq = {};
43
- for (const t of tokens) freq[t] = (freq[t] || 0) + 1;
135
+ for (const token of tokens) freq[token] = (freq[token] || 0) + 1;
136
+
44
137
  const len = tokens.length || 1;
45
138
  const out = {};
46
- for (const [t, c] of Object.entries(freq)) out[t] = c / len;
139
+ for (const [token, count] of Object.entries(freq)) out[token] = count / len;
47
140
  return out;
48
141
  }
49
142
 
50
143
  function buildIDF(corpus) {
51
144
  const df = {};
52
- const N = corpus.length;
145
+ const count = corpus.length;
146
+
53
147
  for (const doc of corpus) {
54
- const seen = new Set(doc);
55
- for (const t of seen) df[t] = (df[t] || 0) + 1;
148
+ for (const token of new Set(doc)) df[token] = (df[token] || 0) + 1;
56
149
  }
150
+
57
151
  const idf = {};
58
- for (const [t, c] of Object.entries(df)) {
59
- idf[t] = Math.log((N + 1) / (c + 1)) + 1;
152
+ for (const [token, docCount] of Object.entries(df)) {
153
+ idf[token] = Math.log((count + 1) / (docCount + 1)) + 1;
60
154
  }
61
155
  return idf;
62
156
  }
63
157
 
64
158
  function tfidfVec(tfMap, idf) {
65
159
  const vec = {};
66
- for (const [t, w] of Object.entries(tfMap)) {
67
- vec[t] = w * (idf[t] || 1);
160
+ for (const [token, weight] of Object.entries(tfMap)) {
161
+ vec[token] = weight * (idf[token] || 1);
68
162
  }
69
163
  return vec;
70
164
  }
71
165
 
72
166
  function cosine(a, b) {
73
- let dot = 0, normA = 0, normB = 0;
167
+ let dot = 0;
168
+ let normA = 0;
169
+ let normB = 0;
74
170
  const keys = new Set([...Object.keys(a), ...Object.keys(b)]);
75
- for (const k of keys) {
76
- const va = a[k] || 0;
77
- const vb = b[k] || 0;
78
- dot += va * vb;
79
- normA += va * va;
80
- normB += vb * vb;
171
+
172
+ for (const key of keys) {
173
+ const av = a[key] || 0;
174
+ const bv = b[key] || 0;
175
+ dot += av * bv;
176
+ normA += av * av;
177
+ normB += bv * bv;
81
178
  }
179
+
82
180
  const denom = Math.sqrt(normA) * Math.sqrt(normB);
83
181
  return denom === 0 ? 0 : dot / denom;
84
182
  }
85
183
 
86
- // ── Token overlap (tie-breaker / fallback) ───────
184
+ function cosineArrays(a, b) {
185
+ if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) return 0;
186
+ let dot = 0;
187
+ let normA = 0;
188
+ let normB = 0;
189
+
190
+ for (let i = 0; i < a.length; i++) {
191
+ dot += a[i] * b[i];
192
+ normA += a[i] * a[i];
193
+ normB += b[i] * b[i];
194
+ }
195
+
196
+ const denom = Math.sqrt(normA) * Math.sqrt(normB);
197
+ return denom === 0 ? 0 : dot / denom;
198
+ }
87
199
 
88
200
  function tokenOverlap(queryTokens, docTokens) {
89
201
  if (!queryTokens.length) return 0;
90
202
  const docSet = new Set(docTokens);
91
- const hits = queryTokens.filter(t => docSet.has(t)).length;
92
- return hits / queryTokens.length;
203
+ return queryTokens.filter((token) => docSet.has(token)).length / queryTokens.length;
204
+ }
205
+
206
+ function minScore(opts) {
207
+ if (opts.minScore === null || opts.minScore === false) return -Infinity;
208
+ return Number.isFinite(opts.minScore) ? Number(opts.minScore) : DEFAULT_MIN_SCORE;
209
+ }
210
+
211
+ function sortScored(a, b) {
212
+ if (b.score !== a.score) return b.score - a.score;
213
+ const bTs = Number.isFinite(b.r && b.r.ts) ? b.r.ts : 0;
214
+ const aTs = Number.isFinite(a.r && a.r.ts) ? a.r.ts : 0;
215
+ if (bTs !== aTs) return bTs - aTs;
216
+ const aId = Number.isFinite(a.r && a.r.id) ? a.r.id : 0;
217
+ const bId = Number.isFinite(b.r && b.r.id) ? b.r.id : 0;
218
+ return aId - bId;
93
219
  }
94
220
 
95
- // ── Main recall function ──────────────────────────
96
-
97
- /**
98
- * recall(query, records, opts) → Record | null
99
- *
100
- * opts.k — number of results to return (default 1)
101
- * opts.embedFn — async fn(texts[]) → number[][] for custom embeddings
102
- * opts.filter — fn(record) → bool for pre-filtering
103
- * opts.since — timestamp: only consider records newer than this
104
- * opts.mode — "exact" | "semantic" | "hybrid" (default "hybrid")
105
- */
106
221
  async function recall(query, records, opts = {}) {
107
222
  const results = await recallMany(query, records, { ...opts, k: opts.k || 1 });
108
223
  return results.length ? results[0] : null;
109
224
  }
110
225
 
111
226
  async function recallMany(query, records, opts = {}) {
112
- const k = opts.k || 5;
113
- const mode = opts.mode || 'hybrid';
227
+ const k = opts.k || 5;
228
+ const mode = opts.mode || "hybrid";
114
229
  const filter = opts.filter || null;
115
- const since = opts.since || null;
230
+ const since = opts.since || null;
231
+ const threshold = minScore(opts);
116
232
 
117
233
  let pool = records;
118
234
  if (filter) pool = pool.filter(filter);
119
- if (since) pool = pool.filter(r => r.ts >= since);
235
+ if (since) pool = pool.filter((record) => record.ts >= since);
120
236
  if (!pool.length) return [];
121
237
 
122
- // 1. Exact match shortcut
123
- const queryLower = query.toLowerCase();
124
- if (mode !== 'semantic') {
125
- const exact = pool.filter(r => r.content.toLowerCase().includes(queryLower));
126
- if (exact.length >= k && mode === 'exact') return exact.slice(0, k);
127
- if (exact.length && mode === 'exact') return exact;
238
+ const queryLower = String(query || "").toLowerCase();
239
+ if (mode !== "semantic") {
240
+ const exact = pool
241
+ .filter((record) => String(record.content || "").toLowerCase().includes(queryLower))
242
+ .map((record) => ({ ...record, _score: 1 }));
243
+
244
+ if (mode === "exact") return exact.slice(0, k);
245
+ if (exact.length >= k) return exact.slice(0, k);
128
246
  }
129
247
 
130
- // 2. Custom embeddings
131
- if (opts.embedFn && mode !== 'exact') {
248
+ if (opts.embedFn && mode !== "exact") {
132
249
  try {
133
- const texts = [query, ...pool.map(r => r.content)];
134
- const vectors = await opts.embedFn(texts);
135
- const qVec = vectors[0];
136
- const scored = pool.map((r, i) => ({ r, score: cosineArrays(qVec, vectors[i + 1]) }));
137
- scored.sort((a, b) => b.score - a.score);
138
- return scored.slice(0, k).map(s => ({ ...s.r, _score: s.score }));
139
- } catch (_) { /* fall through to TF-IDF */ }
250
+ const texts = [query, ...pool.map((record) => record.content)];
251
+ const vectors = await opts.embedFn(texts);
252
+ const qVec = vectors[0];
253
+ const scored = pool
254
+ .map((record, index) => ({ r: record, score: cosineArrays(qVec, vectors[index + 1]) }))
255
+ .filter((item) => item.score >= threshold);
256
+
257
+ scored.sort(sortScored);
258
+ return scored.slice(0, k).map((item) => ({ ...item.r, _score: item.score }));
259
+ } catch (_) {
260
+ // Fall through to local TF-IDF.
261
+ }
140
262
  }
141
263
 
142
- // 3. TF-IDF semantic
143
- const qTokens = tokenize(query);
144
- const docTokens = pool.map(r => tokenize(r.content));
145
- const corpus = [qTokens, ...docTokens];
146
- const idf = buildIDF(corpus);
147
-
148
- const qTF = tf(qTokens);
149
- const qVec = tfidfVec(qTF, idf);
150
-
151
- const scored = pool.map((r, i) => {
152
- const dVec = tfidfVec(tf(docTokens[i]), idf);
153
- const sem = cosine(qVec, dVec);
154
- const over = tokenOverlap(qTokens, docTokens[i]);
155
- // Blend: 70% semantic + 30% overlap, with recency boost
156
- const recency = Math.exp(-(Date.now() - r.ts) / (1000 * 60 * 60 * 24 * 7)); // 7-day half-life
157
- const score = (sem * 0.70) + (over * 0.20) + (recency * 0.10);
158
- return { r, score, _debug: { sem, over, recency } };
159
- });
160
-
161
- scored.sort((a, b) => b.score - a.score);
162
- return scored.slice(0, k).map(s => ({ ...s.r, _score: s.score }));
163
- }
264
+ const qTokens = tokenize(query);
265
+ if (!qTokens.length) return [];
164
266
 
165
- function cosineArrays(a, b) {
166
- let dot = 0, normA = 0, normB = 0;
167
- for (let i = 0; i < a.length; i++) {
168
- dot += a[i] * b[i];
169
- normA += a[i] * a[i];
170
- normB += b[i] * b[i];
171
- }
172
- const denom = Math.sqrt(normA) * Math.sqrt(normB);
173
- return denom === 0 ? 0 : dot / denom;
267
+ const docTokens = pool.map((record) => tokenize(record.content));
268
+ const idf = buildIDF([qTokens, ...docTokens]);
269
+ const qVec = tfidfVec(tf(qTokens), idf);
270
+
271
+ const scored = pool
272
+ .map((record, index) => {
273
+ const dVec = tfidfVec(tf(docTokens[index]), idf);
274
+ const semantic = cosine(qVec, dVec);
275
+ const overlap = tokenOverlap(qTokens, docTokens[index]);
276
+ const related = semantic > 0 || overlap > 0;
277
+ const recency = related
278
+ ? Math.exp(-(Date.now() - record.ts) / (1000 * 60 * 60 * 24 * 7))
279
+ : 0;
280
+ const score = semantic * 0.7 + overlap * 0.2 + recency * 0.1;
281
+
282
+ return { r: record, score, _debug: { semantic, overlap, recency } };
283
+ })
284
+ .filter((item) => item.score >= threshold);
285
+
286
+ scored.sort(sortScored);
287
+ return scored.slice(0, k).map((item) => ({ ...item.r, _score: item.score }));
174
288
  }
175
289
 
176
290
  module.exports = { recall, recallMany, tokenize };