moflo 4.10.7 → 4.10.8

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 (32) hide show
  1. package/.claude/guidance/shipped/moflo-cli-reference.md +1 -1
  2. package/.claude/guidance/shipped/moflo-memory-strategy.md +1 -1
  3. package/.claude/guidance/shipped/moflo-yaml-reference.md +4 -4
  4. package/.claude/skills/memory-optimization/SKILL.md +1 -1
  5. package/.claude/skills/memory-patterns/SKILL.md +3 -3
  6. package/.claude/skills/vector-search/SKILL.md +2 -2
  7. package/README.md +5 -5
  8. package/bin/lib/daemon-port.mjs +66 -0
  9. package/dist/src/cli/commands/daemon.js +31 -10
  10. package/dist/src/cli/commands/doctor-checks-config.js +139 -1
  11. package/dist/src/cli/commands/doctor-fixes.js +75 -2
  12. package/dist/src/cli/commands/doctor-registry.js +10 -1
  13. package/dist/src/cli/commands/memory.js +8 -8
  14. package/dist/src/cli/commands/neural.js +8 -6
  15. package/dist/src/cli/config/moflo-config.js +68 -3
  16. package/dist/src/cli/index.js +18 -19
  17. package/dist/src/cli/init/moflo-yaml-template.js +1 -1
  18. package/dist/src/cli/mcp-server.js +59 -10
  19. package/dist/src/cli/mcp-tools/memory-tools.js +46 -27
  20. package/dist/src/cli/memory/auto-memory-bridge.js +1 -1
  21. package/dist/src/cli/memory/controllers/attestation-log.js +1 -1
  22. package/dist/src/cli/memory/controllers/causal-graph.js +1 -1
  23. package/dist/src/cli/memory/daemon-write-client.js +178 -49
  24. package/dist/src/cli/memory/database-provider.js +58 -3
  25. package/dist/src/cli/memory/intelligence.js +54 -26
  26. package/dist/src/cli/memory/memory-initializer.js +21 -11
  27. package/dist/src/cli/services/daemon-dashboard.js +94 -25
  28. package/dist/src/cli/services/daemon-lock.js +390 -3
  29. package/dist/src/cli/services/daemon-port.js +217 -0
  30. package/dist/src/cli/version.js +1 -1
  31. package/package.json +2 -2
  32. package/dist/src/cli/config-adapter.js +0 -182
@@ -103,7 +103,7 @@ const storeCommand = {
103
103
  size: Buffer.byteLength(value, 'utf8')
104
104
  };
105
105
  output.printInfo(`Storing in ${namespace}/${key}...`);
