moflo 4.10.1 → 4.10.3
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/skills/healer/SKILL.md +3 -1
- package/bin/session-start-launcher.mjs +112 -5
- package/dist/src/cli/commands/doctor-checks-config.js +4 -4
- package/dist/src/cli/commands/doctor-checks-memory-access.js +27 -1
- package/dist/src/cli/commands/doctor-embedding-hygiene.js +48 -12
- package/dist/src/cli/commands/doctor-render.js +118 -74
- package/dist/src/cli/commands/doctor-version.js +1 -1
- package/dist/src/cli/commands/doctor.js +70 -25
- package/dist/src/cli/commands/index.js +0 -6
- package/dist/src/cli/init/executor.js +2 -2
- package/dist/src/cli/mcp-tools/swarm-tools.js +3 -4
- package/dist/src/cli/memory/bridge-core.js +36 -0
- package/dist/src/cli/services/moflo-paths.js +6 -5
- package/dist/src/cli/services/moflo-require.js +2 -2
- package/dist/src/cli/shared/core/config/loader.js +2 -2
- package/dist/src/cli/version.js +1 -1
- package/package.json +2 -2
- package/dist/src/cli/appliance/gguf-engine.js +0 -425
- package/dist/src/cli/appliance/ruvllm-bridge.js +0 -231
- package/dist/src/cli/appliance/rvfa-builder.js +0 -325
- package/dist/src/cli/appliance/rvfa-distribution.js +0 -370
- package/dist/src/cli/appliance/rvfa-format.js +0 -393
- package/dist/src/cli/appliance/rvfa-runner.js +0 -238
- package/dist/src/cli/appliance/rvfa-signing.js +0 -351
- package/dist/src/cli/commands/appliance-advanced.js +0 -213
- package/dist/src/cli/commands/appliance.js +0 -404
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
import { output } from '../output.js';
|
|
14
14
|
import { allChecks, componentMap, zombieScanCheck } from './doctor-registry.js';
|
|
15
|
-
import { emitJsonOutput, finalize, formatCheck, maybeAutoInstallClaudeCode, renderSummary, runAutoFix,
|
|
15
|
+
import { emitJsonOutput, finalize, formatCheck, maybeAutoInstallClaudeCode, renderSummary, runAutoFix, runKillZombies, } from './doctor-render.js';
|
|
16
16
|
import { checkEmbeddings } from './doctor-checks-memory.js';
|
|
17
17
|
import { checkMofloYamlCompliance } from './doctor-checks-config.js';
|
|
18
18
|
// Re-export for tests + external consumers (#639 stale-vector-stats test
|
|
@@ -125,24 +125,21 @@ export const doctorCommand = {
|
|
|
125
125
|
output.writeln(output.warning('--allow-warn requires --strict; ignoring (warnings are tolerated by default).'));
|
|
126
126
|
output.writeln();
|
|
127
127
|
}
|
|
128
|
-
if (killZombies) {
|
|
129
|
-
await runKillZombiesBanner();
|
|
130
|
-
}
|
|
131
128
|
const checksToRun = component && componentMap[component]
|
|
132
129
|
? [componentMap[component]]
|
|
133
130
|
: allChecks;
|
|
134
131
|
const results = [];
|
|
135
132
|
const fixes = [];
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
: output.createSpinner({ text: 'Running health checks in parallel...', spinner: 'dots' });
|
|
140
|
-
spinner?.start();
|
|
133
|
+
let zombieScan;
|
|
134
|
+
let claudeCodeInstall;
|
|
135
|
+
let fixesApplied;
|
|
141
136
|
// Issue #818: in --json mode, several deep checks (spell engine probe,
|
|
142
137
|
// mcp-spell bridge, etc.) write `[spell] ...` log lines straight to
|
|
143
138
|
// stdout — that breaks the single-JSON-document contract. Capture and
|
|
144
|
-
// discard stdout writes while checks run; restore
|
|
145
|
-
// throw can't leave the process with a stubbed stdout.
|
|
139
|
+
// discard stdout writes while checks AND post-check actions run; restore
|
|
140
|
+
// in `finally` so a throw can't leave the process with a stubbed stdout.
|
|
141
|
+
// Issue #1122: extended to wrap zombie-kill banner, --install, and
|
|
142
|
+
// --fix work so each runs on the JSON path with prose suppressed.
|
|
146
143
|
const realStdoutWrite = process.stdout.write.bind(process.stdout);
|
|
147
144
|
const restoreStdout = () => {
|
|
148
145
|
if (jsonOutput) {
|
|
@@ -153,7 +150,18 @@ export const doctorCommand = {
|
|
|
153
150
|
process.stdout.write =
|
|
154
151
|
(..._args) => true;
|
|
155
152
|
}
|
|
153
|
+
// OPTIMIZATION: Run all checks in parallel for 3-5x faster execution
|
|
154
|
+
const spinner = jsonOutput
|
|
155
|
+
? null
|
|
156
|
+
: output.createSpinner({ text: 'Running health checks in parallel...', spinner: 'dots' });
|
|
156
157
|
try {
|
|
158
|
+
// Issue #1122: kill-zombies prose used to write BEFORE the JSON
|
|
159
|
+
// suppression activated, corrupting the JSON document. Now runs
|
|
160
|
+
// under suppression and feeds a structured result into the payload.
|
|
161
|
+
if (killZombies) {
|
|
162
|
+
zombieScan = await runKillZombies({ silent: jsonOutput });
|
|
163
|
+
}
|
|
164
|
+
spinner?.start();
|
|
157
165
|
let checkResults;
|
|
158
166
|
try {
|
|
159
167
|
checkResults = await Promise.allSettled(checksToRun.map(check => check()));
|
|
@@ -174,7 +182,6 @@ export const doctorCommand = {
|
|
|
174
182
|
}
|
|
175
183
|
finally {
|
|
176
184
|
spinner?.stop();
|
|
177
|
-
restoreStdout();
|
|
178
185
|
}
|
|
179
186
|
for (const settledResult of checkResults) {
|
|
180
187
|
if (settledResult.status === 'fulfilled') {
|
|
@@ -197,26 +204,64 @@ export const doctorCommand = {
|
|
|
197
204
|
output.writeln(formatCheck(errorResult));
|
|
198
205
|
}
|
|
199
206
|
}
|
|
207
|
+
// Issue #1122: action flags must run on BOTH the JSON path and the
|
|
208
|
+
// formatted path. Previously the JSON branch early-returned before
|
|
209
|
+
// any of this ran, so `--json --fix` (and `--json --install`) silently
|
|
210
|
+
// no-op'd. Now they execute under stdout suppression and their
|
|
211
|
+
// outcomes feed the JSON payload below.
|
|
212
|
+
if (autoInstall) {
|
|
213
|
+
claudeCodeInstall = await maybeAutoInstallClaudeCode(results, fixes, { silent: jsonOutput });
|
|
214
|
+
}
|
|
215
|
+
if (!jsonOutput)
|
|
216
|
+
renderSummary(results);
|
|
217
|
+
if (showFix && fixes.length > 0) {
|
|
218
|
+
const outcome = await runAutoFix(results, fixes, checksToRun, { silent: jsonOutput });
|
|
219
|
+
fixesApplied = outcome.fixesApplied;
|
|
220
|
+
// Replace `results` with post-fix state so JSON consumers see the
|
|
221
|
+
// re-evaluated truth, not the pre-fix snapshot. Mirror the #992
|
|
222
|
+
// post-parallel zombie-scan append so the post-fix shape matches
|
|
223
|
+
// pre-fix shape (otherwise `--json --fix` silently drops the
|
|
224
|
+
// Zombie Processes entry from the JSON `results[]`).
|
|
225
|
+
if (outcome.reEvaluated) {
|
|
226
|
+
const finalChecks = [...outcome.reEvaluated];
|
|
227
|
+
if (!component) {
|
|
228
|
+
try {
|
|
229
|
+
finalChecks.push(await zombieScanCheck());
|
|
230
|
+
}
|
|
231
|
+
catch (reason) {
|
|
232
|
+
finalChecks.push({
|
|
233
|
+
name: 'Zombie Processes',
|
|
234
|
+
status: 'fail',
|
|
235
|
+
message: reason?.message ?? 'Unknown error',
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
results.length = 0;
|
|
240
|
+
results.push(...finalChecks);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
else if (fixes.length > 0 && !showFix && !jsonOutput) {
|
|
244
|
+
output.writeln();
|
|
245
|
+
output.writeln(output.dim(`Run with --fix to auto-fix ${fixes.length} issue${fixes.length > 1 ? 's' : ''}`));
|
|
246
|
+
}
|
|
200
247
|
}
|
|
201
248
|
catch {
|
|
202
249
|
spinner?.stop();
|
|
203
|
-
restoreStdout();
|
|
204
250
|
if (!jsonOutput)
|
|
205
251
|
output.writeln(output.error('Failed to run health checks'));
|
|
206
252
|
}
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
}
|
|
210
|
-
if (autoInstall) {
|
|
211
|
-
await maybeAutoInstallClaudeCode(results, fixes);
|
|
212
|
-
}
|
|
213
|
-
renderSummary(results);
|
|
214
|
-
if (showFix && fixes.length > 0) {
|
|
215
|
-
await runAutoFix(results, fixes, checksToRun);
|
|
253
|
+
finally {
|
|
254
|
+
restoreStdout();
|
|
216
255
|
}
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
256
|
+
if (jsonOutput) {
|
|
257
|
+
return emitJsonOutput({
|
|
258
|
+
results,
|
|
259
|
+
strict,
|
|
260
|
+
allowWarnList,
|
|
261
|
+
fixesApplied,
|
|
262
|
+
zombieScan,
|
|
263
|
+
claudeCodeInstall,
|
|
264
|
+
});
|
|
220
265
|
}
|
|
221
266
|
return finalize({ results, strict, allowWarnList });
|
|
222
267
|
},
|
|
@@ -62,8 +62,6 @@ const commandLoaders = {
|
|
|
62
62
|
benchmark: () => import('./benchmark.js'),
|
|
63
63
|
// Guidance Control Plane
|
|
64
64
|
guidance: () => import('./guidance.js'),
|
|
65
|
-
// RVFA Appliance Management
|
|
66
|
-
appliance: () => import('./appliance.js'),
|
|
67
65
|
// MoFlo Spell Gates
|
|
68
66
|
gate: () => import('./gate.js'),
|
|
69
67
|
// Feature Orchestrator
|
|
@@ -137,7 +135,6 @@ import { issuesCommand } from './issues.js';
|
|
|
137
135
|
import updateCommand from './update.js';
|
|
138
136
|
import { processCommand } from './process.js';
|
|
139
137
|
import { guidanceCommand } from './guidance.js';
|
|
140
|
-
import { applianceCommand } from './appliance.js';
|
|
141
138
|
import { diagnoseCommand } from './diagnose.js';
|
|
142
139
|
import { githubCommand } from './github.js';
|
|
143
140
|
// Pre-populate cache with core commands
|
|
@@ -184,7 +181,6 @@ export { performanceCommand } from './performance.js';
|
|
|
184
181
|
export { securityCommand } from './security.js';
|
|
185
182
|
export { hiveMindCommand } from './hive-mind.js';
|
|
186
183
|
export { guidanceCommand } from './guidance.js';
|
|
187
|
-
export { applianceCommand } from './appliance.js';
|
|
188
184
|
export { diagnoseCommand } from './diagnose.js';
|
|
189
185
|
export { githubCommand } from './github.js';
|
|
190
186
|
// Lazy-loaded command re-exports (for backwards compatibility, but async-only)
|
|
@@ -209,7 +205,6 @@ export async function getRouteCommand() { return loadCommand('route'); }
|
|
|
209
205
|
export async function getProgressCommand() { return loadCommand('progress'); }
|
|
210
206
|
export async function getIssuesCommand() { return loadCommand('issues'); }
|
|
211
207
|
export async function getGuidanceCommand() { return loadCommand('guidance'); }
|
|
212
|
-
export async function getApplianceCommand() { return loadCommand('appliance'); }
|
|
213
208
|
/**
|
|
214
209
|
* Core commands loaded synchronously (available immediately)
|
|
215
210
|
* Advanced commands loaded on-demand for faster startup
|
|
@@ -285,7 +280,6 @@ export const commandsByCategory = {
|
|
|
285
280
|
issuesCommand,
|
|
286
281
|
updateCommand,
|
|
287
282
|
processCommand,
|
|
288
|
-
applianceCommand,
|
|
289
283
|
githubCommand,
|
|
290
284
|
],
|
|
291
285
|
};
|
|
@@ -70,8 +70,8 @@ const COMMANDS_MAP = {};
|
|
|
70
70
|
* Agents to copy based on configuration. Exported for integrity tests.
|
|
71
71
|
*
|
|
72
72
|
* Each value is a directory name under `.claude/agents/` that ships in the
|
|
73
|
-
* moflo package. After #932 retired ~50
|
|
74
|
-
*
|
|
73
|
+
* moflo package. After #932 retired ~50 aspirational agents, the set is
|
|
74
|
+
* narrowed to actual development specialties Claude is likely to invoke.
|
|
75
75
|
*/
|
|
76
76
|
export const AGENTS_MAP = {
|
|
77
77
|
core: ['core'],
|
|
@@ -12,7 +12,7 @@ import { getSwarmCoordinator, isSwarmCoordinatorInitialized, } from './swarm-coo
|
|
|
12
12
|
import { scaleHandler, SCALE_STRATEGIES, TARGET_AGENTS_MIN, TARGET_AGENTS_MAX, } from './swarm-scale-handler.js';
|
|
13
13
|
import { findProjectRoot } from '../services/project-root.js';
|
|
14
14
|
import { MOFLO_DIR } from '../services/moflo-paths.js';
|
|
15
|
-
// Inputs accepted by the MCP layer (covers
|
|
15
|
+
// Inputs accepted by the MCP layer (covers legacy aliases). The coordinator's
|
|
16
16
|
// TopologyType is narrower: 'mesh' | 'hierarchical' | 'centralized' | 'hybrid'.
|
|
17
17
|
const TOPOLOGY_MAP = {
|
|
18
18
|
hierarchical: 'hierarchical',
|
|
@@ -23,9 +23,8 @@ const TOPOLOGY_MAP = {
|
|
|
23
23
|
'hierarchical-mesh': 'hybrid',
|
|
24
24
|
hybrid: 'hybrid',
|
|
25
25
|
};
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
// `byzantine`/`raft`/`gossip`/`paxos`.
|
|
26
|
+
// `unanimous`/`weighted`/`majority` are the user-facing aliases; the
|
|
27
|
+
// coordinator only speaks `byzantine`/`raft`/`gossip`/`paxos`.
|
|
29
28
|
const CONSENSUS_MAP = {
|
|
30
29
|
unanimous: { algorithm: 'byzantine', threshold: 1.0 },
|
|
31
30
|
byzantine: { algorithm: 'byzantine', threshold: 1.0 },
|
|
@@ -95,6 +95,27 @@ export function logBridgeError(context, err, opts) {
|
|
|
95
95
|
const msg = errorDetail(err);
|
|
96
96
|
console.error(`[moflo] ${context}: ${msg}`);
|
|
97
97
|
}
|
|
98
|
+
/**
|
|
99
|
+
* Recognises the node:sqlite "operation on closed handle" error shape.
|
|
100
|
+
*
|
|
101
|
+
* #1123 — A concurrent `withDb` call's `checkBridgeCoherence` can fire
|
|
102
|
+
* `shutdownBridge()` between our `getDb(registry)` and `fn(ctx, registry)`,
|
|
103
|
+
* closing the underlying `DatabaseSync`. Our previously-captured `ctx.db`
|
|
104
|
+
* then throws `ERR_INVALID_STATE: database is not open` on the next op.
|
|
105
|
+
*
|
|
106
|
+
* The operation hadn't started its mutation yet, so a single retry against a
|
|
107
|
+
* fresh registry is safe (matches the `withBusyRetry` shape for SQLITE_BUSY).
|
|
108
|
+
* Bounded to one retry so a *genuinely* broken DB still surfaces — we don't
|
|
109
|
+
* want to mask a registry that can't be re-acquired.
|
|
110
|
+
*/
|
|
111
|
+
function isStaleHandleError(err) {
|
|
112
|
+
if (!err || typeof err !== 'object')
|
|
113
|
+
return false;
|
|
114
|
+
const e = err;
|
|
115
|
+
if (e.code === 'ERR_INVALID_STATE')
|
|
116
|
+
return true;
|
|
117
|
+
return typeof e.message === 'string' && /database is not open/i.test(e.message);
|
|
118
|
+
}
|
|
98
119
|
/**
|
|
99
120
|
* Treats an error as a SQLITE_BUSY lock-contention failure if either the
|
|
100
121
|
* error code or message indicates it. Belt-and-suspenders around node:sqlite,
|
|
@@ -456,6 +477,9 @@ async function checkBridgeCoherence(dbPath) {
|
|
|
456
477
|
* self-fire is suppressed.
|
|
457
478
|
*/
|
|
458
479
|
export async function withDb(dbPath, fn) {
|
|
480
|
+
return withDbInner(dbPath, fn, 0);
|
|
481
|
+
}
|
|
482
|
+
async function withDbInner(dbPath, fn, attempt) {
|
|
459
483
|
await checkBridgeCoherence(dbPath);
|
|
460
484
|
const registry = await getRegistry(dbPath);
|
|
461
485
|
if (!registry)
|
|
@@ -510,6 +534,18 @@ export async function withDb(dbPath, fn) {
|
|
|
510
534
|
return result;
|
|
511
535
|
}
|
|
512
536
|
catch (err) {
|
|
537
|
+
// #1123 — stale-handle race: a concurrent withDb's coherence check tore
|
|
538
|
+
// the registry down between our getDb() and fn() execution, closing the
|
|
539
|
+
// underlying DatabaseSync. Drop the dead handle and retry once against a
|
|
540
|
+
// freshly-acquired registry. The first attempt threw BEFORE its mutation
|
|
541
|
+
// landed (node:sqlite errors at prepare/exec time, not mid-statement), so
|
|
542
|
+
// a retry is idempotent. Bounded to one retry so a genuinely-unrecoverable
|
|
543
|
+
// bridge (e.g. corrupt file, missing module) still surfaces as a null
|
|
544
|
+
// return + logged error, not an infinite loop.
|
|
545
|
+
if (attempt === 0 && isStaleHandleError(err)) {
|
|
546
|
+
await shutdownBridge();
|
|
547
|
+
return await withDbInner(dbPath, fn, attempt + 1);
|
|
548
|
+
}
|
|
513
549
|
logBridgeError('bridge operation failed', err);
|
|
514
550
|
return null;
|
|
515
551
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* MoFlo runtime state directory constants.
|
|
3
3
|
*
|
|
4
|
-
* MoFlo owns its state under `.moflo/` at the project root.
|
|
5
|
-
*
|
|
4
|
+
* MoFlo owns its state under `.moflo/` at the project root. Pre-#699 builds
|
|
5
|
+
* used `.claude-flow/`; both legacy locations are still recognized as
|
|
6
6
|
* read-only sources for the version-bump-gated cherry-pick (#851) but are
|
|
7
7
|
* never relocated or renamed automatically — leaving them in place gives
|
|
8
8
|
* consumers a recovery source and avoids the failure modes that motivated
|
|
@@ -27,11 +27,12 @@ export const MEMORY_DB_FILE = 'moflo.db';
|
|
|
27
27
|
/** HNSW persisted index sidecar. Lives next to the DB at `<root>/.moflo/hnsw.index`. */
|
|
28
28
|
export const HNSW_INDEX_FILE = 'hnsw.index';
|
|
29
29
|
/**
|
|
30
|
-
* Legacy runtime directory
|
|
31
|
-
* migration code paths — production code should use
|
|
30
|
+
* Legacy `.claude-flow/` runtime directory used by pre-#699 moflo builds.
|
|
31
|
+
* Only referenced from migration code paths — production code should use
|
|
32
|
+
* {@link MOFLO_DIR}.
|
|
32
33
|
*/
|
|
33
34
|
export const LEGACY_CLAUDE_FLOW_DIR = '.claude-flow';
|
|
34
|
-
/** Legacy `.swarm/` directory used by
|
|
35
|
+
/** Legacy `.swarm/` directory used by pre-#727 moflo builds for the memory DB. */
|
|
35
36
|
export const LEGACY_SWARM_DIR = '.swarm';
|
|
36
37
|
/** Legacy memory DB filename — only ever inside `.swarm/`. Pre-#727. */
|
|
37
38
|
export const LEGACY_MEMORY_DB_FILE = 'memory.db';
|
|
@@ -116,8 +116,8 @@ export function mofloResolve(specifier) {
|
|
|
116
116
|
// ≈ 6 hops to the moflo root. 12 gives headroom for worktree/monorepo layouts.
|
|
117
117
|
const MAX_WALK_DEPTH = 12;
|
|
118
118
|
// Names a package.json may carry while still being "us" — covers the moflo
|
|
119
|
-
// rename
|
|
120
|
-
const MOFLO_PACKAGE_NAMES = new Set(['moflo', 'claude-flow'
|
|
119
|
+
// rename from the pre-collapse claude-flow workspace.
|
|
120
|
+
const MOFLO_PACKAGE_NAMES = new Set(['moflo', 'claude-flow']);
|
|
121
121
|
// Walk up from this file's dir, returning the first non-null `test(dir)` result.
|
|
122
122
|
function walkUpFromSelf(test) {
|
|
123
123
|
let dir = dirname(fileURLToPath(import.meta.url));
|
|
@@ -11,8 +11,8 @@ import { defaultSystemConfig, mergeWithDefaults } from './defaults.js';
|
|
|
11
11
|
/**
|
|
12
12
|
* Configuration file names to search for. Canonical names come first;
|
|
13
13
|
* `claude-flow.*` names are kept as legacy fallback so consumers upgrading
|
|
14
|
-
* from
|
|
15
|
-
*
|
|
14
|
+
* from pre-#699 moflo builds keep loading their existing config without a
|
|
15
|
+
* manual rename.
|
|
16
16
|
*/
|
|
17
17
|
const CONFIG_FILE_NAMES = [
|
|
18
18
|
'moflo.config.json',
|
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.3",
|
|
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.2",
|
|
99
99
|
"tsx": "^4.21.0",
|
|
100
100
|
"typescript": "^5.9.3",
|
|
101
101
|
"vitest": "^4.0.0"
|