monomind 2.3.0 → 2.3.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "monomind",
3
- "version": "2.3.0",
3
+ "version": "2.3.1",
4
4
  "description": "Open-source CLI extension for Claude Code. Adds an MCP server with a codebase knowledge graph, persistent memory, multi-agent coordination, and reusable slash commands. MIT licensed, runs locally, no data leaves your machine.",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -358,7 +358,12 @@ module.exports = {
358
358
  if (fs.existsSync(secAuditFile) && fs.statSync(secAuditFile).size < 32768) {
359
359
  var secData = JSON.parse(fs.readFileSync(secAuditFile, 'utf-8'));
360
360
  if (secData && secData.findings && secData.findings.length > 0) {
361
- console.log('[SECURITY] ' + secData.findings.length + ' findings from background scan. Review .monomind/metrics/security-audit.json');
361
+ var serious = secData.findings.filter(function (f) { return f && (f.severity === 'high' || f.severity === 'medium'); });
362
+ if (serious.length > 0) {
363
+ console.log('[SECURITY] ' + serious.length + ' finding(s) from background scan. Review .monomind/metrics/security-audit.json');
364
+ } else {
365
+ console.log('[AUDIT] ' + secData.findings.length + ' low-severity note(s) (architecture heuristics, not vulnerabilities) — .monomind/metrics/security-audit.json');
366
+ }
362
367
  }
363
368
  }
364
369
  // Codebase map top files (high-centrality god nodes from monograph)
@@ -157,6 +157,8 @@ Implementer subagents report one of four statuses. Handle each appropriately:
157
157
 
158
158
  **Never** ignore an escalation or force the same model to retry without changes. If the implementer said it's stuck, something needs to change.
159
159
 
160
+ **Async dispatches:** reviewer and implementer subagents may complete asynchronously even when dispatched synchronously. Wait for each result before acting on it — never proceed to the next gate, and never touch the files a dispatched subagent is working on, while its result is pending.
161
+
160
162
  ---
161
163
 
162
164
  ## Handling Reviewer ⚠️ Items
@@ -30,6 +30,7 @@ const KEEP_CONFIG_PATHS = [
30
30
  join('.claude', 'settings.json'),
31
31
  ];
32
32
  /** Scratch pruning (--scratch): taskdev handoff files and loop state. */
33
+ // monolean: manual flag only — upgrade path: invoke from the `cache` background worker so crashed-run scratch is pruned without anyone remembering the flag
33
34
  const SCRATCH_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000; // taskdev scratch older than this is stale
34
35
  const LOOP_STALE_GRACE_MS = 24 * 60 * 60 * 1000; // a live loop reschedules every <=1h; overdue by a day = abandoned
35
36
  /**
@@ -121,6 +121,15 @@ let bridgeAvailable = null;
121
121
  let _embedder = null;
122
122
  const MAX_INIT_ATTEMPTS = 3;
123
123
  let initAttempts = 0;
124
+ /** Flush after mutations: the sql.js fallback backend is in-memory WASM and
125
+ * only reaches disk via persist(); the CLI process is short-lived, so waiting
126
+ * for an auto-persist interval would lose writes. No-op on better-sqlite3. */
127
+ async function flushBackend(backend) {
128
+ try {
129
+ await backend?.persist?.();
130
+ }
131
+ catch { /* best effort */ }
132
+ }
124
133
  async function getBackend(dbPath) {
125
134
  if (bridgeAvailable === false)
126
135
  return null;
@@ -134,7 +143,6 @@ async function getBackend(dbPath) {
134
143
  backendPromise = (async () => {
135
144
  try {
136
145
  const mod = await import('@monoes/memory');
137
- const { LanceDBBackend } = mod;
138
146
  // Try to create embedding generator from HuggingFace transformers
139
147
  let embeddingGenerator;
140
148
  try {
@@ -152,26 +160,39 @@ async function getBackend(dbPath) {
152
160
  if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
153
161
  console.error('[memory-bridge] embedding model failed to load — store and search without vectors:', e);
154
162
  }
155
- const backend = new LanceDBBackend({
156
- // Always route through getDbPath's traversal guarddbPath here can
157
- // originate from a caller/tool-supplied value, and getDbPath(undefined)
158
- // already returns the correct default, so there's no case where
159
- // calling it unconditionally changes behavior for the no-path case.
160
- dbPath: getDbPath(dbPath),
161
- vectorDimension: BRIDGE_EMBEDDING_DIMS,
163
+ // Local SQLite engine (LanceDB replaced 2026-07): better-sqlite3 when its
164
+ // native binding loads, sql.js (pure WASM) otherwise both persist text
165
+ // AND embeddings, so vectors are always recomputable/derivable data.
166
+ // getDbPath keeps its traversal guard and directory semantics; the SQLite
167
+ // file lives inside that directory.
168
+ const dir = getDbPath(dbPath);
169
+ fs.mkdirSync(dir, { recursive: true });
170
+ const cfg = {
171
+ databasePath: path.join(dir, 'memory.db'),
172
+ walMode: true,
173
+ optimize: true,
174
+ defaultNamespace: 'default',
162
175
  embeddingGenerator,
163
- enableFts: false,
164
- nProbes: 20,
165
- });
176
+ };
166
177
  const origLog = console.log;
167
178
  console.log = (...args) => {
168
179
  const msg = String(args[0] ?? '');
169
- if (msg.includes('Transformers.js') || msg.includes('[LanceDB]') || msg.includes('Loading model'))
180
+ if (msg.includes('Transformers.js') || msg.includes('Loading model'))
170
181
  return;
171
182
  origLog.apply(console, args);
172
183
  };
184
+ let backend;
173
185
  try {
174
- await backend.initialize();
186
+ try {
187
+ backend = new mod.SQLiteBackend(cfg);
188
+ await backend.initialize();
189
+ }
190
+ catch (e) {
191
+ if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
192
+ console.error('[memory-bridge] better-sqlite3 unavailable — using sql.js backend:', e);
193
+ backend = new mod.SqlJsBackend(cfg);
194
+ await backend.initialize();
195
+ }
175
196
  }
176
197
  finally {
177
198
  console.log = origLog;
@@ -256,6 +277,7 @@ export async function bridgeStoreEntry(options) {
256
277
  catch { /* non-fatal — store anyway */ }
257
278
  }
258
279
  await backend.store(entry);
280
+ await flushBackend(backend);
259
281
  return { success: true, id, embedding: embeddingInfo };
260
282
  }
261
283
  catch (err) {
@@ -302,20 +324,31 @@ export async function bridgeSearchEntries(options) {
302
324
  namespace: namespace ?? 'default',
303
325
  limit: 50000,
304
326
  });
305
- const queryLower = queryStr.toLowerCase();
306
- results = entries
307
- .filter((e) => (e.content || '').toLowerCase().includes(queryLower)
308
- || (e.key || '').toLowerCase().includes(queryLower))
309
- .slice(0, limit)
310
- .map((e) => ({
311
- id: e.id,
312
- key: e.key,
313
- content: e.content || '',
314
- score: 0.5,
315
- namespace: e.namespace,
316
- provenance: 'keyword',
317
- _createdAt: e.createdAt || 0,
318
- }));
327
+ // Token-based matching, not whole-phrase substring: "semantic test" must
328
+ // match an entry keyed "semantic-test" (the old .includes(query) required
329
+ // the exact phrase — including its whitespace — to appear verbatim).
330
+ // Score = fraction of query tokens present in key+content.
331
+ const tokens = queryStr.toLowerCase().split(/[^a-z0-9]+/).filter(t => t.length > 1);
332
+ if (tokens.length) {
333
+ results = entries
334
+ .map((e) => {
335
+ const haystack = `${e.key || ''} ${e.content || ''}`.toLowerCase();
336
+ const hits = tokens.filter(t => haystack.includes(t)).length;
337
+ return { e, score: hits / tokens.length };
338
+ })
339
+ .filter((x) => x.score > 0)
340
+ .sort((a, b) => b.score - a.score)
341
+ .slice(0, limit)
342
+ .map(({ e, score }) => ({
343
+ id: e.id,
344
+ key: e.key,
345
+ content: e.content || '',
346
+ score: Math.min(0.9, 0.3 + score * 0.6),
347
+ namespace: e.namespace,
348
+ provenance: `keyword:${score.toFixed(2)}`,
349
+ _createdAt: e.createdAt || 0,
350
+ }));
351
+ }
319
352
  searchMethod = 'keyword';
320
353
  }
321
354
  // Filter stale entries based on automem config — skip for knowledge
@@ -413,6 +446,8 @@ export async function bridgeDeleteEntry(options) {
413
446
  if (entry)
414
447
  deleted = await backend.delete(entry.id);
415
448
  }
449
+ if (deleted)
450
+ await flushBackend(backend);
416
451
  return { success: true, deleted };
417
452
  }
418
453
  catch {
@@ -667,6 +702,7 @@ export async function bridgeSessionEnd(options) {
667
702
  }),
668
703
  tags: ['session', 'ended'],
669
704
  });
705
+ await flushBackend(backend);
670
706
  }
671
707
  return { success: true };
672
708
  }
@@ -758,6 +794,8 @@ export async function bridgeConsolidate(params) {
758
794
  deleted++;
759
795
  }
760
796
  }
797
+ if (deleted)
798
+ await flushBackend(backend);
761
799
  return { success: true, consolidated: deleted };
762
800
  }
763
801
  catch {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@monoes/monomindcli",
3
- "version": "2.3.0",
3
+ "version": "2.3.1",
4
4
  "type": "module",
5
5
  "description": "CLI engine for Monomind — an open-source MCP server that extends Claude Code with a codebase knowledge graph (tree-sitter + SQLite), persistent memory, multi-agent task coordination, and session hooks. MIT licensed, fully local.",
6
6
  "main": "dist/src/index.js",
@@ -105,7 +105,7 @@
105
105
  },
106
106
  "optionalDependencies": {
107
107
  "@huggingface/transformers": "^3.8.1",
108
- "@monoes/memory": "^1.0.5",
108
+ "@monoes/memory": "^1.0.6",
109
109
  "@monomind/hooks": "*",
110
110
  "@monomind/mcp": "*",
111
111
  "@monomind/routing": "*",