monomind 2.2.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.
Files changed (91) hide show
  1. package/package.json +2 -2
  2. package/packages/@monomind/cli/.claude/commands/mastermind/createorg.md +24 -16
  3. package/packages/@monomind/cli/.claude/commands/mastermind/master.md +23 -7
  4. package/packages/@monomind/cli/.claude/commands/mastermind/runorg.md +3 -1
  5. package/packages/@monomind/cli/.claude/commands/mastermind/stoporg.md +6 -3
  6. package/packages/@monomind/cli/.claude/helpers/handlers/route-handler.cjs +6 -1
  7. package/packages/@monomind/cli/.claude/skills/mastermind-skills/code-quality-reviewer-prompt.md +3 -3
  8. package/packages/@monomind/cli/.claude/skills/mastermind-skills/createorg.md +19 -24
  9. package/packages/@monomind/cli/.claude/skills/mastermind-skills/debug.md +49 -4
  10. package/packages/@monomind/cli/.claude/skills/mastermind-skills/design.md +11 -10
  11. package/packages/@monomind/cli/.claude/skills/mastermind-skills/final-reviewer-prompt.md +144 -0
  12. package/packages/@monomind/cli/.claude/skills/mastermind-skills/implementer-prompt.md +9 -4
  13. package/packages/@monomind/cli/.claude/skills/mastermind-skills/org-settings.md +20 -2
  14. package/packages/@monomind/cli/.claude/skills/mastermind-skills/orgs.md +40 -19
  15. package/packages/@monomind/cli/.claude/skills/mastermind-skills/orgstatus.md +90 -41
  16. package/packages/@monomind/cli/.claude/skills/mastermind-skills/plan.md +19 -0
  17. package/packages/@monomind/cli/.claude/skills/mastermind-skills/skill-builder.md +171 -0
  18. package/packages/@monomind/cli/.claude/skills/mastermind-skills/spec-reviewer-prompt.md +19 -2
  19. package/packages/@monomind/cli/.claude/skills/mastermind-skills/stoporg.md +7 -5
  20. package/packages/@monomind/cli/.claude/skills/mastermind-skills/taskdev.md +85 -15
  21. package/packages/@monomind/cli/.claude/skills/mastermind-skills/tdd.md +30 -0
  22. package/packages/@monomind/cli/dist/src/autopilot-state.js +6 -4
  23. package/packages/@monomind/cli/dist/src/browser/dashboard/server.js +4 -1
  24. package/packages/@monomind/cli/dist/src/capabilities/manager.js +3 -1
  25. package/packages/@monomind/cli/dist/src/commands/agent-lifecycle.js +26 -0
  26. package/packages/@monomind/cli/dist/src/commands/agent-ops.js +28 -46
  27. package/packages/@monomind/cli/dist/src/commands/cleanup.d.ts +18 -0
  28. package/packages/@monomind/cli/dist/src/commands/cleanup.js +121 -0
  29. package/packages/@monomind/cli/dist/src/commands/doctor-project-checks.js +13 -4
  30. package/packages/@monomind/cli/dist/src/commands/hooks-routing-commands.js +34 -18
  31. package/packages/@monomind/cli/dist/src/commands/hooks-workers.js +8 -2
  32. package/packages/@monomind/cli/dist/src/commands/hooks.js +1 -1
  33. package/packages/@monomind/cli/dist/src/commands/mcp.js +4 -1
  34. package/packages/@monomind/cli/dist/src/commands/memory-admin.js +7 -1
  35. package/packages/@monomind/cli/dist/src/commands/org-observe.d.ts +18 -0
  36. package/packages/@monomind/cli/dist/src/commands/org-observe.js +295 -0
  37. package/packages/@monomind/cli/dist/src/commands/org.d.ts +9 -1
  38. package/packages/@monomind/cli/dist/src/commands/org.js +191 -9
  39. package/packages/@monomind/cli/dist/src/commands/performance.js +4 -1
  40. package/packages/@monomind/cli/dist/src/commands/platforms.js +4 -1
  41. package/packages/@monomind/cli/dist/src/commands/security-scan.js +8 -2
  42. package/packages/@monomind/cli/dist/src/commands/start.js +4 -1
  43. package/packages/@monomind/cli/dist/src/commands/swarm.js +3 -2
  44. package/packages/@monomind/cli/dist/src/index.js +13 -3
  45. package/packages/@monomind/cli/dist/src/init/executor.js +19 -5
  46. package/packages/@monomind/cli/dist/src/knowledge/document-pipeline.js +3 -2
  47. package/packages/@monomind/cli/dist/src/mcp-tools/agent-tools.d.ts +1 -0
  48. package/packages/@monomind/cli/dist/src/mcp-tools/agent-tools.js +63 -20
  49. package/packages/@monomind/cli/dist/src/mcp-tools/claims-tools.js +3 -1
  50. package/packages/@monomind/cli/dist/src/mcp-tools/config-tools.js +3 -2
  51. package/packages/@monomind/cli/dist/src/mcp-tools/daa-tools.js +3 -2
  52. package/packages/@monomind/cli/dist/src/mcp-tools/embeddings-tools.js +6 -2
  53. package/packages/@monomind/cli/dist/src/mcp-tools/github-tools.js +35 -10
  54. package/packages/@monomind/cli/dist/src/mcp-tools/hive-mind-tools.js +24 -7
  55. package/packages/@monomind/cli/dist/src/mcp-tools/hooks-embedding.js +13 -3
  56. package/packages/@monomind/cli/dist/src/mcp-tools/hooks-intelligence.js +9 -3
  57. package/packages/@monomind/cli/dist/src/mcp-tools/hooks-routing.d.ts +1 -0
  58. package/packages/@monomind/cli/dist/src/mcp-tools/hooks-routing.js +112 -10
  59. package/packages/@monomind/cli/dist/src/mcp-tools/hooks-tools.js +2 -1
  60. package/packages/@monomind/cli/dist/src/mcp-tools/monograph-tools.js +8 -2
  61. package/packages/@monomind/cli/dist/src/mcp-tools/performance-tools.js +3 -2
  62. package/packages/@monomind/cli/dist/src/mcp-tools/session-tools.js +15 -5
  63. package/packages/@monomind/cli/dist/src/mcp-tools/swarm-tools.js +4 -1
  64. package/packages/@monomind/cli/dist/src/mcp-tools/system-tools.js +6 -4
  65. package/packages/@monomind/cli/dist/src/mcp-tools/task-tools.d.ts +20 -0
  66. package/packages/@monomind/cli/dist/src/mcp-tools/task-tools.js +38 -16
  67. package/packages/@monomind/cli/dist/src/mcp-tools/terminal-tools.d.ts +2 -0
  68. package/packages/@monomind/cli/dist/src/mcp-tools/terminal-tools.js +41 -24
  69. package/packages/@monomind/cli/dist/src/memory/ewc-consolidation.js +6 -2
  70. package/packages/@monomind/cli/dist/src/memory/intelligence.js +32 -10
  71. package/packages/@monomind/cli/dist/src/memory/memory-bridge.js +81 -31
  72. package/packages/@monomind/cli/dist/src/memory/memory-crud.js +26 -2
  73. package/packages/@monomind/cli/dist/src/memory/memory-initializer.js +3 -2
  74. package/packages/@monomind/cli/dist/src/memory/memory-migrations.js +2 -0
  75. package/packages/@monomind/cli/dist/src/monovector/command-outcomes.js +4 -1
  76. package/packages/@monomind/cli/dist/src/orgrt/bus.js +4 -1
  77. package/packages/@monomind/cli/dist/src/orgrt/daemon.js +74 -19
  78. package/packages/@monomind/cli/dist/src/orgrt/inbox.js +3 -1
  79. package/packages/@monomind/cli/dist/src/orgrt/reporting.d.ts +40 -0
  80. package/packages/@monomind/cli/dist/src/orgrt/reporting.js +127 -0
  81. package/packages/@monomind/cli/dist/src/orgrt/session.d.ts +2 -0
  82. package/packages/@monomind/cli/dist/src/orgrt/session.js +10 -2
  83. package/packages/@monomind/cli/dist/src/orgrt/templates.d.ts +16 -0
  84. package/packages/@monomind/cli/dist/src/orgrt/templates.js +57 -0
  85. package/packages/@monomind/cli/dist/src/services/config-file-manager.js +10 -1
  86. package/packages/@monomind/cli/dist/src/transfer/storage/gcs.js +6 -2
  87. package/packages/@monomind/cli/dist/src/ui/orgs.html +32 -0
  88. package/packages/@monomind/cli/dist/src/ui/server.mjs +25 -0
  89. package/packages/@monomind/cli/dist/src/update/executor.js +3 -1
  90. package/packages/@monomind/cli/dist/src/update/rate-limiter.js +3 -1
  91. package/packages/@monomind/cli/package.json +3 -3