106
- // Use direct sql.js storage with automatic embedding generation
106
+ // Use direct memory-backend storage with automatic embedding generation
107
107
  try {
108
108
  const { storeEntry } = await import('../memory/memory-initializer.js');
109
109
  if (asVector) {
@@ -175,7 +175,7 @@ const retrieveCommand = {
175
175
  output.printError('Key is required');
176
176
  return { success: false, exitCode: 1 };
177
177
  }
178
- // Use sql.js directly for consistent data access
178
+ // Use the memory backend directly for consistent data access
179
179
  try {
180
180
  const { getEntry } = await import('../memory/memory-initializer.js');
181
181
  const result = await getEntry({ key, namespace });
@@ -302,7 +302,7 @@ const searchCommand = {
302
302
  }
303
303
  output.printInfo(`Searching: "${query}" (${searchType})`);
304
304
  output.writeln();
305
- // Use direct sql.js search with vector similarity
305
+ // Use direct memory-backend search with vector similarity
306
306
  try {
307
307
  const { searchEntries } = await import('../memory/memory-initializer.js');
308
308
  const searchResult = await searchEntries({
@@ -381,7 +381,7 @@ const listCommand = {
381
381
  action: async (ctx) => {
382
382
  const namespace = ctx.flags.namespace;
383
383
  const limit = ctx.flags.limit;
384
- // Use sql.js directly for consistent data access
384
+ // Use the memory backend directly for consistent data access
385
385
  try {
386
386
  const { listEntries } = await import('../memory/memory-initializer.js');
387
387
  const listResult = await listEntries({ namespace, limit, offset: 0 });
@@ -499,7 +499,7 @@ const deleteCommand = {
499
499
  return { success: true };
500
500
  }
501
501
  }
502
- // Use sql.js directly for consistent data access (Issue #980)
502
+ // Use the memory backend directly for consistent data access (Issue #980)
503
503
  try {
504
504
  const { deleteEntry } = await import('../memory/memory-initializer.js');
505
505
  const result = await deleteEntry({ key, namespace });
@@ -1034,10 +1034,10 @@ const importCommand = {
1034
1034
  }
1035
1035
  }
1036
1036
  };
1037
- // Init subcommand - initialize memory database using sql.js
1037
+ // Init subcommand - initialize memory database using node:sqlite
1038
1038
  const initMemoryCommand = {
1039
1039
  name: 'init',
1040
- description: 'Initialize memory database with sql.js (WASM SQLite) - includes vector embeddings, pattern learning, temporal decay',
1040
+ description: 'Initialize memory database with node:sqlite (Node 22+ built-in) - includes vector embeddings, pattern learning, temporal decay',
1041
1041
  options: [
1042
1042
  {
1043
1043
  name: 'backend',
@@ -2637,7 +2637,7 @@ export const memoryCommand = {
2637
2637
  output.writeln();
2638
2638
  output.writeln('Subcommands:');
2639
2639
  output.printList([
2640
- `${output.highlight('init')} - Initialize memory database (sql.js)`,
2640
+ `${output.highlight('init')} - Initialize memory database (node:sqlite)`,
2641
2641
  `${output.highlight('store')} - Store data in memory`,
2642
2642
  `${output.highlight('retrieve')} - Retrieve data from memory`,
2643
2643
  `${output.highlight('search')} - Semantic/vector search`,
@@ -5,6 +5,8 @@
5
5
  * Created with ❤️ by motailz.com
6
6
  */
7
7
  import { output } from '../output.js';
8
+ import { findProjectRoot } from '../services/project-root.js';
9
+ import { MOFLO_DIR } from '../services/moflo-paths.js';
8
10
  import { errorDetail } from '../shared/utils/error-detail.js';
9
11
  // Train subcommand - REAL WASM training with MoVector
10
12
  const trainCommand = {
@@ -673,8 +675,8 @@ const optimizeCommand = {
673
675
  await initializeIntelligence();
674
676
  const patterns = await getAllPatterns();
675
677
  const stats = getIntelligenceStats();
676
- // Get actual pattern storage size
677
- const patternDir = path.join(process.cwd(), '.moflo', 'neural');
678
+ // Get actual pattern storage size (#1152: anchor on projectRoot, not cwd)
679
+ const patternDir = path.join(findProjectRoot(), MOFLO_DIR, 'neural');
678
680
  let beforeSize = 0;
679
681
  try {
680
682
  const patternFile = path.join(patternDir, 'patterns.json');
@@ -849,8 +851,8 @@ const exportCommand = {
849
851
  totalUsage: 0,
850
852
  },
851
853
  };
852
- // Load patterns from local storage
853
- const memoryDir = path.join(process.cwd(), '.moflo', 'memory');
854
+ // Load patterns from local storage (#1152: projectRoot-anchored)
855
+ const memoryDir = path.join(findProjectRoot(), MOFLO_DIR, 'memory');
854
856
  const patternsFile = path.join(memoryDir, 'patterns.json');
855
857
  if (fs.existsSync(patternsFile)) {
856
858
  const patterns = JSON.parse(fs.readFileSync(patternsFile, 'utf8'));
@@ -1242,8 +1244,8 @@ const importCommand = {
1242
1244
  if (validPatterns.length < patterns.length) {
1243
1245
  output.writeln(output.warning(`Filtered ${patterns.length - validPatterns.length} suspicious patterns`));
1244
1246
  }
1245
- // Save to local memory
1246
- const memoryDir = path.join(process.cwd(), '.moflo', 'memory');
1247
+ // Save to local memory (#1152: projectRoot-anchored)
1248
+ const memoryDir = path.join(findProjectRoot(), MOFLO_DIR, 'memory');
1247
1249
  if (!fs.existsSync(memoryDir)) {
1248
1250
  fs.mkdirSync(memoryDir, { recursive: true });
1249
1251
  }
@@ -41,7 +41,7 @@ const DEFAULT_CONFIG = {
41
41
  code_map: true,
42
42
  },
43
43
  memory: {
44
- backend: 'sql.js',
44
+ backend: 'node-sqlite',
45
45
  embedding_model: 'Xenova/all-MiniLM-L6-v2',
46
46
  namespace: 'default',
47
47
  },
@@ -138,6 +138,39 @@ function findConfigFile(root) {
138
138
  function positiveNumber(value, fallback) {
139
139
  return typeof value === 'number' && isFinite(value) && value > 0 ? value : fallback;
140
140
  }
141
+ const VALID_MEMORY_BACKENDS = new Set([
142
+ 'node-sqlite',
143
+ 'sql.js',
144
+ 'rvf',
145
+ 'json',
146
+ ]);
147
+ // Dedupe stderr warnings — `loadMofloConfig()` runs on every
148
+ // `createDatabase()` call that omits an explicit provider, so a single
149
+ // typo in moflo.yaml would otherwise spam N lines per process. Keyed by
150
+ // the offending raw value so unrelated typos still each emit once.
151
+ const _seenUnknownBackendWarnings = new Set();
152
+ /**
153
+ * Validate a raw `memory.backend` value against the narrow union. Unknown
154
+ * values (previously: `agentdb`, future typos) fall back to the default
155
+ * with a one-line stderr warning so consumers don't silently get the wrong
156
+ * backend after a migration.
157
+ *
158
+ * `sql.js` is accepted here but normalised later by
159
+ * {@link resolveDatabaseProvider} — keeping it in the YAML-side type lets
160
+ * existing `moflo.yaml` files keep parsing across the upgrade.
161
+ */
162
+ function coerceMemoryBackend(raw) {
163
+ if (typeof raw !== 'string' || raw.length === 0)
164
+ return DEFAULT_CONFIG.memory.backend;
165
+ if (VALID_MEMORY_BACKENDS.has(raw))
166
+ return raw;
167
+ if (!_seenUnknownBackendWarnings.has(raw)) {
168
+ _seenUnknownBackendWarnings.add(raw);
169
+ process.stderr.write(`[moflo] WARNING: unknown memory.backend "${raw}" in moflo.yaml — ` +
170
+ `falling back to "${DEFAULT_CONFIG.memory.backend}". Valid: ${[...VALID_MEMORY_BACKENDS].join(', ')}.\n`);
171
+ }
172
+ return DEFAULT_CONFIG.memory.backend;
173
+ }
141
174
  /**
142
175
  * Parse raw config object into typed config, merging with defaults.
143
176
  */
@@ -166,7 +199,7 @@ function mergeConfig(raw, root) {
166
199
  code_map: raw.auto_index?.code_map ?? raw.autoIndex?.code_map ?? DEFAULT_CONFIG.auto_index.code_map,
167
200
  },
168
201
  memory: {
169
- backend: raw.memory?.backend || DEFAULT_CONFIG.memory.backend,
202
+ backend: coerceMemoryBackend(raw.memory?.backend),
170
203
  embedding_model: raw.memory?.embedding_model || raw.memory?.embeddingModel || DEFAULT_CONFIG.memory.embedding_model,
171
204
  namespace: raw.memory?.namespace || DEFAULT_CONFIG.memory.namespace,
172
205
  },
@@ -361,7 +394,7 @@ auto_index:
361
394
 
362
395
  # Memory backend
363
396
  memory:
364
- backend: sql.js # sql.js (WASM, no native deps) | agentdb | json
397
+ backend: node-sqlite # node-sqlite (default) | rvf (pure-TS fallback) | json (last resort). Passed to createDatabase() as the preferred provider.
365
398
  embedding_model: Xenova/all-MiniLM-L6-v2
366
399
  namespace: default
367
400
 
@@ -471,4 +504,36 @@ export function writeMofloConfig(projectRoot) {
471
504
  fs.writeFileSync(configPath, content, 'utf-8');
472
505
  return configPath;
473
506
  }
507
+ // Dedupe stderr deprecation messages — one banner per (process, deprecated
508
+ // alias) keeps the noise to a single line per session even when multiple
509
+ // subsystems (daemon + indexer + statusline) all load the same config.
510
+ const _seenBackendDeprecations = new Set();
511
+ /**
512
+ * Map a YAML-side `memory.backend` value to the runtime
513
+ * {@link ResolvedMemoryBackend}. `sql.js` (deprecated since Phase 5 / #1084)
514
+ * is rewritten to `node-sqlite` with a one-time stderr deprecation note so
515
+ * consumers see they need to update their `moflo.yaml` but the run still
516
+ * succeeds.
517
+ *
518
+ * Centralising the mapping here is the whole point of #1144: every future
519
+ * caller that opens a DB based on the user's YAML knob goes through this
520
+ * helper, so the YAML schema and the runtime selection can never drift.
521
+ */
522
+ export function resolveDatabaseProvider(backend) {
523
+ if (backend === 'sql.js') {
524
+ if (!_seenBackendDeprecations.has(backend)) {
525
+ _seenBackendDeprecations.add(backend);
526
+ process.stderr.write(`[moflo] DEPRECATED: memory.backend "sql.js" in moflo.yaml — ` +
527
+ `sql.js was removed in Phase 5 (#1084). Using "node-sqlite" instead. ` +
528
+ `Update your moflo.yaml to silence this warning.\n`);
529
+ }
530
+ return 'node-sqlite';
531
+ }
532
+ return backend;
533
+ }
534
+ /** @internal — test hook only; resets the dedupe sets between cases. */
535
+ export function _resetBackendDeprecations() {
536
+ _seenBackendDeprecations.clear();
537
+ _seenUnknownBackendWarnings.clear();
538
+ }
474
539
  //# sourceMappingURL=moflo-config.js.map
@@ -519,30 +519,29 @@ export class CLI {
519
519
  }
520
520
  }
521
521
  /**
522
- * Load configuration file
522
+ * Load moflo project configuration.
523
+ *
524
+ * Returns the user's `moflo.yaml` merged with defaults so command actions
525
+ * can read project settings directly. Prior versions invoked a v2→v3
526
+ * SystemConfig adapter — collapsed in #1144 because nothing actually read
527
+ * `ctx.config.*` and the parallel schema was a silent-drift bug class.
528
+ *
529
+ * `configPath` (the global `--config` flag) used to point at a
530
+ * claude-flow SystemConfig file. With the adapter gone, the flag has no
531
+ * runtime effect; we surface a one-line warning when a consumer passes
532
+ * it so the silent-no-op is visible rather than mysterious. A future
533
+ * flag-driven override can plumb a directory through to
534
+ * `loadMofloConfig()` without touching callers.
523
535
  */
524
536
  async loadConfig(configPath) {
537
+ if (configPath) {
538
+ this.output.printWarning(`--config "${configPath}" is no longer honoured — moflo loads moflo.yaml ` +
539
+ `from the project root directly (#1144).`);
540
+ }
525
541
  try {
526
- // Import config utilities
527
- const { loadConfig: loadSystemConfig } = await import('./shared/index.js');
528
- const { systemConfigToV3Config } = await import('./config-adapter.js');
529
- // Load configuration
530
- const loaded = await loadSystemConfig({
531
- file: configPath,
532
- paths: configPath ? undefined : [process.cwd()],
533
- });
534
- // Convert to V3Config format
535
- const v3Config = systemConfigToV3Config(loaded.config);
536
- // Log warnings if any
537
- if (loaded.warnings && loaded.warnings.length > 0) {
538
- for (const warning of loaded.warnings) {
539
- this.output.printWarning(warning);
540
- }
541
- }
542
- return v3Config;
542
+ return loadMofloConfig();
543
543
  }
544
544
  catch (error) {
545
- // Config loading is optional - don't fail if it doesn't exist
546
545
  if (process.env.DEBUG) {
547
546
  this.output.writeln(this.output.dim(`Config loading failed: ${error.message}`));
548
547
  }
@@ -215,7 +215,7 @@ auto_index:
215
215
 
216
216
  # Memory backend
217
217
  memory:
218
- backend: sql.js
218
+ backend: node-sqlite
219
219
  embedding_model: Xenova/all-MiniLM-L6-v2
220
220
  namespace: default
221
221
 
@@ -24,19 +24,44 @@ import * as os from 'os';
24
24
  import { fileURLToPath } from 'url';
25
25
  import { dirname } from 'path';
26
26
  import { errorDetail } from './shared/utils/error-detail.js';
27
+ import { findProjectRoot } from './services/project-root.js';
27
28
  // ESM-compatible __dirname
28
29
  const __filename = fileURLToPath(import.meta.url);
29
30
  const __dirname = dirname(__filename);
30
31
  /**
31
- * Default configuration
32
+ * Resolve the per-project `.moflo/` directory for MCP state files.
33
+ *
34
+ * Routed through the unified `findProjectRoot` so the launcher, daemon, healers
35
+ * and the MCP server all agree on the same anchor (see #1057, #1145).
36
+ * Replaces the prior `os.tmpdir()` location which was shared across every
37
+ * moflo consumer on the machine — concurrent projects overwrote each other's
38
+ * PID file and `flo mcp stop` could kill the wrong project's MCP server.
39
+ */
40
+ function resolveMcpStateDir() {
41
+ return path.join(findProjectRoot(), '.moflo');
42
+ }
43
+ /**
44
+ * Legacy tmpdir paths (pre-#1151). Kept only so we can clean up dead PID
45
+ * files left behind by older moflo versions. Never written to.
32
46
  */
33
- const DEFAULT_OPTIONS = {
34
- transport: 'stdio',
35
- pidFile: path.join(os.tmpdir(), 'claude-flow-mcp.pid'),
36
- logFile: path.join(os.tmpdir(), 'claude-flow-mcp.log'),
37
- tools: 'all',
38
- daemonize: false,
39
- };
47
+ const LEGACY_TMPDIR_PID_FILE = path.join(os.tmpdir(), 'claude-flow-mcp.pid');
48
+ const LEGACY_TMPDIR_LOG_FILE = path.join(os.tmpdir(), 'claude-flow-mcp.log');
49
+ /**
50
+ * Build default configuration at construction time. PID/log paths resolve
51
+ * against `findProjectRoot()` so each project gets its own MCP state files
52
+ * under `<projectRoot>/.moflo/`. Lazy so test code that sets
53
+ * `CLAUDE_PROJECT_DIR` per-test sees the override.
54
+ */
55
+ function buildDefaultOptions() {
56
+ const stateDir = resolveMcpStateDir();
57
+ return {
58
+ transport: 'stdio',
59
+ pidFile: path.join(stateDir, 'mcp-server.pid'),
60
+ logFile: path.join(stateDir, 'mcp-server.log'),
61
+ tools: 'all',
62
+ daemonize: false,
63
+ };
64
+ }
40
65
  /**
41
66
  * MCP Server Manager
42
67
  *
@@ -49,7 +74,7 @@ export class MCPServerManager extends EventEmitter {
49
74
  healthCheckInterval;
50
75
  constructor(options = {}) {
51
76
  super();
52
- this.options = { ...DEFAULT_OPTIONS, ...options };
77
+ this.options = { ...buildDefaultOptions(), ...options };
53
78
  }
54
79
  /**
55
80
  * Start the MCP server
@@ -461,11 +486,35 @@ export class MCPServerManager extends EventEmitter {
461
486
  this.healthCheckInterval.unref();
462
487
  }
463
488
  /**
464
- * Write PID file
489
+ * Write PID file. Ensures the per-project state directory exists and opportunistically
490
+ * cleans up an abandoned tmpdir PID file (pre-#1151 layout) when it points at a dead
491
+ * PID — abandoned dead-PID files belong to nobody so we can safely unlink them, but
492
+ * a live tmpdir PID is left alone since it may belong to another project on an
493
+ * older moflo version.
465
494
  */
466
495
  async writePidFile() {
467
496
  const pid = this.process?.pid || process.pid;
497
+ await fs.promises.mkdir(path.dirname(this.options.pidFile), { recursive: true });
468
498
  await fs.promises.writeFile(this.options.pidFile, String(pid), 'utf8');
499
+ await this.cleanupAbandonedTmpdirPid();
500
+ }
501
+ /**
502
+ * Remove a stale `<tmpdir>/claude-flow-mcp.pid` left by a pre-#1151 moflo if
503
+ * the PID it points to is no longer running. Live PIDs are preserved so we
504
+ * don't break stop/status for another project still on the old layout.
505
+ */
506
+ async cleanupAbandonedTmpdirPid() {
507
+ try {
508
+ const legacy = await fs.promises.readFile(LEGACY_TMPDIR_PID_FILE, 'utf8');
509
+ const legacyPid = parseInt(legacy.trim(), 10);
510
+ if (!Number.isNaN(legacyPid) && !this.isProcessRunning(legacyPid)) {
511
+ await fs.promises.unlink(LEGACY_TMPDIR_PID_FILE).catch(() => { });
512
+ await fs.promises.unlink(LEGACY_TMPDIR_LOG_FILE).catch(() => { });
513
+ }
514
+ }
515
+ catch {
516
+ // No legacy file (the common path post-upgrade) — nothing to do.
517
+ }
469
518
  }
470
519
  /**
471
520
  * Read PID file
@@ -1,17 +1,16 @@
1
1
  /**
2
- * Memory MCP Tools for CLI - V3 with sql.js/HNSW Backend
2
+ * Memory MCP Tools for CLI node:sqlite + HNSW backend
3
3
  *
4
- * UPGRADED: Now uses the advanced sql.js + HNSW backend for:
5
- * - 150x-12,500x faster semantic search
6
- * - Vector embeddings with cosine similarity
7
- * - Persistent SQLite storage (WASM)
8
- * - Backward compatible with legacy JSON storage (auto-migrates)
4
+ * Backed by Node's built-in `node:sqlite` engine (Phase 4 #1083 flipped the
5
+ * default; Phase 5 #1084 deleted the prior sql.js path) plus an HNSW vector
6
+ * index for semantic search. Auto-migrates legacy JSON stores on first use.
9
7
  *
10
8
  * @module v3/cli/mcp-tools/memory-tools
11
9
  */
12
10
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
13
11
  import { join, resolve } from 'path';
14
12
  import { GateService } from '../services/spell-gate.js';
13
+ import { BACKEND_LABEL } from '../memory/database-provider.js';
15
14
  // Paths
16
15
  const MEMORY_DIR = '.moflo/memory';
17
16
  const LEGACY_MEMORY_FILE = 'store.json';
@@ -108,7 +107,7 @@ function shapeRetrievedEntry(entry) {
108
107
  hasEmbedding: entry.hasEmbedding,
109
108
  navigation: parseNavigation(entry.metadata, 'full'),
110
109
  found: true,
111
- backend: 'sql.js + HNSW',
110
+ backend: BACKEND_LABEL,
112
111
  };
113
112
  }
114
113
  function validateMemoryInput(key, value, query) {
@@ -185,7 +184,7 @@ async function ensureInitialized() {
185
184
  if (hasLegacyStore()) {
186
185
  const legacyStore = loadLegacyStore();
187
186
  if (legacyStore && Object.keys(legacyStore.entries).length > 0) {
188
- console.error('[MCP Memory] Migrating legacy JSON store to sql.js...');
187
+ console.error('[MCP Memory] Migrating legacy JSON store to node:sqlite...');
189
188
  let migrated = 0;
190
189
  for (const [key, entry] of Object.entries(legacyStore.entries)) {
191
190
  try {
@@ -211,7 +210,7 @@ async function ensureInitialized() {
211
210
  export const memoryTools = [
212
211
  {
213
212
  name: 'memory_store',
214
- description: 'Store a value in memory with vector embedding for semantic search (sql.js + HNSW backend). Upserts by default — pass upsert:false to fail on duplicate keys. Optional `metadata` lets chunk-row producers set the navigation fields (parentDoc, prevChunk, nextChunk, siblings, …) that `memory_get_neighbors` reads.',
213
+ description: 'Store a value in memory with vector embedding for semantic search (node:sqlite + HNSW backend). Upserts by default — pass upsert:false to fail on duplicate keys. Optional `metadata` lets chunk-row producers set the navigation fields (parentDoc, prevChunk, nextChunk, siblings, …) that `memory_get_neighbors` reads.',
215
214
  category: 'memory',
216
215
  inputSchema: {
217
216
  type: 'object',
@@ -268,7 +267,7 @@ export const memoryTools = [
268
267
  storedAt: new Date().toISOString(),
269
268
  hasEmbedding: !!result.embedding,
270
269
  embeddingDimensions: result.embedding?.dimensions || null,
271
- backend: 'sql.js + HNSW',
270
+ backend: BACKEND_LABEL,
272
271
  storeTime: `${duration.toFixed(2)}ms`,
273
272
  error: result.error,
274
273
  };
@@ -387,7 +386,7 @@ export const memoryTools = [
387
386
  query,
388
387
  results,
389
388
  total: results.length,
390
- backend: 'HNSW + sql.js',
389
+ backend: BACKEND_LABEL,
391
390
  };
392
391
  }
393
392
  catch (error) {
@@ -479,7 +478,7 @@ export const memoryTools = [
479
478
  include,
480
479
  neighbors,
481
480
  total: neighbors.length,
482
- backend: 'sql.js + HNSW',
481
+ backend: BACKEND_LABEL,
483
482
  };
484
483
  }
485
484
  catch (error) {
@@ -524,7 +523,7 @@ export const memoryTools = [
524
523
  key,
525
524
  namespace,
526
525
  deleted,
527
- backend: 'sql.js + HNSW',
526
+ backend: BACKEND_LABEL,
528
527
  ...(errorReason ? { error: errorReason } : {}),
529
528
  };
530
529
  }
@@ -577,7 +576,7 @@ export const memoryTools = [
577
576
  total: result.total,
578
577
  limit,
579
578
  offset,
580
- backend: 'sql.js + HNSW',
579
+ backend: BACKEND_LABEL,
581
580
  };
582
581
  }
583
582
  catch (error) {
@@ -601,27 +600,47 @@ export const memoryTools = [
601
600
  },
602
601
  handler: async () => {
603
602
  await ensureInitialized();
604
- const { checkMemoryInitialization, listEntries } = await getMemoryFunctions();
603
+ const { checkMemoryInitialization } = await getMemoryFunctions();
605
604
  try {
606
605
  const status = await checkMemoryInitialization();
607
- const allEntries = await listEntries({ limit: 100000 });
608
- // Count by namespace
609
- const namespaces = {};
610
- let withEmbeddings = 0;
611
- for (const entry of allEntries.entries) {
612
- namespaces[entry.namespace] = (namespaces[entry.namespace] || 0) + 1;
613
- if (entry.hasEmbedding)
614
- withEmbeddings++;
606
+ // #1149 server-side aggregation. The pre-fix path iterated every
607
+ // namespace via listEntries({limit:100000}) and tripped the daemon's
608
+ // `limit 10 000` cap → 400 → tryDaemonList defaulted total to 0 →
609
+ // the MCP tool silently reported zero entries on populated DBs.
610
+ // Route through the dedicated stats endpoint; on routed:false, run
611
+ // the same GROUP BY in-process so users always see real counts; on
612
+ // a daemon error, surface it rather than masking it as zero.
613
+ const { tryDaemonStats } = await import('../memory/daemon-write-client.js');
614
+ const routed = await tryDaemonStats();
615
+ let namespaces;
616
+ let totalEntries;
617
+ let withEmbeddings;
618
+ if (routed.routed && routed.data) {
619
+ ({ namespaces, totalEntries, withEmbeddings } = routed.data);
620
+ }
621
+ else if (routed.routed && routed.error) {
622
+ return {
623
+ initialized: status.initialized,
624
+ error: `daemon memory_stats failed: ${routed.error}`,
625
+ backend: BACKEND_LABEL,
626
+ };
627
+ }
628
+ else {
629
+ const { getNamespaceCounts } = await import('../memory/memory-initializer.js');
630
+ const direct = await getNamespaceCounts();
631
+ namespaces = direct.namespaces;
632
+ totalEntries = direct.total;
633
+ withEmbeddings = direct.withEmbeddings;
615
634
  }
616
635
  return {
617
636
  initialized: status.initialized,
618
- totalEntries: allEntries.total,
637
+ totalEntries,
619
638
  entriesWithEmbeddings: withEmbeddings,
620
- embeddingCoverage: allEntries.total > 0
621
- ? `${((withEmbeddings / allEntries.total) * 100).toFixed(1)}%`
639
+ embeddingCoverage: totalEntries > 0
640
+ ? `${((withEmbeddings / totalEntries) * 100).toFixed(1)}%`
622
641
  : '0%',
623
642
  namespaces,
624
- backend: 'sql.js + HNSW',
643
+ backend: BACKEND_LABEL,
625
644
  version: status.version || '3.0.0',
626
645
  features: status.features || {
627
646
  vectorEmbeddings: true,
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Per ADR-048: Bridges Claude Code's auto memory (markdown files at
5
5
  * ~/.claude/projects/<project>/memory/) with claude-flow's unified memory
6
- * system (MofloDb: sql.js + HNSW).
6
+ * system (MofloDb: node:sqlite + HNSW).
7
7
  *
8
8
  * Auto memory files are human-readable markdown that Claude loads into its
9
9
  * system prompt. MEMORY.md (first 200 lines) is the entrypoint; topic files
@@ -2,7 +2,7 @@
2
2
  * AttestationLog — moflo-owned append-only audit log (epic #464 Phase C2).
3
3
  *
4
4
  * Replaces `agentdb.AttestationLog`. Writes observability records into a
5
- * sql.js-backed table with a hash chain so tampering can be detected.
5
+ * SQLite table with a hash chain so tampering can be detected.
6
6
  *
7
7
  * Consumer surface (from src/cli/memory/memory-bridge.ts):
8
8
  * - record({ operation, entryId, timestamp?, ...metadata })
@@ -2,7 +2,7 @@
2
2
  * CausalGraph — moflo-owned causal edge store.
3
3
  *
4
4
  * Replaces `agentdb.CausalGraph.addEdge`. Stores typed edges between
5
- * memory-entry IDs in a sql.js-backed table with composite indexes so
5
+ * memory-entry IDs in a SQLite table with composite indexes so
6
6
  * CausalRecall's BFS walks don't hit a full scan on the relation filter.
7
7
  */
8
8
  import { clamp01, clampInt, parseJsonSafe } from './_shared.js';