moflo 4.10.6 → 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.
- package/.claude/guidance/shipped/moflo-cli-reference.md +1 -1
- package/.claude/guidance/shipped/moflo-memory-strategy.md +1 -1
- package/.claude/guidance/shipped/moflo-yaml-reference.md +19 -5
- package/.claude/skills/memory-optimization/SKILL.md +1 -1
- package/.claude/skills/memory-patterns/SKILL.md +3 -3
- package/.claude/skills/vector-search/SKILL.md +2 -2
- package/README.md +5 -5
- package/bin/lib/daemon-port.mjs +66 -0
- package/bin/session-start-launcher.mjs +189 -15
- package/bin/setup-project.mjs +38 -58
- package/dist/src/cli/commands/daemon.js +31 -10
- package/dist/src/cli/commands/doctor-checks-config.js +139 -1
- package/dist/src/cli/commands/doctor-checks-deep.js +105 -0
- package/dist/src/cli/commands/doctor-fixes.js +99 -2
- package/dist/src/cli/commands/doctor-registry.js +15 -2
- package/dist/src/cli/commands/memory.js +8 -8
- package/dist/src/cli/commands/neural.js +8 -6
- package/dist/src/cli/config/moflo-config.js +79 -3
- package/dist/src/cli/index.js +18 -19
- package/dist/src/cli/init/claudemd-generator.js +6 -2
- package/dist/src/cli/init/moflo-init.js +13 -21
- package/dist/src/cli/init/moflo-yaml-template.js +1 -1
- package/dist/src/cli/mcp-server.js +59 -10
- package/dist/src/cli/mcp-tools/memory-tools.js +46 -27
- package/dist/src/cli/memory/auto-memory-bridge.js +1 -1
- package/dist/src/cli/memory/controllers/attestation-log.js +1 -1
- package/dist/src/cli/memory/controllers/causal-graph.js +1 -1
- package/dist/src/cli/memory/daemon-write-client.js +178 -49
- package/dist/src/cli/memory/database-provider.js +58 -3
- package/dist/src/cli/memory/intelligence.js +54 -26
- package/dist/src/cli/memory/memory-initializer.js +21 -11
- package/dist/src/cli/services/claudemd-injection.js +173 -0
- package/dist/src/cli/services/daemon-dashboard.js +94 -25
- package/dist/src/cli/services/daemon-lock.js +390 -3
- package/dist/src/cli/services/daemon-port.js +217 -0
- package/dist/src/cli/version.js +1 -1
- package/package.json +2 -2
- package/dist/src/cli/config-adapter.js +0 -182
|
@@ -41,7 +41,7 @@ const DEFAULT_CONFIG = {
|
|
|
41
41
|
code_map: true,
|
|
42
42
|
},
|
|
43
43
|
memory: {
|
|
44
|
-
backend: '
|
|
44
|
+
backend: 'node-sqlite',
|
|
45
45
|
embedding_model: 'Xenova/all-MiniLM-L6-v2',
|
|
46
46
|
namespace: 'default',
|
|
47
47
|
},
|
|
@@ -83,6 +83,7 @@ const DEFAULT_CONFIG = {
|
|
|
83
83
|
scripts: true,
|
|
84
84
|
helpers: true,
|
|
85
85
|
hook_block_drift: 'warn',
|
|
86
|
+
claudemd_injection_drift: 'regenerate',
|
|
86
87
|
},
|
|
87
88
|
sandbox: {
|
|
88
89
|
enabled: false,
|
|
@@ -137,6 +138,39 @@ function findConfigFile(root) {
|
|
|
137
138
|
function positiveNumber(value, fallback) {
|
|
138
139
|
return typeof value === 'number' && isFinite(value) && value > 0 ? value : fallback;
|
|
139
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
|
+
}
|
|
140
174
|
/**
|
|
141
175
|
* Parse raw config object into typed config, merging with defaults.
|
|
142
176
|
*/
|
|
@@ -165,7 +199,7 @@ function mergeConfig(raw, root) {
|
|
|
165
199
|
code_map: raw.auto_index?.code_map ?? raw.autoIndex?.code_map ?? DEFAULT_CONFIG.auto_index.code_map,
|
|
166
200
|
},
|
|
167
201
|
memory: {
|
|
168
|
-
backend: raw.memory?.backend
|
|
202
|
+
backend: coerceMemoryBackend(raw.memory?.backend),
|
|
169
203
|
embedding_model: raw.memory?.embedding_model || raw.memory?.embeddingModel || DEFAULT_CONFIG.memory.embedding_model,
|
|
170
204
|
namespace: raw.memory?.namespace || DEFAULT_CONFIG.memory.namespace,
|
|
171
205
|
},
|
|
@@ -212,6 +246,12 @@ function mergeConfig(raw, root) {
|
|
|
212
246
|
? v
|
|
213
247
|
: DEFAULT_CONFIG.auto_update.hook_block_drift;
|
|
214
248
|
})(),
|
|
249
|
+
claudemd_injection_drift: (() => {
|
|
250
|
+
const v = raw.auto_update?.claudemd_injection_drift ?? raw.autoUpdate?.claudemdInjectionDrift;
|
|
251
|
+
return v === 'regenerate' || v === 'off' || v === 'warn'
|
|
252
|
+
? v
|
|
253
|
+
: DEFAULT_CONFIG.auto_update.claudemd_injection_drift;
|
|
254
|
+
})(),
|
|
215
255
|
},
|
|
216
256
|
sandbox: {
|
|
217
257
|
enabled: raw.sandbox?.enabled ?? DEFAULT_CONFIG.sandbox.enabled,
|
|
@@ -354,7 +394,7 @@ auto_index:
|
|
|
354
394
|
|
|
355
395
|
# Memory backend
|
|
356
396
|
memory:
|
|
357
|
-
backend:
|
|
397
|
+
backend: node-sqlite # node-sqlite (default) | rvf (pure-TS fallback) | json (last resort). Passed to createDatabase() as the preferred provider.
|
|
358
398
|
embedding_model: Xenova/all-MiniLM-L6-v2
|
|
359
399
|
namespace: default
|
|
360
400
|
|
|
@@ -409,6 +449,10 @@ auto_update:
|
|
|
409
449
|
# warn = print drift summary on session start (default)
|
|
410
450
|
# regenerate = auto-add missing hooks (only when no customisations)
|
|
411
451
|
# off = skip detection entirely
|
|
452
|
+
claudemd_injection_drift: regenerate # warn | regenerate | off
|
|
453
|
+
# regenerate = auto-refresh CLAUDE.md MoFlo block on drift (default)
|
|
454
|
+
# warn = print drift summary on session start
|
|
455
|
+
# off = skip detection entirely
|
|
412
456
|
|
|
413
457
|
# OS-level sandbox for spell bash steps
|
|
414
458
|
# Denylist always runs regardless of this setting
|
|
@@ -460,4 +504,36 @@ export function writeMofloConfig(projectRoot) {
|
|
|
460
504
|
fs.writeFileSync(configPath, content, 'utf-8');
|
|
461
505
|
return configPath;
|
|
462
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
|
+
}
|
|
463
539
|
//# sourceMappingURL=moflo-config.js.map
|
package/dist/src/cli/index.js
CHANGED
|
@@ -519,30 +519,29 @@ export class CLI {
|
|
|
519
519
|
}
|
|
520
520
|
}
|
|
521
521
|
/**
|
|
522
|
-
* Load configuration
|
|
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
|
-
|
|
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
|
}
|
|
@@ -60,8 +60,12 @@ ${MARKER_END}`;
|
|
|
60
60
|
export { MARKER_START, MARKER_END, LEGACY_MARKER_STARTS, LEGACY_MARKER_ENDS };
|
|
61
61
|
/**
|
|
62
62
|
* Generate the MoFlo section to inject into CLAUDE.md.
|
|
63
|
-
*
|
|
64
|
-
*
|
|
63
|
+
*
|
|
64
|
+
* Both parameters are accepted for backward compatibility but ignored — all
|
|
65
|
+
* templates produce the same minimal injection and the options shape is no
|
|
66
|
+
* longer consulted. Optional so callers from both the dev tree (TS) and the
|
|
67
|
+
* dogfood launcher (plain JS) can invoke as `generateClaudeMd()` /
|
|
68
|
+
* `generateClaudeMd({})` interchangeably.
|
|
65
69
|
*/
|
|
66
70
|
export function generateClaudeMd(_options, _template) {
|
|
67
71
|
return mofloSection() + '\n';
|
|
@@ -15,7 +15,8 @@ import { execSync } from 'child_process';
|
|
|
15
15
|
import { locateMofloRootPath } from '../services/moflo-require.js';
|
|
16
16
|
import { errorDetail } from '../shared/utils/error-detail.js';
|
|
17
17
|
import { discoverGuidanceDirs, discoverSrcDirs, discoverTestDirs, detectExtensions, renderMofloYaml, } from './moflo-yaml-template.js';
|
|
18
|
-
import { generateClaudeMd as generateMofloSection
|
|
18
|
+
import { generateClaudeMd as generateMofloSection } from './claudemd-generator.js';
|
|
19
|
+
import { applyInjectionReplacement } from '../services/claudemd-injection.js';
|
|
19
20
|
import { DEFAULT_INIT_OPTIONS } from './types.js';
|
|
20
21
|
export { discoverTestDirs };
|
|
21
22
|
// ============================================================================
|
|
@@ -400,29 +401,20 @@ function generateSkill(root, force) {
|
|
|
400
401
|
// ============================================================================
|
|
401
402
|
function generateClaudeMd(root, _force) {
|
|
402
403
|
const claudeMdPath = path.join(root, 'CLAUDE.md');
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
for (let i = 0; i < allStartMarkers.length; i++) {
|
|
410
|
-
if (existing.includes(allStartMarkers[i])) {
|
|
411
|
-
const startIdx = existing.indexOf(allStartMarkers[i]);
|
|
412
|
-
const endIdx = existing.indexOf(allEndMarkers[i]);
|
|
413
|
-
if (endIdx > startIdx) {
|
|
414
|
-
existing = existing.substring(0, startIdx) + existing.substring(endIdx + allEndMarkers[i].length);
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
// Single source of truth: claudemd-generator.ts owns the section content.
|
|
404
|
+
const existed = fs.existsSync(claudeMdPath);
|
|
405
|
+
const existing = existed ? fs.readFileSync(claudeMdPath, 'utf-8') : null;
|
|
406
|
+
// Single source of truth: claudemd-generator.ts owns the section content,
|
|
407
|
+
// claudemd-injection.ts owns the marker-replace logic. Replaces in place
|
|
408
|
+
// when a marker pair (current or legacy) already exists; otherwise creates
|
|
409
|
+
// a fresh CLAUDE.md or appends to a non-moflo one.
|
|
420
410
|
const canonical = generateMofloSection(DEFAULT_INIT_OPTIONS);
|
|
421
|
-
const
|
|
422
|
-
|
|
411
|
+
const result = applyInjectionReplacement(existing, canonical);
|
|
412
|
+
if (result.contents !== null && (result.changed || !existed)) {
|
|
413
|
+
fs.writeFileSync(claudeMdPath, result.contents, 'utf-8');
|
|
414
|
+
}
|
|
423
415
|
return {
|
|
424
416
|
name: 'CLAUDE.md',
|
|
425
|
-
status:
|
|
417
|
+
status: existed ? 'updated' : 'created',
|
|
426
418
|
detail: 'MoFlo section injected (~22 lines)',
|
|
427
419
|
};
|
|
428
420
|
}
|
|
@@ -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
|
-
*
|
|
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
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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 = { ...
|
|
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
|
|
2
|
+
* Memory MCP Tools for CLI — node:sqlite + HNSW backend
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* -
|
|
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:
|
|
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
|
|
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 (
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
603
|
+
const { checkMemoryInitialization } = await getMemoryFunctions();
|
|
605
604
|
try {
|
|
606
605
|
const status = await checkMemoryInitialization();
|
|
607
|
-
|
|
608
|
-
//
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
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
|
|
637
|
+
totalEntries,
|
|
619
638
|
entriesWithEmbeddings: withEmbeddings,
|
|
620
|
-
embeddingCoverage:
|
|
621
|
-
? `${((withEmbeddings /
|
|
639
|
+
embeddingCoverage: totalEntries > 0
|
|
640
|
+
? `${((withEmbeddings / totalEntries) * 100).toFixed(1)}%`
|
|
622
641
|
: '0%',
|
|
623
642
|
namespaces,
|
|
624
|
-
backend:
|
|
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:
|
|
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
|
-
*
|
|
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
|
|
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';
|