moflo 4.10.11 → 4.10.12
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/dist/src/cli/commands/doctor-checks-config.js +12 -0
- package/dist/src/cli/commands/doctor-fixes.js +26 -9
- package/dist/src/cli/commands/memory.js +11 -4
- package/dist/src/cli/commands/swarm.js +29 -60
- package/dist/src/cli/memory/ewc-consolidation.js +22 -6
- package/dist/src/cli/memory/sona-optimizer.js +25 -7
- package/dist/src/cli/movector/lora-adapter.js +22 -7
- package/dist/src/cli/movector/moe-router.js +22 -6
- package/dist/src/cli/services/moflo-paths.js +20 -0
- package/dist/src/cli/version.js +1 -1
- package/package.json +2 -2
|
@@ -272,6 +272,12 @@ export async function checkMemoryDatabase() {
|
|
|
272
272
|
* - `memory.db` / `memory.db.bak` — stale once `.moflo/moflo.db` exists.
|
|
273
273
|
* - `q-learning-model.json` / `model-router-state.json` — live router state
|
|
274
274
|
* that pre-dates the `.moflo/movector/` defaults; migrate, don't delete.
|
|
275
|
+
* - `lora-weights.json` / `moe-weights.json` — LoRA + MoE weights (#1168
|
|
276
|
+
* moved the writers to `.moflo/movector/`).
|
|
277
|
+
* - `ewc-fisher.json` / `sona-patterns.json` — neural runtime state (#1168
|
|
278
|
+
* moved the writers to `.moflo/neural/`).
|
|
279
|
+
* - `state.json` — `flo swarm init` snapshot (#1168 → `.moflo/swarm/`).
|
|
280
|
+
* - `code-map-hash.txt` — `flo memory code-map` cache (#1168 → `.moflo/memory/`).
|
|
275
281
|
* - `hooks.log` / `background.log` — diagnostic logs the launcher used to
|
|
276
282
|
* route to `.swarm/`; relocate to `.moflo/logs/`.
|
|
277
283
|
*
|
|
@@ -291,6 +297,12 @@ export async function checkSwarmResidue() {
|
|
|
291
297
|
'memory.db.bak',
|
|
292
298
|
'q-learning-model.json',
|
|
293
299
|
'model-router-state.json',
|
|
300
|
+
'lora-weights.json',
|
|
301
|
+
'moe-weights.json',
|
|
302
|
+
'ewc-fisher.json',
|
|
303
|
+
'sona-patterns.json',
|
|
304
|
+
'state.json',
|
|
305
|
+
'code-map-hash.txt',
|
|
294
306
|
'hooks.log',
|
|
295
307
|
'background.log',
|
|
296
308
|
];
|
|
@@ -152,10 +152,27 @@ async function fixSwarmLegacyResidue() {
|
|
|
152
152
|
allMigrated = false;
|
|
153
153
|
}
|
|
154
154
|
}
|
|
155
|
-
// (2) router state JSONs — rename into .moflo/movector/.
|
|
155
|
+
// (2) router state + neural state JSONs — rename into .moflo/{movector,neural,swarm,memory}/.
|
|
156
|
+
//
|
|
157
|
+
// q-learning-model.json + model-router-state.json: shipped at #727.
|
|
158
|
+
// lora-weights.json + moe-weights.json: writer relocation in #1168
|
|
159
|
+
// (lora-adapter.ts, moe-router.ts).
|
|
160
|
+
// ewc-fisher.json + sona-patterns.json: writer relocation in #1168
|
|
161
|
+
// (ewc-consolidation.ts, sona-optimizer.ts).
|
|
162
|
+
// state.json + code-map-hash.txt: writer relocation in #1168
|
|
163
|
+
// (commands/swarm.ts, commands/memory.ts).
|
|
164
|
+
const neuralDir = join(moflo, 'neural');
|
|
165
|
+
const swarmStateDir = join(moflo, 'swarm');
|
|
166
|
+
const memoryStateDir = join(moflo, 'memory');
|
|
156
167
|
const stateFiles = [
|
|
157
168
|
{ name: 'q-learning-model.json', dest: movectorDir },
|
|
158
169
|
{ name: 'model-router-state.json', dest: movectorDir },
|
|
170
|
+
{ name: 'lora-weights.json', dest: movectorDir },
|
|
171
|
+
{ name: 'moe-weights.json', dest: movectorDir },
|
|
172
|
+
{ name: 'ewc-fisher.json', dest: neuralDir },
|
|
173
|
+
{ name: 'sona-patterns.json', dest: neuralDir },
|
|
174
|
+
{ name: 'state.json', dest: swarmStateDir },
|
|
175
|
+
{ name: 'code-map-hash.txt', dest: memoryStateDir },
|
|
159
176
|
];
|
|
160
177
|
for (const { name, dest } of stateFiles) {
|
|
161
178
|
const src = join(swarmDir, name);
|
|
@@ -230,10 +247,11 @@ export async function autoFixCheck(check) {
|
|
|
230
247
|
// Map checks to programmatic fixes (not just shell commands)
|
|
231
248
|
const fixActions = {
|
|
232
249
|
'Memory Database': async () => {
|
|
250
|
+
// Canonical DB lives at `.moflo/moflo.db`; `initializeMemoryDatabase`
|
|
251
|
+
// creates the parent dir itself. The pre-#1168 fix also `mkdirSync`'d
|
|
252
|
+
// `.swarm/` — vestigial residue that fought the 'Swarm Residue' fix in
|
|
253
|
+
// the same healer pass. Removed.
|
|
233
254
|
try {
|
|
234
|
-
const swarmDir = join(process.cwd(), '.swarm');
|
|
235
|
-
if (!existsSync(swarmDir))
|
|
236
|
-
mkdirSync(swarmDir, { recursive: true });
|
|
237
255
|
const { initializeMemoryDatabase } = await import('../memory/memory-initializer.js');
|
|
238
256
|
const result = await initializeMemoryDatabase({ force: true, verbose: false });
|
|
239
257
|
return result.success;
|
|
@@ -243,12 +261,11 @@ export async function autoFixCheck(check) {
|
|
|
243
261
|
}
|
|
244
262
|
},
|
|
245
263
|
'Embeddings': async () => {
|
|
264
|
+
// Same fix as Memory Database — ensure the canonical DB exists, then
|
|
265
|
+
// populate embeddings. Pre-#1168 wrote to `.swarm/memory.db` directly,
|
|
266
|
+
// contradicting the post-#727 layout; that branch is removed.
|
|
246
267
|
try {
|
|
247
|
-
|
|
248
|
-
if (!existsSync(swarmDir))
|
|
249
|
-
mkdirSync(swarmDir, { recursive: true });
|
|
250
|
-
const dbPath = join(swarmDir, 'memory.db');
|
|
251
|
-
if (!existsSync(dbPath)) {
|
|
268
|
+
if (!existsSync(memoryDbPath(findProjectRoot()))) {
|
|
252
269
|
const { initializeMemoryDatabase } = await import('../memory/memory-initializer.js');
|
|
253
270
|
await initializeMemoryDatabase({ force: true, verbose: false });
|
|
254
271
|
}
|
|
@@ -9,6 +9,7 @@ import { select, confirm, input } from '../prompt.js';
|
|
|
9
9
|
import { callMCPTool, MCPClientError } from '../mcp-client.js';
|
|
10
10
|
import { openDaemonDatabase } from '../memory/daemon-backend.js';
|
|
11
11
|
import { errorDetail } from '../shared/utils/error-detail.js';
|
|
12
|
+
import { legacySwarmPath, runtimePath } from '../services/moflo-paths.js';
|
|
12
13
|
// Memory backends
|
|
13
14
|
const BACKENDS = [
|
|
14
15
|
{ value: 'agentdb', label: 'AgentDB', hint: 'Vector database with HNSW indexing (150x-12,500x faster)' },
|
|
@@ -2129,7 +2130,10 @@ const codeMapCommand = {
|
|
|
2129
2130
|
const { execSync } = await import('child_process');
|
|
2130
2131
|
const { createHash } = await import('crypto');
|
|
2131
2132
|
const cwd = ctx.cwd || process.cwd();
|
|
2132
|
-
|
|
2133
|
+
// Post-#1168: canonical at `.moflo/memory/code-map-hash.txt`. Legacy
|
|
2134
|
+
// `.swarm/code-map-hash.txt` is read-only fallback for upgrade scenarios.
|
|
2135
|
+
const hashCachePath = runtimePath('memory', 'code-map-hash.txt');
|
|
2136
|
+
const legacyHashCachePath = legacySwarmPath('code-map-hash.txt');
|
|
2133
2137
|
output.writeln();
|
|
2134
2138
|
output.writeln(output.bold('Generating Code Map'));
|
|
2135
2139
|
output.writeln(output.dim('─'.repeat(50)));
|
|
@@ -2168,9 +2172,12 @@ const codeMapCommand = {
|
|
|
2168
2172
|
output.writeln(`File list hash: ${currentHash.slice(0, 12)}...`);
|
|
2169
2173
|
return { success: true };
|
|
2170
2174
|
}
|
|
2171
|
-
// Check if unchanged
|
|
2172
|
-
|
|
2173
|
-
|
|
2175
|
+
// Check if unchanged — canonical first, then legacy `.swarm/` fallback.
|
|
2176
|
+
const cachedReadPath = fs.existsSync(hashCachePath)
|
|
2177
|
+
? hashCachePath
|
|
2178
|
+
: (fs.existsSync(legacyHashCachePath) ? legacyHashCachePath : null);
|
|
2179
|
+
if (!forceRegen && cachedReadPath) {
|
|
2180
|
+
const cached = fs.readFileSync(cachedReadPath, 'utf-8').trim();
|
|
2174
2181
|
if (cached === currentHash) {
|
|
2175
2182
|
const { db } = await openDb(cwd);
|
|
2176
2183
|
const stmt = db.prepare(`SELECT COUNT(*) as cnt FROM memory_entries WHERE namespace = ?`);
|
|
@@ -7,14 +7,26 @@ import { select, confirm } from '../prompt.js';
|
|
|
7
7
|
import { callMCPTool, MCPClientError } from '../mcp-client.js';
|
|
8
8
|
import * as fs from 'fs';
|
|
9
9
|
import * as path from 'path';
|
|
10
|
-
import { memoryDbCandidatePaths } from '../services/moflo-paths.js';
|
|
10
|
+
import { LEGACY_SWARM_DIR, memoryDbCandidatePaths, mofloDir } from '../services/moflo-paths.js';
|
|
11
|
+
import { findProjectRoot } from '../services/project-root.js';
|
|
11
12
|
// Get dynamic swarm status from memory/session files
|
|
12
13
|
function getSwarmStatus(swarmId) {
|
|
13
|
-
const
|
|
14
|
+
const projectRoot = findProjectRoot();
|
|
15
|
+
// `.moflo/swarm/state.json` is canonical post-#1168; `.swarm/state.json`
|
|
16
|
+
// is a read-only fallback so a consumer who initialised on an older moflo
|
|
17
|
+
// still sees their swarm. The pre-#1168 agents/tasks JSON probe blocks
|
|
18
|
+
// were removed — no current writer creates those directories, so they
|
|
19
|
+
// always produced 0 counts. The coordinator-backed MCP tools
|
|
20
|
+
// (agent_list / task_list) are the live source of truth.
|
|
21
|
+
const canonicalSwarmDir = path.join(mofloDir(projectRoot), 'swarm');
|
|
22
|
+
const legacySwarmDir = path.join(projectRoot, LEGACY_SWARM_DIR);
|
|
14
23
|
const sessionDir = path.join(process.cwd(), '.claude', 'sessions');
|
|
15
24
|
const memoryPaths = memoryDbCandidatePaths(process.cwd());
|
|
16
|
-
// Check for active swarm state file
|
|
17
|
-
|
|
25
|
+
// Check for active swarm state file — canonical first, then legacy.
|
|
26
|
+
let swarmStateFile = path.join(canonicalSwarmDir, 'state.json');
|
|
27
|
+
if (!fs.existsSync(swarmStateFile)) {
|
|
28
|
+
swarmStateFile = path.join(legacySwarmDir, 'state.json');
|
|
29
|
+
}
|
|
18
30
|
let swarmState = null;
|
|
19
31
|
if (fs.existsSync(swarmStateFile)) {
|
|
20
32
|
try {
|
|
@@ -24,30 +36,14 @@ function getSwarmStatus(swarmId) {
|
|
|
24
36
|
// Ignore parse errors
|
|
25
37
|
}
|
|
26
38
|
}
|
|
27
|
-
//
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
for (const file of agentFiles) {
|
|
36
|
-
try {
|
|
37
|
-
const agent = JSON.parse(fs.readFileSync(path.join(agentsDir, file), 'utf-8'));
|
|
38
|
-
if (agent.status === 'active' || agent.status === 'running') {
|
|
39
|
-
activeAgents++;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
catch {
|
|
43
|
-
// Ignore
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
catch {
|
|
48
|
-
// Ignore
|
|
49
|
-
}
|
|
50
|
-
}
|
|
39
|
+
// agents/tasks counters: no file-store readers post-#1168. Coordinator
|
|
40
|
+
// MCP tools own the live counts; getSwarmStatus surfaces a static summary
|
|
41
|
+
// of the persisted state file plus session/memory rough indicators.
|
|
42
|
+
const activeAgents = 0;
|
|
43
|
+
const totalAgents = 0;
|
|
44
|
+
const completedTasks = 0;
|
|
45
|
+
const inProgressTasks = 0;
|
|
46
|
+
const pendingTasks = 0;
|
|
51
47
|
// Get session count
|
|
52
48
|
let sessionCount = 0;
|
|
53
49
|
if (fs.existsSync(sessionDir)) {
|
|
@@ -71,36 +67,6 @@ function getSwarmStatus(swarmId) {
|
|
|
71
67
|
}
|
|
72
68
|
}
|
|
73
69
|
}
|
|
74
|
-
// Count task files if they exist
|
|
75
|
-
let completedTasks = 0;
|
|
76
|
-
let inProgressTasks = 0;
|
|
77
|
-
let pendingTasks = 0;
|
|
78
|
-
const tasksDir = path.join(swarmDir, 'tasks');
|
|
79
|
-
if (fs.existsSync(tasksDir)) {
|
|
80
|
-
try {
|
|
81
|
-
const taskFiles = fs.readdirSync(tasksDir).filter(f => f.endsWith('.json'));
|
|
82
|
-
for (const file of taskFiles) {
|
|
83
|
-
try {
|
|
84
|
-
const task = JSON.parse(fs.readFileSync(path.join(tasksDir, file), 'utf-8'));
|
|
85
|
-
if (task.status === 'completed' || task.status === 'done') {
|
|
86
|
-
completedTasks++;
|
|
87
|
-
}
|
|
88
|
-
else if (task.status === 'in_progress' || task.status === 'running') {
|
|
89
|
-
inProgressTasks++;
|
|
90
|
-
}
|
|
91
|
-
else {
|
|
92
|
-
pendingTasks++;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
catch {
|
|
96
|
-
// Ignore
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
catch {
|
|
101
|
-
// Ignore
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
70
|
// Calculate dynamic progress based on actual state
|
|
105
71
|
// If no swarm state, show 0%. Otherwise calculate from completed tasks
|
|
106
72
|
const totalTasks = completedTasks + inProgressTasks + pendingTasks;
|
|
@@ -279,8 +245,11 @@ const initCommand = {
|
|
|
279
245
|
});
|
|
280
246
|
output.writeln();
|
|
281
247
|
output.printSuccess('Swarm initialized successfully');
|
|
282
|
-
// Save swarm state locally for status command to read
|
|
283
|
-
|
|
248
|
+
// Save swarm state locally for status command to read. Post-#1168 the
|
|
249
|
+
// canonical home is `<root>/.moflo/swarm/state.json`; the legacy
|
|
250
|
+
// `.swarm/state.json` path is preserved as a read-only fallback in
|
|
251
|
+
// `getSwarmStatus`.
|
|
252
|
+
const swarmDir = path.join(mofloDir(findProjectRoot()), 'swarm');
|
|
284
253
|
try {
|
|
285
254
|
if (!fs.existsSync(swarmDir)) {
|
|
286
255
|
fs.mkdirSync(swarmDir, { recursive: true });
|
|
@@ -16,13 +16,15 @@
|
|
|
16
16
|
* - Fisher Information Matrix computation from gradient history
|
|
17
17
|
* - Online EWC updates for streaming patterns
|
|
18
18
|
* - Selective consolidation based on pattern importance
|
|
19
|
-
* - Persistent storage in .
|
|
19
|
+
* - Persistent storage in .moflo/neural/ewc-fisher.json
|
|
20
|
+
* (legacy fallback read: .swarm/ewc-fisher.json)
|
|
20
21
|
*
|
|
21
22
|
* @module v3/cli/memory/ewc-consolidation
|
|
22
23
|
*/
|
|
23
24
|
import * as fs from 'fs';
|
|
24
25
|
import * as path from 'path';
|
|
25
26
|
import { errorDetail } from '../shared/utils/error-detail.js';
|
|
27
|
+
import { legacySwarmPath, runtimePath } from '../services/moflo-paths.js';
|
|
26
28
|
// ============================================================================
|
|
27
29
|
// Default Configuration
|
|
28
30
|
// ============================================================================
|
|
@@ -31,7 +33,6 @@ const DEFAULT_EWC_CONFIG = {
|
|
|
31
33
|
maxPatterns: 1000,
|
|
32
34
|
fisherDecayRate: 0.01,
|
|
33
35
|
importanceThreshold: 0.3,
|
|
34
|
-
storagePath: path.join(process.cwd(), '.swarm', 'ewc-fisher.json'),
|
|
35
36
|
onlineMode: true,
|
|
36
37
|
dimensions: 384
|
|
37
38
|
};
|
|
@@ -51,7 +52,15 @@ export class EWCConsolidator {
|
|
|
51
52
|
consolidationHistory = [];
|
|
52
53
|
initialized = false;
|
|
53
54
|
constructor(config) {
|
|
54
|
-
|
|
55
|
+
// Resolve storagePath lazily here (#1168) — the default routes through
|
|
56
|
+
// findProjectRoot at construct-time, not module-load time. Default-rescue
|
|
57
|
+
// runs *last* so an explicit `storagePath: undefined` falls back to the
|
|
58
|
+
// canonical path instead of leaving the field undefined.
|
|
59
|
+
this.config = {
|
|
60
|
+
...DEFAULT_EWC_CONFIG,
|
|
61
|
+
...config,
|
|
62
|
+
storagePath: config?.storagePath ?? runtimePath('neural', 'ewc-fisher.json'),
|
|
63
|
+
};
|
|
55
64
|
this.globalFisher = new Array(this.config.dimensions).fill(0);
|
|
56
65
|
}
|
|
57
66
|
/**
|
|
@@ -447,10 +456,17 @@ export class EWCConsolidator {
|
|
|
447
456
|
* Load state from disk
|
|
448
457
|
*/
|
|
449
458
|
async loadFromDisk() {
|
|
450
|
-
|
|
451
|
-
|
|
459
|
+
// Canonical path first, then legacy `.swarm/ewc-fisher.json` as a
|
|
460
|
+
// read-only fallback for consumers who upgraded mid-training (#1168).
|
|
461
|
+
let sourcePath = this.config.storagePath;
|
|
462
|
+
if (!fs.existsSync(sourcePath)) {
|
|
463
|
+
const legacy = legacySwarmPath('ewc-fisher.json');
|
|
464
|
+
if (!fs.existsSync(legacy)) {
|
|
465
|
+
throw new Error('No persisted state found');
|
|
466
|
+
}
|
|
467
|
+
sourcePath = legacy;
|
|
452
468
|
}
|
|
453
|
-
const content = fs.readFileSync(
|
|
469
|
+
const content = fs.readFileSync(sourcePath, 'utf-8');
|
|
454
470
|
const state = JSON.parse(content);
|
|
455
471
|
// Validate version
|
|
456
472
|
if (state.version !== '1.0.0') {
|
|
@@ -8,17 +8,18 @@
|
|
|
8
8
|
* - Processes trajectory outcomes from the spell-engine trajectory pipeline
|
|
9
9
|
* - Extracts keywords from tasks for pattern matching
|
|
10
10
|
* - Maintains learned routing patterns with confidence scoring
|
|
11
|
-
* - Persists patterns to .
|
|
11
|
+
* - Persists patterns to .moflo/neural/sona-patterns.json
|
|
12
|
+
* (legacy fallback read: .swarm/sona-patterns.json)
|
|
12
13
|
* - Integrates with Q-learning router for combined routing
|
|
13
14
|
*
|
|
14
15
|
* @module v3/cli/memory/sona-optimizer
|
|
15
16
|
*/
|
|
16
17
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
17
|
-
import { dirname,
|
|
18
|
+
import { dirname, isAbsolute, resolve } from 'path';
|
|
19
|
+
import { legacySwarmPath, runtimePath } from '../services/moflo-paths.js';
|
|
18
20
|
// ============================================================================
|
|
19
21
|
// Constants
|
|
20
22
|
// ============================================================================
|
|
21
|
-
const DEFAULT_PERSISTENCE_PATH = '.swarm/sona-patterns.json';
|
|
22
23
|
const PATTERN_VERSION = '1.0.0';
|
|
23
24
|
const MIN_CONFIDENCE = 0.1;
|
|
24
25
|
const MAX_CONFIDENCE = 0.99;
|
|
@@ -105,7 +106,12 @@ export class SONAOptimizer {
|
|
|
105
106
|
qLearningRouter = null;
|
|
106
107
|
qLearningEnabled = false;
|
|
107
108
|
constructor(options) {
|
|
108
|
-
|
|
109
|
+
// Resolve persistencePath lazily here (#1168). When the caller supplies
|
|
110
|
+
// one we honor it verbatim (may be relative — preserved for existing
|
|
111
|
+
// tests/callers that join against their own cwd). When unset, we route
|
|
112
|
+
// through runtimePath so writes land under `.moflo/neural/` regardless
|
|
113
|
+
// of subprocess cwd.
|
|
114
|
+
this.persistencePath = options?.persistencePath || runtimePath('neural', 'sona-patterns.json');
|
|
109
115
|
}
|
|
110
116
|
/**
|
|
111
117
|
* Initialize the optimizer and load persisted state
|
|
@@ -499,9 +505,17 @@ export class SONAOptimizer {
|
|
|
499
505
|
*/
|
|
500
506
|
loadFromDisk() {
|
|
501
507
|
try {
|
|
502
|
-
|
|
508
|
+
// Treat absolute persistencePath verbatim (new #1168 default routes
|
|
509
|
+
// through runtimePath → absolute `.moflo/neural/...`); relative paths
|
|
510
|
+
// preserve the pre-#1168 behaviour of resolving against cwd.
|
|
511
|
+
let fullPath = isAbsolute(this.persistencePath)
|
|
512
|
+
? this.persistencePath
|
|
513
|
+
: resolve(process.cwd(), this.persistencePath);
|
|
503
514
|
if (!existsSync(fullPath)) {
|
|
504
|
-
|
|
515
|
+
const legacy = legacySwarmPath('sona-patterns.json');
|
|
516
|
+
if (!existsSync(legacy))
|
|
517
|
+
return false;
|
|
518
|
+
fullPath = legacy;
|
|
505
519
|
}
|
|
506
520
|
const data = readFileSync(fullPath, 'utf-8');
|
|
507
521
|
const state = JSON.parse(data);
|
|
@@ -536,7 +550,11 @@ export class SONAOptimizer {
|
|
|
536
550
|
*/
|
|
537
551
|
saveToDisk() {
|
|
538
552
|
try {
|
|
539
|
-
|
|
553
|
+
// See loadFromDisk: absolute persistencePath is taken verbatim, relative
|
|
554
|
+
// paths resolve against cwd. New #1168 default writes to `.moflo/neural/`.
|
|
555
|
+
const fullPath = isAbsolute(this.persistencePath)
|
|
556
|
+
? this.persistencePath
|
|
557
|
+
: resolve(process.cwd(), this.persistencePath);
|
|
540
558
|
const dir = dirname(fullPath);
|
|
541
559
|
// Ensure directory exists
|
|
542
560
|
if (!existsSync(dir)) {
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* - Rank decomposition (r << d) for memory efficiency
|
|
9
9
|
* - Additive weight updates: W' = W + BA (where B ∈ R^{d×r}, A ∈ R^{r×k})
|
|
10
10
|
* - Support for multiple adaptation heads
|
|
11
|
-
* - Persistence to .swarm/lora-weights.json
|
|
11
|
+
* - Persistence to .moflo/movector/lora-weights.json (legacy fallback: .swarm/lora-weights.json)
|
|
12
12
|
*
|
|
13
13
|
* Memory savings:
|
|
14
14
|
* - Original: d × k parameters
|
|
@@ -18,7 +18,8 @@
|
|
|
18
18
|
* @module lora-adapter
|
|
19
19
|
*/
|
|
20
20
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
21
|
-
import { dirname
|
|
21
|
+
import { dirname } from 'path';
|
|
22
|
+
import { legacySwarmPath, runtimePath } from '../services/moflo-paths.js';
|
|
22
23
|
// ============================================================================
|
|
23
24
|
// Types & Constants
|
|
24
25
|
// ============================================================================
|
|
@@ -47,7 +48,6 @@ const DEFAULT_CONFIG = {
|
|
|
47
48
|
inputDim: INPUT_DIM,
|
|
48
49
|
outputDim: OUTPUT_DIM,
|
|
49
50
|
learningRate: 0.001,
|
|
50
|
-
weightsPath: join(process.cwd(), '.swarm', 'lora-weights.json'),
|
|
51
51
|
enableDropout: true,
|
|
52
52
|
dropoutProb: 0.1,
|
|
53
53
|
autoSaveInterval: 50,
|
|
@@ -67,7 +67,16 @@ export class LoRAAdapter {
|
|
|
67
67
|
lastUpdate = null;
|
|
68
68
|
updatesSinceLastSave = 0;
|
|
69
69
|
constructor(config) {
|
|
70
|
-
|
|
70
|
+
// Resolve weightsPath lazily here, not at module load — captures the
|
|
71
|
+
// *current* consumer project root, not the cwd at first import (#1168).
|
|
72
|
+
// Default-rescue runs *last* so an explicit `weightsPath: undefined` from
|
|
73
|
+
// the caller still falls back to the canonical path instead of crashing
|
|
74
|
+
// save/load on an undefined string.
|
|
75
|
+
this.config = {
|
|
76
|
+
...DEFAULT_CONFIG,
|
|
77
|
+
...config,
|
|
78
|
+
weightsPath: config?.weightsPath ?? runtimePath('movector', 'lora-weights.json'),
|
|
79
|
+
};
|
|
71
80
|
this.weights = this.initializeWeights();
|
|
72
81
|
}
|
|
73
82
|
/**
|
|
@@ -309,10 +318,16 @@ export class LoRAAdapter {
|
|
|
309
318
|
*/
|
|
310
319
|
loadWeights() {
|
|
311
320
|
try {
|
|
312
|
-
|
|
313
|
-
|
|
321
|
+
// Canonical path first, then legacy `.swarm/lora-weights.json` as a
|
|
322
|
+
// read-only fallback for consumers who upgraded mid-training (#1168).
|
|
323
|
+
let sourcePath = this.config.weightsPath;
|
|
324
|
+
if (!existsSync(sourcePath)) {
|
|
325
|
+
const legacy = legacySwarmPath('lora-weights.json');
|
|
326
|
+
if (!existsSync(legacy))
|
|
327
|
+
return false;
|
|
328
|
+
sourcePath = legacy;
|
|
314
329
|
}
|
|
315
|
-
const content = readFileSync(
|
|
330
|
+
const content = readFileSync(sourcePath, 'utf-8');
|
|
316
331
|
const data = JSON.parse(content);
|
|
317
332
|
if (data.version !== 1) {
|
|
318
333
|
return false;
|
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
* - Gating network for soft expert selection (top-k)
|
|
7
7
|
* - Online weight updates via reward signals
|
|
8
8
|
* - Load balancing with auxiliary loss
|
|
9
|
-
* - Weight persistence to .
|
|
9
|
+
* - Weight persistence to .moflo/movector/moe-weights.json
|
|
10
|
+
* (legacy fallback read: .swarm/moe-weights.json)
|
|
10
11
|
*
|
|
11
12
|
* Architecture:
|
|
12
13
|
* - Input: 384-dim task embedding (from ONNX)
|
|
@@ -17,6 +18,7 @@
|
|
|
17
18
|
*/
|
|
18
19
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
19
20
|
import { dirname } from 'path';
|
|
21
|
+
import { legacySwarmPath, runtimePath } from '../services/moflo-paths.js';
|
|
20
22
|
/**
|
|
21
23
|
* Expert names in order (index corresponds to expert slot)
|
|
22
24
|
*/
|
|
@@ -43,14 +45,15 @@ export const INPUT_DIM = 384;
|
|
|
43
45
|
*/
|
|
44
46
|
export const HIDDEN_DIM = 128;
|
|
45
47
|
/**
|
|
46
|
-
* Default configuration
|
|
48
|
+
* Default configuration. `weightsPath` is overridden lazily in the
|
|
49
|
+
* MoERouter constructor (see #1168) so the path resolves against the
|
|
50
|
+
* consumer's project root, not the module-load cwd.
|
|
47
51
|
*/
|
|
48
52
|
const DEFAULT_CONFIG = {
|
|
49
53
|
topK: 2,
|
|
50
54
|
learningRate: 0.01,
|
|
51
55
|
temperature: 1.0,
|
|
52
56
|
loadBalanceCoef: 0.01,
|
|
53
|
-
weightsPath: '.swarm/moe-weights.json',
|
|
54
57
|
autoSaveInterval: 50,
|
|
55
58
|
enableNoise: true,
|
|
56
59
|
noiseStd: 0.1,
|
|
@@ -202,7 +205,15 @@ export class MoERouter {
|
|
|
202
205
|
lastProbs = null;
|
|
203
206
|
lastSelectedExperts = [];
|
|
204
207
|
constructor(config = {}) {
|
|
205
|
-
|
|
208
|
+
// Resolve weightsPath lazily here (#1168) — the default routes through
|
|
209
|
+
// findProjectRoot at construct-time, not module-load time. Default-rescue
|
|
210
|
+
// runs *last* so an explicit `weightsPath: undefined` falls back to the
|
|
211
|
+
// canonical path instead of leaving the field undefined.
|
|
212
|
+
this.config = {
|
|
213
|
+
...DEFAULT_CONFIG,
|
|
214
|
+
...config,
|
|
215
|
+
weightsPath: config.weightsPath ?? runtimePath('movector', 'moe-weights.json'),
|
|
216
|
+
};
|
|
206
217
|
// Initialize weights
|
|
207
218
|
this.W1 = xavierInit(INPUT_DIM, HIDDEN_DIM);
|
|
208
219
|
this.b1 = new Float32Array(HIDDEN_DIM);
|
|
@@ -445,10 +456,15 @@ export class MoERouter {
|
|
|
445
456
|
* Load weights from persistence file
|
|
446
457
|
*/
|
|
447
458
|
async loadWeights(path) {
|
|
448
|
-
|
|
459
|
+
// Canonical path first, then legacy `.swarm/moe-weights.json` as a
|
|
460
|
+
// read-only fallback for consumers who upgraded mid-training (#1168).
|
|
461
|
+
let weightsPath = path || this.config.weightsPath;
|
|
449
462
|
try {
|
|
450
463
|
if (!existsSync(weightsPath)) {
|
|
451
|
-
|
|
464
|
+
const legacy = legacySwarmPath('moe-weights.json');
|
|
465
|
+
if (!existsSync(legacy))
|
|
466
|
+
return false;
|
|
467
|
+
weightsPath = legacy;
|
|
452
468
|
}
|
|
453
469
|
const data = readFileSync(weightsPath, 'utf-8');
|
|
454
470
|
const model = JSON.parse(data);
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
*/
|
|
22
22
|
import { homedir } from 'node:os';
|
|
23
23
|
import { join } from 'node:path';
|
|
24
|
+
import { findProjectRoot } from './project-root.js';
|
|
24
25
|
export const MOFLO_DIR = '.moflo';
|
|
25
26
|
/** Canonical memory DB filename (post-#727). Lives at `<root>/.moflo/moflo.db`. */
|
|
26
27
|
export const MEMORY_DB_FILE = 'moflo.db';
|
|
@@ -68,6 +69,25 @@ export function legacyHnswIndexPath(projectRoot) {
|
|
|
68
69
|
export function legacyMemoryDbBakPath(projectRoot) {
|
|
69
70
|
return join(projectRoot, LEGACY_SWARM_DIR, `${LEGACY_MEMORY_DB_FILE}${LEGACY_MEMORY_DB_BAK_SUFFIX}`);
|
|
70
71
|
}
|
|
72
|
+
/**
|
|
73
|
+
* Resolve a runtime-state path under `.moflo/<subdir>/<filename>` at the
|
|
74
|
+
* project root. Lazy by design — every call routes through `findProjectRoot()`
|
|
75
|
+
* so the path never bakes in `process.cwd()` at module-load time (#1168 bug
|
|
76
|
+
* class). Use this for any persisted runtime state the daemon, MCP server,
|
|
77
|
+
* or neural runtime writes (weights, fisher matrix, routing patterns, etc.).
|
|
78
|
+
*/
|
|
79
|
+
export function runtimePath(subdir, filename) {
|
|
80
|
+
return join(mofloDir(findProjectRoot()), subdir, filename);
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Legacy `.swarm/<filename>` path at the project root. Read-only fallback for
|
|
84
|
+
* consumers who saved state under the pre-#1168 location; never written by
|
|
85
|
+
* production code (enforced by the drift-guard in
|
|
86
|
+
* `published-package-drift-guard.test.ts`).
|
|
87
|
+
*/
|
|
88
|
+
export function legacySwarmPath(filename) {
|
|
89
|
+
return join(findProjectRoot(), LEGACY_SWARM_DIR, filename);
|
|
90
|
+
}
|
|
71
91
|
/**
|
|
72
92
|
* Memory-DB probe order used by every reader that does best-effort detection
|
|
73
93
|
* (statusline, doctor, swarm status, hooks aggregator). Canonical first so
|
package/dist/src/cli/version.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "moflo",
|
|
3
|
-
"version": "4.10.
|
|
3
|
+
"version": "4.10.12",
|
|
4
4
|
"description": "MoFlo — AI agent orchestration for Claude Code. A standalone, opinionated toolkit with semantic memory, learned routing, gates, spells, and the /flo issue-execution skill.",
|
|
5
5
|
"main": "dist/src/cli/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -95,7 +95,7 @@
|
|
|
95
95
|
"@typescript-eslint/eslint-plugin": "^7.18.0",
|
|
96
96
|
"@typescript-eslint/parser": "^7.18.0",
|
|
97
97
|
"eslint": "^8.0.0",
|
|
98
|
-
"moflo": "^4.10.
|
|
98
|
+
"moflo": "^4.10.11",
|
|
99
99
|
"tsx": "^4.21.0",
|
|
100
100
|
"typescript": "^5.9.3",
|
|
101
101
|
"vitest": "^4.0.0"
|