@@ -4,5 +4,25 @@
4
4
  * Tool definitions for task management with file persistence.
5
5
  */
6
6
  import { type MCPTool } from './types.js';
7
+ interface TaskRecord {
8
+ taskId: string;
9
+ type: string;
10
+ description: string;
11
+ priority: 'low' | 'normal' | 'high' | 'critical';
12
+ status: 'pending' | 'in_progress' | 'completed' | 'failed' | 'cancelled';
13
+ progress: number;
14
+ assignedTo: string[];
15
+ tags: string[];
16
+ createdAt: string;
17
+ startedAt: string | null;
18
+ completedAt: string | null;
19
+ result?: Record<string, unknown>;
20
+ }
21
+ interface TaskStore {
22
+ tasks: Record<string, TaskRecord>;
23
+ version: string;
24
+ }
25
+ export declare function loadTaskStore(): TaskStore;
7
26
  export declare const taskTools: MCPTool[];
27
+ export {};
8
28
  //# sourceMappingURL=task-tools.d.ts.map
@@ -23,7 +23,7 @@ function ensureTaskDir() {
23
23
  }
24
24
  }
25
25
  const MAX_TASK_STORE_BYTES = 50 * 1024 * 1024;
26
- function loadTaskStore() {
26
+ export function loadTaskStore() {
27
27
  try {
28
28
  const path = getTaskPath();
29
29
  if (existsSync(path)) {
@@ -36,8 +36,9 @@ function loadTaskStore() {
36
36
  return parsed;
37
37
  }
38
38
  }
39
- catch {
40
- // Return empty store on error
39
+ catch (e) {
40
+ if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
41
+ console.error('[loadTaskStore] failed to read/parse task store:', e);
41
42
  }
42
43
  return { tasks: {}, version: '3.0.0' };
43
44
  }
@@ -273,8 +274,9 @@ export const taskTools = [
273
274
  writeFileSync(tmpAgent1, JSON.stringify(agentStore, null, 2), 'utf-8');
274
275
  renameSync(tmpAgent1, agentStorePath);
275
276
  }
276
- catch {
277
- // Best-effort agent sync
277
+ catch (e) {
278
+ if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
279
+ console.error('[task_complete] agent store sync failed:', e);
278
280
  }
279
281
  }
280
282
  return {
@@ -372,15 +374,30 @@ export const taskTools = [
372
374
  return { taskId, error: 'Task not found' };
373
375
  }
374
376
  const previouslyAssigned = [...task.assignedTo];
375
- // Load agent store to sync worker state
377
+ // Load agent store to sync worker state. Distinguish "file doesn't
378
+ // exist yet" (safe to proceed with an empty store) from "file exists
379
+ // but failed to read/parse" (a real store that's momentarily corrupt
380
+ // or oversized) — the latter must NOT fall through to the unconditional
381
+ // write-back below, or a transient read failure silently wipes out
382
+ // every agent's real on-disk state.
376
383
  const agentStorePath = join(getMonomindDataRoot(), 'agents', 'store.json');
377
384
  let agentStore = { agents: {} };
385
+ let agentStoreReadFailed = false;
378
386
  try {
379
- if (existsSync(agentStorePath) && statSync(agentStorePath).size <= MAX_TASK_STORE_BYTES) {
380
- agentStore = JSON.parse(readFileSync(agentStorePath, 'utf-8'));
387
+ if (existsSync(agentStorePath)) {
388
+ if (statSync(agentStorePath).size <= MAX_TASK_STORE_BYTES) {
389
+ agentStore = JSON.parse(readFileSync(agentStorePath, 'utf-8'));
390
+ }
391
+ else {
392
+ agentStoreReadFailed = true;
393
+ }
381
394
  }
382
395
  }
383
- catch { /* ignore */ }
396
+ catch (e) {
397
+ agentStoreReadFailed = true;
398
+ if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
399
+ console.error('[task_assign] failed to read agent store — skipping agent-store sync to avoid overwriting it with an empty one:', e);
400
+ }
384
401
  // Reject IDs that would mutate Object.prototype when used as a key in
385
402
  // the JSON-loaded plain object `agentStore.agents`.
386
403
  const FORBIDDEN_AGENT_IDS = new Set(['__proto__', 'constructor', 'prototype']);
@@ -422,19 +439,24 @@ export const taskTools = [
422
439
  }
423
440
  }
424
441
  saveTaskStore(store);
425
- // Save agent store
426
- const agentDir = join(getMonomindDataRoot(), 'agents');
427
- if (!existsSync(agentDir)) {
428
- mkdirSync(agentDir, { recursive: true });
442
+ // Save agent store — skipped when the read above failed, so a
443
+ // transient corrupt/oversized store.json is left alone instead of
444
+ // being overwritten with an empty one.
445
+ if (!agentStoreReadFailed) {
446
+ const agentDir = join(getMonomindDataRoot(), 'agents');
447
+ if (!existsSync(agentDir)) {
448
+ mkdirSync(agentDir, { recursive: true });
449
+ }
450
+ const tmpAgent2 = `${agentStorePath}.${process.pid}.${Date.now()}.tmp`;
451
+ writeFileSync(tmpAgent2, JSON.stringify(agentStore, null, 2), 'utf-8');
452
+ renameSync(tmpAgent2, agentStorePath);
429
453
  }
430
- const tmpAgent2 = `${agentStorePath}.${process.pid}.${Date.now()}.tmp`;
431
- writeFileSync(tmpAgent2, JSON.stringify(agentStore, null, 2), 'utf-8');
432
- renameSync(tmpAgent2, agentStorePath);
433
454
  return {
434
455
  taskId: task.taskId,
435
456
  assignedTo: task.assignedTo,
436
457
  previouslyAssigned,
437
458
  status: task.status,
459
+ ...(agentStoreReadFailed ? { agentStoreSyncSkipped: true } : {}),
438
460
  };
439
461
  },
440
462
  },
@@ -4,5 +4,7 @@
4
4
  * Terminal session management with real command execution.
5
5
  */
6
6
  import type { MCPTool } from './types.js';
7
+ /** Returns a copy of `env` with secret-shaped variable names removed. */
8
+ export declare function filterSecretEnvVars(env: NodeJS.ProcessEnv): Record<string, string>;
7
9
  export declare const terminalTools: MCPTool[];
8
10
  //# sourceMappingURL=terminal-tools.d.ts.map
@@ -1,9 +1,10 @@
1
1
  import { getProjectCwd } from './types.js';
2
- import { existsSync, readFileSync, statSync, writeFileSync, renameSync, mkdirSync } from 'node:fs';
2
+ import { existsSync, readFileSync, statSync } from 'node:fs';
3
3
  import { join, resolve } from 'node:path';
4
4
  import { homedir } from 'node:os';
5
5
  import { execSync } from 'node:child_process';
6
6
  import { randomBytes } from 'node:crypto';
7
+ import { writeJsonFileAtomic } from '../utils/json-file.js';
7
8
  // Storage paths
8
9
  const STORAGE_DIR = '.monomind';
9
10
  const TERMINAL_DIR = 'terminals';
@@ -24,7 +25,7 @@ const TERMINAL_FILE = 'store.json';
24
25
  // is layered on *after* the filtered process.env below).
25
26
  const SECRET_ENV_NAME_PATTERN = /(KEY|TOKEN|SECRET|PASSWORD|PASSWD|CREDENTIAL|_AUTH$|^AUTH_|PRIVATE_KEY|ACCESS_KEY)/i;
26
27
  /** Returns a copy of `env` with secret-shaped variable names removed. */
27
- function filterSecretEnvVars(env) {
28
+ export function filterSecretEnvVars(env) {
28
29
  const filtered = {};
29
30
  for (const [k, v] of Object.entries(env)) {
30
31
  if (v === undefined)
@@ -41,32 +42,39 @@ function getTerminalDir() {
41
42
  function getTerminalPath() {
42
43
  return join(getTerminalDir(), TERMINAL_FILE);
43
44
  }
44
- function ensureTerminalDir() {
45
- const dir = getTerminalDir();
46
- if (!existsSync(dir)) {
47
- mkdirSync(dir, { recursive: true });
48
- }
49
- }
50
45
  const MAX_TERMINAL_STORE_BYTES = 10 * 1024 * 1024; // 10 MB
51
- function loadTerminalStore() {
46
+ // Same hardened-loader convention as agent-tools.ts's loadAgentStoreOrNull:
47
+ // handlers that mutate and save must use the null-aware variant, since
48
+ // treating a corrupt/oversized store as empty and then saving that back
49
+ // would silently wipe every real session. Also guards against a __proto__
50
+ // key in the parsed JSON (agent-tools.ts's loader does the same). Can't
51
+ // delegate to utils/json-file.ts's readJsonFileSync here — it collapses
52
+ // "file absent" and "file corrupt" into the same fallback value, but this
53
+ // loader must tell them apart (absent → empty default is safe to build on
54
+ // and save; corrupt → null, refuse to save over it).
55
+ function loadTerminalStoreOrNull() {
52
56
  try {
53
57
  const path = getTerminalPath();
54
- if (existsSync(path) && statSync(path).size <= MAX_TERMINAL_STORE_BYTES) {
55
- return JSON.parse(readFileSync(path, 'utf-8'));
56
- }
58
+ if (!existsSync(path))
59
+ return { sessions: {}, version: '3.0.0' };
60
+ if (statSync(path).size > MAX_TERMINAL_STORE_BYTES)
61
+ return null;
62
+ const parsed = JSON.parse(readFileSync(path, 'utf-8'));
63
+ if (parsed && typeof parsed === 'object' && Object.prototype.hasOwnProperty.call(parsed, '__proto__'))
64
+ return null;
65
+ return parsed;
57
66
  }
58
- catch {
59
- // Return empty store
67
+ catch (e) {
68
+ if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
69
+ console.error('[loadTerminalStore] terminal store unreadable/corrupt:', e);
70
+ return null;
60
71
  }
61
- return { sessions: {}, version: '3.0.0' };
72
+ }
73
+ function loadTerminalStore() {
74
+ return loadTerminalStoreOrNull() ?? { sessions: {}, version: '3.0.0' };
62
75
  }
63
76
  function saveTerminalStore(store) {
64
- ensureTerminalDir();
65
- // Unique tmp filename so concurrent handler invocations cannot clobber each
66
- // other's .tmp mid-write (which would produce a partial JSON on rename).
67
- const tmpPath = `${getTerminalPath()}.${process.pid}.${Date.now()}.tmp`;
68
- writeFileSync(tmpPath, JSON.stringify(store, null, 2), 'utf-8');
69
- renameSync(tmpPath, getTerminalPath());
77
+ writeJsonFileAtomic(getTerminalPath(), store);
70
78
  }
71
79
  export const terminalTools = [
72
80
  {
@@ -82,7 +90,10 @@ export const terminalTools = [
82
90
  },
83
91
  },
84
92
  handler: async (input) => {
85
- const store = loadTerminalStore();
93
+ const store = loadTerminalStoreOrNull();
94
+ if (!store) {
95
+ return { success: false, error: 'Terminal store is unreadable/corrupt — refusing to create a session to avoid overwriting real session data.' };
96
+ }
86
97
  const MAX_SESSIONS = 1000;
87
98
  if (Object.keys(store.sessions).length >= MAX_SESSIONS) {
88
99
  return { success: false, error: 'Session limit reached' };
@@ -167,7 +178,10 @@ export const terminalTools = [
167
178
  required: ['command'],
168
179
  },
169
180
  handler: async (input) => {
170
- const store = loadTerminalStore();
181
+ const store = loadTerminalStoreOrNull();
182
+ if (!store) {
183
+ return { success: false, error: 'Terminal store is unreadable/corrupt — refusing to execute to avoid overwriting real session data.' };
184
+ }
171
185
  const sessionId = input.sessionId;
172
186
  // Cap command: the metacharacter regex check at line 220 is O(n), and the
173
187
  // raw command is stored verbatim in session history (up to 200 entries).
@@ -313,7 +327,10 @@ export const terminalTools = [
313
327
  required: ['sessionId'],
314
328
  },
315
329
  handler: async (input) => {
316
- const store = loadTerminalStore();
330
+ const store = loadTerminalStoreOrNull();
331
+ if (!store) {
332
+ return { success: false, error: 'Terminal store is unreadable/corrupt — refusing to close a session to avoid overwriting real session data.' };
333
+ }
317
334
  const sessionId = input.sessionId;
318
335
  const FORBIDDEN_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
319
336
  if (!sessionId || FORBIDDEN_KEYS.has(sessionId)) {
@@ -70,8 +70,10 @@ export class EWCConsolidator {
70
70
  this.initialized = true;
71
71
  return true;
72
72
  }
73
- catch {
73
+ catch (e) {
74
74
  // Start fresh if no persisted state
75
+ if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
76
+ console.error('[ewc-consolidation] failed to load persisted state, starting fresh:', e);
75
77
  this.initialized = true;
76
78
  return true;
77
79
  }
@@ -544,8 +546,10 @@ export class EWCConsolidator {
544
546
  fs.writeFileSync(tmp, JSON.stringify(state, null, 2));
545
547
  fs.renameSync(tmp, this.config.storagePath);
546
548
  }
547
- catch {
549
+ catch (e) {
548
550
  // Silently fail - persistence is best-effort
551
+ if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
552
+ console.error('[ewc-consolidation] failed to persist state:', e);
549
553
  }
550
554
  }
551
555
  /**
@@ -454,8 +454,10 @@ class LocalReasoningBank {
454
454
  // ReasoningBank via intelligence.ts's public API, so no separate
455
455
  // models.json bridge is needed.
456
456
  }
457
- catch {
457
+ catch (e) {
458
458
  // Ignore load errors, start fresh
459
+ if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
460
+ console.error('[intelligence] failed to load patterns.json, starting fresh:', e);
459
461
  }
460
462
  }
461
463
  /**
@@ -680,8 +682,10 @@ function loadPersistedStats() {
680
682
  }
681
683
  }
682
684
  }
683
- catch {
685
+ catch (e) {
684
686
  // Ignore load errors, start fresh
687
+ if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
688
+ console.error('[intelligence] failed to load stats.json, starting fresh:', e);
685
689
  }
686
690
  }
687
691
  /**
@@ -693,8 +697,10 @@ function savePersistedStats() {
693
697
  const path = getStatsPath();
694
698
  writeFileSync(path, JSON.stringify(globalStats, null, 2), 'utf-8');
695
699
  }
696
- catch {
700
+ catch (e) {
697
701
  // Ignore save errors
702
+ if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
703
+ console.error('[intelligence] failed to save stats.json:', e);
698
704
  }
699
705
  }
700
706
  // ============================================================================
@@ -762,7 +768,11 @@ async function _doInitializeIntelligence(config) {
762
768
  }
763
769
  }
764
770
  }
765
- catch { /* neural patterns file unreadable — skip */ }
771
+ catch (e) {
772
+ /* neural patterns file unreadable — skip */
773
+ if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
774
+ console.error('[intelligence] failed to seed patterns.json into ReasoningBank:', e);
775
+ }
766
776
  }
767
777
  // Seed SONA routing patterns into the ReasoningBank so keyword-based
768
778
  // routing knowledge from .swarm/sona-patterns.json participates in
@@ -890,7 +900,9 @@ export async function recordStep(step) {
890
900
  globalStats.trajectoriesRecorded++;
891
901
  return true;
892
902
  }
893
- catch {
903
+ catch (e) {
904
+ if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
905
+ console.error('[intelligence] recordStep failed:', e);
894
906
  return false;
895
907
  }
896
908
  }
@@ -923,7 +935,9 @@ export async function recordTrajectory(steps, verdict) {
923
935
  savePersistedStats();
924
936
  return true;
925
937
  }
926
- catch {
938
+ catch (e) {
939
+ if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
940
+ console.error('[intelligence] recordTrajectory failed:', e);
927
941
  return false;
928
942
  }
929
943
  }
@@ -980,7 +994,9 @@ export async function findSimilarPatterns(query, options) {
980
994
  similarity: r.similarity ?? r.confidence ?? 0.5
981
995
  }));
982
996
  }
983
- catch {
997
+ catch (e) {
998
+ if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
999
+ console.error('[intelligence] findSimilarPatterns failed:', e);
984
1000
  return [];
985
1001
  }
986
1002
  }
@@ -1030,7 +1046,9 @@ export async function endTrajectoryWithVerdict(verdict) {
1030
1046
  savePersistedStats();
1031
1047
  return result;
1032
1048
  }
1033
- catch {
1049
+ catch (e) {
1050
+ if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
1051
+ console.error('[intelligence] endTrajectoryWithVerdict failed:', e);
1034
1052
  return null;
1035
1053
  }
1036
1054
  }
@@ -1052,7 +1070,9 @@ export async function distillLearning() {
1052
1070
  savePersistedStats();
1053
1071
  return result;
1054
1072
  }
1055
- catch {
1073
+ catch (e) {
1074
+ if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
1075
+ console.error('[intelligence] distillLearning failed:', e);
1056
1076
  return null;
1057
1077
  }
1058
1078
  }
@@ -1184,7 +1204,9 @@ function loadSonaRoutingPatterns() {
1184
1204
  }
1185
1205
  return results;
1186
1206
  }
1187
- catch {
1207
+ catch (e) {
1208
+ if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
1209
+ console.error('[intelligence] failed to load .swarm/sona-patterns.json:', e);
1188
1210
  return [];
1189
1211
  }
1190
1212
  }
@@ -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 {
@@ -148,27 +156,43 @@ async function getBackend(dbPath) {
148
156
  };
149
157
  _embedder = embeddingGenerator;
150
158
  }
151
- catch { /* embeddings unavailable — store and search without vectors */ }
152
- const backend = new LanceDBBackend({
153
- // Always route through getDbPath's traversal guard dbPath here can
154
- // originate from a caller/tool-supplied value, and getDbPath(undefined)
155
- // already returns the correct default, so there's no case where
156
- // calling it unconditionally changes behavior for the no-path case.
157
- dbPath: getDbPath(dbPath),
158
- vectorDimension: BRIDGE_EMBEDDING_DIMS,
159
+ catch (e) {
160
+ if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
161
+ console.error('[memory-bridge] embedding model failed to loadstore and search without vectors:', e);
162
+ }
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',
159
175
  embeddingGenerator,
160
- enableFts: false,
161
- nProbes: 20,
162
- });
176
+ };
163
177
  const origLog = console.log;
164
178
  console.log = (...args) => {
165
179
  const msg = String(args[0] ?? '');
166
- if (msg.includes('Transformers.js') || msg.includes('[LanceDB]') || msg.includes('Loading model'))
180
+ if (msg.includes('Transformers.js') || msg.includes('Loading model'))
167
181
  return;
168
182
  origLog.apply(console, args);
169
183
  };
184
+ let backend;
170
185
  try {
171
- 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
+ }
172
196
  }
173
197
  finally {
174
198
  console.log = origLog;
@@ -212,7 +236,10 @@ export async function bridgeStoreEntry(options) {
212
236
  embedding = await _embedder(value);
213
237
  embeddingInfo = { dimensions: embedding.length, model: BRIDGE_EMBEDDING_MODEL };
214
238
  }
215
- catch { /* store without embedding */ }
239
+ catch (e) {
240
+ if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
241
+ console.error('[memory-bridge] embedding generation failed — storing entry without embedding:', e);
242
+ }
216
243
  }
217
244
  const mod = await import('@monoes/memory');
218
245
  const entry = mod.createDefaultEntry({
@@ -233,7 +260,10 @@ export async function bridgeStoreEntry(options) {
233
260
  if (existing)
234
261
  await backend.delete(existing.id);
235
262
  }
236
- catch { /* non-fatal */ }
263
+ catch (e) {
264
+ if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
265
+ console.error('[memory-bridge] upsert failed to delete existing entry — may create a duplicate:', e);
266
+ }
237
267
  }
238
268
  // Dedup gate: skip if a near-duplicate already exists
239
269
  const automemCfg = getAutomemConfig();
@@ -247,6 +277,7 @@ export async function bridgeStoreEntry(options) {
247
277
  catch { /* non-fatal — store anyway */ }
248
278
  }
249
279
  await backend.store(entry);
280
+ await flushBackend(backend);
250
281
  return { success: true, id, embedding: embeddingInfo };
251
282
  }
252
283
  catch (err) {
@@ -293,20 +324,31 @@ export async function bridgeSearchEntries(options) {
293
324
  namespace: namespace ?? 'default',
294
325
  limit: 50000,
295
326
  });
296
- const queryLower = queryStr.toLowerCase();
297
- results = entries
298
- .filter((e) => (e.content || '').toLowerCase().includes(queryLower)
299
- || (e.key || '').toLowerCase().includes(queryLower))
300
- .slice(0, limit)
301
- .map((e) => ({
302
- id: e.id,
303
- key: e.key,
304
- content: e.content || '',
305
- score: 0.5,
306
- namespace: e.namespace,
307
- provenance: 'keyword',
308
- _createdAt: e.createdAt || 0,
309
- }));
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
+ }
310
352
  searchMethod = 'keyword';
311
353
  }
312
354
  // Filter stale entries based on automem config — skip for knowledge
@@ -404,6 +446,8 @@ export async function bridgeDeleteEntry(options) {
404
446
  if (entry)
405
447
  deleted = await backend.delete(entry.id);
406
448
  }
449
+ if (deleted)
450
+ await flushBackend(backend);
407
451
  return { success: true, deleted };
408
452
  }
409
453
  catch {
@@ -644,7 +688,10 @@ export async function bridgeSessionEnd(options) {
644
688
  try {
645
689
  data = JSON.parse(existing.content);
646
690
  }
647
- catch { /* use empty */ }
691
+ catch (e) {
692
+ if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
693
+ console.error('[memory-bridge] session content failed to parse — ending session with empty prior state:', e);
694
+ }
648
695
  await backend.update(existing.id, {
649
696
  content: JSON.stringify({
650
697
  ...data,
@@ -655,6 +702,7 @@ export async function bridgeSessionEnd(options) {
655
702
  }),
656
703
  tags: ['session', 'ended'],
657
704
  });
705
+ await flushBackend(backend);
658
706
  }
659
707
  return { success: true };
660
708
  }
@@ -746,6 +794,8 @@ export async function bridgeConsolidate(params) {
746
794
  deleted++;
747
795
  }
748
796
  }
797
+ if (deleted)
798
+ await flushBackend(backend);
749
799
  return { success: true, consolidated: deleted };
750
800
  }
751
801
  catch {
@@ -238,8 +238,32 @@ export async function storeEntry(options) {
238
238
  }
239
239
  const fileBuffer = fs.readFileSync(dbPath);
240
240
  const db = new SQL.Database(fileBuffer);
241
- const id = `entry_${Date.now()}_${Math.random().toString(36).substring(7)}`;
241
+ // memory_entries has no UNIQUE constraint on (key, namespace) — only
242
+ // `id TEXT PRIMARY KEY`. `INSERT OR REPLACE` only replaces a row when
243
+ // its PRIMARY KEY collides, so generating a fresh id on every call (as
244
+ // this used to do unconditionally) meant upsert never actually matched
245
+ // the existing row for a given key+namespace — it silently inserted a
246
+ // duplicate instead of replacing it. When upserting, look up the
247
+ // existing row's real id first and reuse it so the replace actually
248
+ // collides; only mint a new id when no existing row is found.
249
+ let id = `entry_${Date.now()}_${Math.random().toString(36).substring(7)}`;
250
+ let existingCreatedAt = null;
251
+ if (upsert) {
252
+ // Only match active rows — a soft-deleted row's key+namespace should
253
+ // not be resurrected by an unrelated upsert; that would both restore
254
+ // history a delete intentionally cleared and mask the deletion.
255
+ const existingIdResult = db.exec("SELECT id, created_at FROM memory_entries WHERE key = ? AND namespace = ? AND status = 'active' LIMIT 1", [key, namespace]);
256
+ const existingRow = existingIdResult[0]?.values?.[0];
257
+ const existingId = existingRow?.[0];
258
+ if (typeof existingId === 'string') {
259
+ id = existingId;
260
+ const createdAt = existingRow?.[1];
261
+ if (typeof createdAt === 'number')
262
+ existingCreatedAt = createdAt;
263
+ }
264
+ }
242
265
  const now = Date.now();
266
+ const createdAt = existingCreatedAt ?? now;
243
267
  let embeddingJson = null;
244
268
  let embeddingDimensions = null;
245
269
  let embeddingModel = null;
@@ -270,7 +294,7 @@ export async function storeEntry(options) {
270
294
  embeddingModel,
271
295
  tags.length > 0 ? JSON.stringify(tags) : null,
272
296
  '{}',
273
- now,
297
+ createdAt,
274
298
  now,
275
299
  ttl ? now + (ttl * 1000) : null
276
300
  ]);
@@ -110,8 +110,9 @@ async function activateControllerRegistry(dbPath, verbose) {
110
110
  console.log(`ControllerRegistry: ${activated.length} controllers activated`);
111
111
  }
112
112
  }
113
- catch {
114
- // ControllerRegistry activation is best-effort
113
+ catch (e) {
114
+ if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
115
+ console.error('[activateControllers] ControllerRegistry activation failed:', e);
115
116
  }
116
117
  return { activated, failed, initTimeMs: performance.now() - startTime };
117
118
  }
@@ -57,6 +57,8 @@ export async function ensureSchemaColumns(dbPath) {
57
57
  }
58
58
  catch (e) {
59
59
  // Column might already exist or other error - continue
60
+ if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
61
+ console.error(`[ensureSchemaColumns] failed to add column '${col.name}':`, e);
60
62
  }
61
63
  }
62
64
  }
@@ -41,7 +41,10 @@ export async function recordCommand(baseDir, cmd) {
41
41
  await fs.writeFile(path, lines.slice(-MAX_COMMAND_RECORDS).join('\n') + '\n', 'utf8');
42
42
  }
43
43
  }
44
- catch { /* non-fatal */ }
44
+ catch (e) {
45
+ if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
46
+ console.error('[recordCommand] failed to record command outcome:', e);
47
+ }
45
48
  }
46
49
  /**
47
50
  * Derive a measured success signal from recent command outcomes.