moflo 4.10.24 → 4.10.25
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-yaml-reference.md +5 -5
- package/.claude/skills/meditate/SKILL.md +2 -2
- package/bin/cli-hooks/statusline.js +12 -8
- package/bin/cli.js +1 -1
- package/bin/hooks.mjs +1 -1
- package/bin/lib/meditate.mjs +1 -0
- package/bin/lib/pii-scrub.mjs +2 -5
- package/dist/src/cli/commands/daemon.js +4 -3
- package/dist/src/cli/commands/doctor-checks-deep.js +2 -2
- package/dist/src/cli/commands/doctor-checks-swarm.js +122 -13
- package/dist/src/cli/commands/hooks.js +7 -23
- package/dist/src/cli/commands/init.js +1 -1
- package/dist/src/cli/commands/mcp.js +2 -1
- package/dist/src/cli/commands/session.js +1 -1
- package/dist/src/cli/commands/start.js +1 -1
- package/dist/src/cli/commands/status.js +1 -1
- package/dist/src/cli/commands/task.js +1 -1
- package/dist/src/cli/commands/update.js +12 -12
- package/dist/src/cli/guidance/analyzer.js +3 -3
- package/dist/src/cli/guidance/gates.js +1 -1
- package/dist/src/cli/guidance/hooks.js +1 -1
- package/dist/src/cli/guidance/meta-governance.js +1 -1
- package/dist/src/cli/hooks/index.js +1 -1
- package/dist/src/cli/hooks/reasoningbank/guidance-provider.js +1 -1
- package/dist/src/cli/hooks/workers/index.js +1 -1
- package/dist/src/cli/hooks/workers/session-hook.js +0 -40
- package/dist/src/cli/index.js +2 -2
- package/dist/src/cli/init/executor.js +36 -20
- package/dist/src/cli/init/mcp-generator.js +10 -8
- package/dist/src/cli/init/settings-generator.js +10 -7
- package/dist/src/cli/init/types.js +2 -2
- package/dist/src/cli/mcp-server.js +2 -1
- package/dist/src/cli/memory/bridge-loader.js +42 -0
- package/dist/src/cli/memory/embedding-model.js +157 -0
- package/dist/src/cli/memory/entries-read.js +380 -0
- package/dist/src/cli/memory/entries-shared.js +73 -0
- package/dist/src/cli/memory/entries-write.js +384 -0
- package/dist/src/cli/memory/hnsw-singleton.js +242 -0
- package/dist/src/cli/memory/init.js +367 -0
- package/dist/src/cli/memory/learnings-overview.js +156 -0
- package/dist/src/cli/memory/memory-initializer.js +37 -2257
- package/dist/src/cli/memory/quantization.js +221 -0
- package/dist/src/cli/memory/schema.js +382 -0
- package/dist/src/cli/memory/verify.js +178 -0
- package/dist/src/cli/movector/index.js +1 -1
- package/dist/src/cli/plugins/store/discovery.js +9 -9
- package/dist/src/cli/{transfer/ipfs/client.js → plugins/store/ipfs-client.js} +4 -1
- package/dist/src/cli/plugins/tests/demo-plugin-store.js +1 -1
- package/dist/src/cli/plugins/tests/standalone-test.js +1 -1
- package/dist/src/cli/runtime/headless.js +5 -4
- package/dist/src/cli/scripts/publish-registry.js +6 -6
- package/dist/src/cli/services/daemon-dashboard.js +108 -7
- package/dist/src/cli/services/daemon-readiness.js +1 -1
- package/dist/src/cli/services/daemon-service.js +1 -1
- package/dist/src/cli/services/env-compat.js +29 -0
- package/dist/src/cli/services/hook-block-hash.js +5 -6
- package/dist/src/cli/services/registry-api.js +1 -1
- package/dist/src/cli/shared/core/config/loader.js +19 -11
- package/dist/src/cli/shared/events/example-usage.js +2 -2
- package/dist/src/cli/shared/events/index.js +1 -1
- package/dist/src/cli/shared/index.js +1 -1
- package/dist/src/cli/shared/mcp/index.js +1 -1
- package/dist/src/cli/shared/mcp/server.js +3 -3
- package/dist/src/cli/shared/plugin-interface.js +1 -1
- package/dist/src/cli/shared/plugins/index.js +1 -1
- package/dist/src/cli/shared/plugins/official/index.js +1 -1
- package/dist/src/cli/shared/security/index.js +1 -1
- package/dist/src/cli/shared/services/v3-progress.service.js +40 -29
- package/dist/src/cli/shared/types.js +1 -1
- package/dist/src/cli/swarm/coordination/swarm-hub.js +1 -1
- package/dist/src/cli/update/index.js +1 -1
- package/dist/src/cli/update/rate-limiter.js +3 -2
- package/dist/src/cli/version.js +1 -1
- package/package.json +2 -2
- package/dist/src/cli/commands/transfer-store.js +0 -428
- package/dist/src/cli/transfer/anonymization/index.js +0 -281
- package/dist/src/cli/transfer/deploy-seraphine.js +0 -205
- package/dist/src/cli/transfer/export.js +0 -113
- package/dist/src/cli/transfer/index.js +0 -31
- package/dist/src/cli/transfer/ipfs/upload.js +0 -411
- package/dist/src/cli/transfer/models/seraphine.js +0 -373
- package/dist/src/cli/transfer/serialization/cfp.js +0 -184
- package/dist/src/cli/transfer/storage/gcs.js +0 -242
- package/dist/src/cli/transfer/storage/index.js +0 -6
- package/dist/src/cli/transfer/store/discovery.js +0 -382
- package/dist/src/cli/transfer/store/download.js +0 -334
- package/dist/src/cli/transfer/store/index.js +0 -153
- package/dist/src/cli/transfer/store/publish.js +0 -294
- package/dist/src/cli/transfer/store/registry.js +0 -285
- package/dist/src/cli/transfer/store/search.js +0 -232
- package/dist/src/cli/transfer/store/tests/standalone-test.js +0 -190
- package/dist/src/cli/transfer/store/types.js +0 -6
- package/dist/src/cli/transfer/test-seraphine.js +0 -105
- package/dist/src/cli/transfer/tests/test-store.js +0 -214
- package/dist/src/cli/transfer/types.js +0 -6
|
@@ -164,17 +164,17 @@ Config is loaded from `moflo.yaml` at the project root — there is no env var t
|
|
|
164
164
|
|
|
165
165
|
```bash
|
|
166
166
|
# Logging
|
|
167
|
-
|
|
167
|
+
MOFLO_LOG_LEVEL=info # debug | info | warn | error
|
|
168
168
|
|
|
169
169
|
# MCP Server (stdio transport — no port)
|
|
170
|
-
|
|
170
|
+
MOFLO_MCP_TRANSPORT=stdio
|
|
171
171
|
|
|
172
172
|
# Memory backend (legacy SystemConfig env vars — moflo.yaml `memory.backend` is the modern surface)
|
|
173
|
-
|
|
174
|
-
|
|
173
|
+
MOFLO_MEMORY_BACKEND=sqlite # informational only; not consumed by selectProvider
|
|
174
|
+
MOFLO_MEMORY_TYPE=sqlite # SystemConfig override (legacy)
|
|
175
175
|
```
|
|
176
176
|
|
|
177
|
-
|
|
177
|
+
These use the canonical `MOFLO_` prefix. The pre-rebrand `CLAUDE_FLOW_` names are still read as a fallback for one deprecation cycle, so consumers upgraded from `claude-flow` keep working without a manual rename.
|
|
178
178
|
|
|
179
179
|
---
|
|
180
180
|
|
|
@@ -87,11 +87,11 @@ mcp__moflo__memory_store {
|
|
|
87
87
|
namespace: "learnings",
|
|
88
88
|
key: "<stable descriptive slug, e.g. pattern:daemon-port-resolver or gotcha:windows-spell-path>",
|
|
89
89
|
value: "<the lesson> — Why: <why it matters>. How to apply: <what to do next time>.",
|
|
90
|
-
tags: ["<topic>", "<area>"]
|
|
90
|
+
tags: ["<topic>", "<area>", "source:meditate-manual"]
|
|
91
91
|
}
|
|
92
92
|
```
|
|
93
93
|
|
|
94
|
-
Keep keys stable and descriptive so the next `/meditate` updates rather than re-adds. In `--preview` mode, **stop here** — print the candidates and their would-be keys/dedup verdicts, write nothing.
|
|
94
|
+
Always include the `source:meditate-manual` tag — it lets the Luminarium "Learnings" panel attribute each lesson to `/meditate` vs the automatic auto-meditate distill. Keep keys stable and descriptive so the next `/meditate` updates rather than re-adds. In `--preview` mode, **stop here** — print the candidates and their would-be keys/dedup verdicts, write nothing.
|
|
95
95
|
|
|
96
96
|
## Step 4 — Report
|
|
97
97
|
|
|
@@ -36,10 +36,10 @@ Usage:
|
|
|
36
36
|
statusline --help Show this help
|
|
37
37
|
|
|
38
38
|
Environment Variables:
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
39
|
+
MOFLO_STATUSLINE_REFRESH Refresh interval in ms
|
|
40
|
+
MOFLO_SHOW_HOOKS_METRICS Show hooks metrics (true/false)
|
|
41
|
+
MOFLO_SHOW_SWARM_ACTIVITY Show swarm activity (true/false)
|
|
42
|
+
MOFLO_SHOW_PERFORMANCE Show performance targets (true/false)
|
|
43
43
|
|
|
44
44
|
Examples:
|
|
45
45
|
statusline # Display formatted status
|
|
@@ -49,13 +49,17 @@ Examples:
|
|
|
49
49
|
process.exit(0);
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
// Create generator with environment-based config
|
|
52
|
+
// Create generator with environment-based config.
|
|
53
|
+
// #1209 — prefer MOFLO_* toggles, fall back to the pre-rebrand CLAUDE_FLOW_*
|
|
54
|
+
// names so consumers who set the old vars keep their statusline preferences.
|
|
55
|
+
const showEnv = (suffix) =>
|
|
56
|
+
process.env['MOFLO_' + suffix] ?? process.env['CLAUDE_' + 'FLOW_' + suffix];
|
|
53
57
|
const generator = new StatuslineGenerator({
|
|
54
58
|
enabled: true,
|
|
55
59
|
refreshOnHook: true,
|
|
56
|
-
showHooksMetrics:
|
|
57
|
-
showSwarmActivity:
|
|
58
|
-
showPerformance:
|
|
60
|
+
showHooksMetrics: showEnv('SHOW_HOOKS_METRICS') !== 'false',
|
|
61
|
+
showSwarmActivity: showEnv('SHOW_SWARM_ACTIVITY') !== 'false',
|
|
62
|
+
showPerformance: showEnv('SHOW_PERFORMANCE') !== 'false',
|
|
59
63
|
});
|
|
60
64
|
|
|
61
65
|
// Try to read from metrics database or files
|
package/bin/cli.js
CHANGED
package/bin/hooks.mjs
CHANGED
|
@@ -510,7 +510,7 @@ function runBackgroundTraining() {
|
|
|
510
510
|
// Falls back to a naive kill(0) check if the import fails (e.g. dist not built).
|
|
511
511
|
let _getDaemonLockHolder = null;
|
|
512
512
|
try {
|
|
513
|
-
const daemonLockPath = resolve(
|
|
513
|
+
const daemonLockPath = resolve(projectRoot, 'node_modules/moflo/dist/src/cli/services/daemon-lock.js');
|
|
514
514
|
if (existsSync(daemonLockPath)) {
|
|
515
515
|
const mod = await import(pathToFileURL(daemonLockPath).href);
|
|
516
516
|
_getDaemonLockHolder = mod.getDaemonLockHolder;
|
package/bin/lib/meditate.mjs
CHANGED
|
@@ -488,6 +488,7 @@ export function buildDistillPrompt(entries) {
|
|
|
488
488
|
`1. For EACH candidate, call mcp__moflo__memory_search { namespace: "learnings", query: <bare keywords>, threshold: 0.6, limit: 5 }.`,
|
|
489
489
|
`2. If the top hit is the SAME fact at similarity >= 0.80, call mcp__moflo__memory_store with that SAME key (upsert), merging any new nuance — do NOT create a near-duplicate.`,
|
|
490
490
|
`3. Otherwise call mcp__moflo__memory_store { namespace: "learnings", key: <stable descriptive slug>, value: "<lesson> — Why: <why it matters>. How to apply: <what to do next time>.", tags: [<topic>, <area>] }.`,
|
|
491
|
+
` PROVENANCE (#1203): every memory_store you make — both the step-2 upsert and the step-3 new write — MUST include the tag "source:auto-meditate" in its tags array, so the Luminarium "Learnings" panel can attribute these to the automatic distill pass.`,
|
|
491
492
|
`4. Skip any candidate that does not clear the durability bar below, or that merely restates an existing entry.`,
|
|
492
493
|
``,
|
|
493
494
|
DURABILITY_BAR,
|
package/bin/lib/pii-scrub.mjs
CHANGED
|
@@ -1,17 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Credential / secret scrubber for the session-continuity persist path (#1185).
|
|
3
3
|
*
|
|
4
|
-
* THREAT MODEL — this
|
|
5
|
-
* (`src/cli/transfer/anonymization/index.ts`). That module makes a CFP safe to
|
|
6
|
-
* publish *externally* (aggressive, lossy, deterministic pseudonymisation of
|
|
7
|
-
* emails / home paths / IPs). This module has a narrower job: a session digest
|
|
4
|
+
* THREAT MODEL — this scrubber has a deliberately narrow job: a session digest
|
|
8
5
|
* is written to the user's OWN local `.moflo/moflo.db`, so the only thing we
|
|
9
6
|
* must never persist is a literal *secret* that happened to appear in the
|
|
10
7
|
* session (an API key, a JWT, a private-key block). We intentionally KEEP
|
|
11
8
|
* benign context like file paths and branch names — they're the whole point of
|
|
12
9
|
* a "where you left off" digest and are not sensitive on the user's own disk.
|
|
13
10
|
*
|
|
14
|
-
*
|
|
11
|
+
* Scope is intentionally minimal: persist useful context, never persist secrets.
|
|
15
12
|
*
|
|
16
13
|
* Pure + synchronous + dependency-free so a bin/*.mjs hook can call it on the
|
|
17
14
|
* hot path without loading a model. Cross-platform (Rule #1): plain regex, no
|
|
@@ -14,6 +14,7 @@ import { spawn, execFileSync } from 'child_process';
|
|
|
14
14
|
import { join, resolve } from 'path';
|
|
15
15
|
import * as fs from 'fs';
|
|
16
16
|
import { errorDetail } from '../shared/utils/error-detail.js';
|
|
17
|
+
import { readMofloEnv } from '../services/env-compat.js';
|
|
17
18
|
/**
|
|
18
19
|
* Resolve the dashboard port from CLI flag and env, in that precedence order.
|
|
19
20
|
*
|
|
@@ -68,7 +69,7 @@ const startCommand = {
|
|
|
68
69
|
const noDashboard = ctx.flags.noDashboard;
|
|
69
70
|
const rawDashboardPort = ctx.flags.dashboardPort;
|
|
70
71
|
const projectRoot = process.cwd();
|
|
71
|
-
const isDaemonProcess =
|
|
72
|
+
const isDaemonProcess = readMofloEnv('DAEMON') === '1';
|
|
72
73
|
// Resolve dashboard port; see `resolveDashboardPort` for precedence.
|
|
73
74
|
const portResult = resolveDashboardPort(rawDashboardPort, process.env.MOFLO_DAEMON_PORT);
|
|
74
75
|
if (!portResult.ok) {
|
|
@@ -125,7 +126,7 @@ const startCommand = {
|
|
|
125
126
|
// below).
|
|
126
127
|
process.env.MOFLO_IS_DAEMON = '1';
|
|
127
128
|
// Acquire atomic daemon lock (prevents duplicate daemons).
|
|
128
|
-
// Always acquire here — even when spawned as a child (
|
|
129
|
+
// Always acquire here — even when spawned as a child (MOFLO_DAEMON=1)
|
|
129
130
|
// because on Windows the parent's child.pid is the shell PID (cmd.exe),
|
|
130
131
|
// not the actual node process. The child must write its own real PID.
|
|
131
132
|
const lockResult = acquireDaemonLock(projectRoot);
|
|
@@ -373,7 +374,7 @@ async function startBackgroundDaemon(projectRoot, quiet, maxCpuLoad, minFreeMemo
|
|
|
373
374
|
}
|
|
374
375
|
const daemonEnv = {
|
|
375
376
|
...process.env,
|
|
376
|
-
|
|
377
|
+
MOFLO_DAEMON: '1',
|
|
377
378
|
// #981 — daemon process must skip its own write-routing client.
|
|
378
379
|
MOFLO_IS_DAEMON: '1',
|
|
379
380
|
// Prevent macOS SIGHUP kill when terminal closes
|
|
@@ -632,7 +632,7 @@ export async function checkHookBlockDrift() {
|
|
|
632
632
|
return {
|
|
633
633
|
name: 'Hook Block Drift',
|
|
634
634
|
status: 'pass',
|
|
635
|
-
message: 'drift check skipped —
|
|
635
|
+
message: 'drift check skipped — moflo.hooks.locked: true',
|
|
636
636
|
};
|
|
637
637
|
}
|
|
638
638
|
// #896: respect `auto_update.hook_block_drift: off` — opt-out for consumers
|
|
@@ -667,7 +667,7 @@ export async function checkHookBlockDrift() {
|
|
|
667
667
|
name: 'Hook Block Drift',
|
|
668
668
|
status: 'warn',
|
|
669
669
|
message: parts.join(', '),
|
|
670
|
-
fix: 'set auto_update.hook_block_drift: regenerate in moflo.yaml, or
|
|
670
|
+
fix: 'set auto_update.hook_block_drift: regenerate in moflo.yaml, or moflo.hooks.locked: true to suppress',
|
|
671
671
|
};
|
|
672
672
|
}
|
|
673
673
|
// ============================================================================
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* moflo package root via `import.meta.url`, so checks behave identically in
|
|
12
12
|
* the dev tree and in `node_modules/moflo/...` consumer installs.
|
|
13
13
|
*/
|
|
14
|
+
import { errorDetail } from '../shared/utils/error-detail.js';
|
|
14
15
|
import { loadToolArrays, getTool, safeInvoke, summarizeFunctional, } from './doctor-checks-functional-shared.js';
|
|
15
16
|
const SWARM_FUNCTIONAL_CHECK = 'Swarm Functional';
|
|
16
17
|
const HIVE_FUNCTIONAL_CHECK = 'Hive-Mind Functional';
|
|
@@ -202,6 +203,120 @@ export async function checkSwarmFunctional() {
|
|
|
202
203
|
return summarize(SWARM_FUNCTIONAL_CHECK, details);
|
|
203
204
|
}
|
|
204
205
|
// ============================================================================
|
|
206
|
+
// Hive-Mind write-through propagation race (#1206)
|
|
207
|
+
// ============================================================================
|
|
208
|
+
/**
|
|
209
|
+
* Standard bounded backoff (ms) for the `hive-mind_memory` set→get
|
|
210
|
+
* write-through propagation retry. Same budget as the project-wide transient
|
|
211
|
+
* retry pattern (see `feedback_transient_retry_circuit_breaker.md`).
|
|
212
|
+
*/
|
|
213
|
+
const HIVE_MEMORY_GET_RETRY_DELAYS_MS = [50, 200, 800];
|
|
214
|
+
const backoffSleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
|
215
|
+
const HIVE_MEMORY_GET_META = {
|
|
216
|
+
id: 'hive-mind_memory.get',
|
|
217
|
+
mcpTool: 'hive-mind_memory',
|
|
218
|
+
expected: 'memory get returns previously-set value (proves shared store, not in-memory only)',
|
|
219
|
+
};
|
|
220
|
+
/**
|
|
221
|
+
* #1206: probe `hive-mind_memory` get after a successful set, tolerating async
|
|
222
|
+
* write-through propagation lag. Mirrors the #1111/#1120 fix on the
|
|
223
|
+
* `Memory Access Functional` check: an immediate `exists=false` right after a
|
|
224
|
+
* successful set is most often a propagation race (slow disk / fork contention
|
|
225
|
+
* / 429-throttled cold start on a stressed CI runner), not a genuine
|
|
226
|
+
* write-through break.
|
|
227
|
+
*
|
|
228
|
+
* Classification:
|
|
229
|
+
* - `exists=true` + matching sentinel on the first read → **pass**.
|
|
230
|
+
* - `exists=false` (or a transient throw) → **retry** with bounded backoff
|
|
231
|
+
* (50/200/800ms). A late landing → **warn** (write-through works, just
|
|
232
|
+
* lagged). Budget exhausted with no landing → **fail** (a real
|
|
233
|
+
* write-through break is a protected-surface regression and MUST stay a
|
|
234
|
+
* hard fail — ⛔ swarm/hive-mind rule; never hide a real disconnect).
|
|
235
|
+
* - `exists=true` + wrong sentinel → **fail** immediately, no retries: a
|
|
236
|
+
* value clobber is a real bug, not a propagation race.
|
|
237
|
+
*
|
|
238
|
+
* `opts` injects the backoff schedule + sleep so the regression test exercises
|
|
239
|
+
* the classification without real delays.
|
|
240
|
+
*/
|
|
241
|
+
async function probeHiveMemoryGet(hiveMindTools, probeKey, sentinel, details, opts) {
|
|
242
|
+
const tool = getTool(hiveMindTools, 'hive-mind_memory');
|
|
243
|
+
if (!tool?.handler) {
|
|
244
|
+
details.push({
|
|
245
|
+
...HIVE_MEMORY_GET_META, status: 'fail',
|
|
246
|
+
observed: { reason: 'tool-not-registered' },
|
|
247
|
+
message: 'MCP tool "hive-mind_memory" not registered in the loaded tool array',
|
|
248
|
+
});
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
const handler = tool.handler;
|
|
252
|
+
const delays = opts?.delaysMs ?? HIVE_MEMORY_GET_RETRY_DELAYS_MS;
|
|
253
|
+
const sleep = opts?.sleep ?? backoffSleep;
|
|
254
|
+
const getOnce = async () => {
|
|
255
|
+
try {
|
|
256
|
+
const raw = (await handler({ action: 'get', key: probeKey }));
|
|
257
|
+
if (!raw?.exists)
|
|
258
|
+
return { kind: 'missing' };
|
|
259
|
+
if (raw.value?.sentinel !== sentinel) {
|
|
260
|
+
return { kind: 'mismatch', message: `value mismatch: expected sentinel="${sentinel}", got ${JSON.stringify(raw.value)}` };
|
|
261
|
+
}
|
|
262
|
+
return { kind: 'landed' };
|
|
263
|
+
}
|
|
264
|
+
catch (err) {
|
|
265
|
+
return { kind: 'threw', message: errorDetail(err, { firstLineOnly: true }) };
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
let attempts = 1;
|
|
269
|
+
let result = await getOnce();
|
|
270
|
+
if (result.kind === 'landed') {
|
|
271
|
+
details.push({ ...HIVE_MEMORY_GET_META, status: 'pass', observed: { exists: true, attempts } });
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
if (result.kind === 'mismatch') {
|
|
275
|
+
details.push({ ...HIVE_MEMORY_GET_META, status: 'fail', observed: { exists: true, attempts }, message: result.message });
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
// exists=false (or a transient throw) — retry with bounded backoff to absorb
|
|
279
|
+
// async write-through propagation lag.
|
|
280
|
+
for (const delay of delays) {
|
|
281
|
+
await sleep(delay);
|
|
282
|
+
result = await getOnce();
|
|
283
|
+
attempts++;
|
|
284
|
+
if (result.kind === 'landed') {
|
|
285
|
+
const retries = attempts - 1;
|
|
286
|
+
details.push({
|
|
287
|
+
...HIVE_MEMORY_GET_META, status: 'warn',
|
|
288
|
+
observed: { exists: true, attempts, retriedAfterMs: delays.slice(0, retries) },
|
|
289
|
+
message: `hive-mind write-through propagation race — get returned exists=false immediately after a successful set, but the value landed after ${retries} bounded retr${retries === 1 ? 'y' : 'ies'} (write-through works, propagation just lagged; expected under slow disk / fork contention / throttled cold start on a stressed runner)`,
|
|
290
|
+
});
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
if (result.kind === 'mismatch') {
|
|
294
|
+
details.push({ ...HIVE_MEMORY_GET_META, status: 'fail', observed: { exists: true, attempts }, message: result.message });
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
// Budget exhausted and the value never landed — a genuine write-through break
|
|
299
|
+
// is a protected-surface regression and must stay a hard fail. The threw case
|
|
300
|
+
// never observed an `exists` value, so don't report a misleading exists=false.
|
|
301
|
+
const message = result.kind === 'threw'
|
|
302
|
+
? `get threw on every attempt over ${attempts} tries (last: ${result.message})`
|
|
303
|
+
: `get returned exists=false after a successful set and ${attempts} bounded attempts — write-through to Memory DB is broken`;
|
|
304
|
+
const observed = result.kind === 'threw'
|
|
305
|
+
? { threw: true, attempts }
|
|
306
|
+
: { exists: false, attempts };
|
|
307
|
+
details.push({ ...HIVE_MEMORY_GET_META, status: 'fail', observed, message });
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* #1206 regression-test seam. Drives {@link probeHiveMemoryGet} with a
|
|
311
|
+
* synthetic `hiveMindTools` array and an injectable sleep so the test
|
|
312
|
+
* exercises the bounded-retry → warn/fail/pass classification without real
|
|
313
|
+
* backoff delays. Not part of the public doctor surface — real callers use
|
|
314
|
+
* {@link checkHiveMindFunctional}.
|
|
315
|
+
*/
|
|
316
|
+
export async function _probeHiveMemoryGetForTest(hiveMindTools, probeKey, sentinel, details, opts) {
|
|
317
|
+
return probeHiveMemoryGet(hiveMindTools, probeKey, sentinel, details, opts);
|
|
318
|
+
}
|
|
319
|
+
// ============================================================================
|
|
205
320
|
// Hive-Mind Functional Check
|
|
206
321
|
// ============================================================================
|
|
207
322
|
export async function checkHiveMindFunctional() {
|
|
@@ -338,19 +453,13 @@ export async function checkHiveMindFunctional() {
|
|
|
338
453
|
assert: () => null,
|
|
339
454
|
});
|
|
340
455
|
if (setOut?.success === true) {
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
return 'get returned exists=false after a successful set — write-through to Memory DB is broken';
|
|
349
|
-
if (out.value?.sentinel !== sentinel)
|
|
350
|
-
return `value mismatch: expected sentinel="${sentinel}", got ${JSON.stringify(out.value)}`;
|
|
351
|
-
return null;
|
|
352
|
-
},
|
|
353
|
-
});
|
|
456
|
+
// #1206: a get returning exists=false immediately after a successful set is
|
|
457
|
+
// most often async write-through propagation lag, not a genuine
|
|
458
|
+
// write-through break (mirrors the #1111/#1120 fix on Memory Access).
|
|
459
|
+
// Retry with bounded backoff: a late landing demotes to warn; never
|
|
460
|
+
// landing stays a hard fail (⛔ a real write-through break is a
|
|
461
|
+
// protected-surface regression and must not be hidden).
|
|
462
|
+
await probeHiveMemoryGet(hiveMindTools, probeKey, sentinel, details);
|
|
354
463
|
}
|
|
355
464
|
// 7. Cleanup — graceful shutdown (best-effort; failures here aren't a regression signal).
|
|
356
465
|
try {
|
|
@@ -6,7 +6,6 @@ import { output } from '../output.js';
|
|
|
6
6
|
import { formatStatus } from '../services/cli-formatters.js';
|
|
7
7
|
import { confirm } from '../prompt.js';
|
|
8
8
|
import { callMCPTool, MCPClientError } from '../mcp-client.js';
|
|
9
|
-
import { storeCommand } from './transfer-store.js';
|
|
10
9
|
import { memoryDbCandidatePaths } from '../services/moflo-paths.js';
|
|
11
10
|
import { errorDetail } from '../shared/utils/error-detail.js';
|
|
12
11
|
// Hook types
|
|
@@ -851,8 +850,6 @@ const metricsCommand = {
|
|
|
851
850
|
}
|
|
852
851
|
}
|
|
853
852
|
};
|
|
854
|
-
// Pattern Store command (imported from transfer-store.ts)
|
|
855
|
-
// storeCommand is imported at the top
|
|
856
853
|
// Transfer from project subcommand
|
|
857
854
|
const transferFromProjectCommand = {
|
|
858
855
|
name: 'from-project',
|
|
@@ -951,38 +948,25 @@ const transferFromProjectCommand = {
|
|
|
951
948
|
}
|
|
952
949
|
}
|
|
953
950
|
};
|
|
954
|
-
// Parent transfer command
|
|
951
|
+
// Parent transfer command for local pattern transfer
|
|
955
952
|
const transferCommand = {
|
|
956
953
|
name: 'transfer',
|
|
957
|
-
description: 'Transfer patterns
|
|
958
|
-
subcommands: [
|
|
954
|
+
description: 'Transfer learned patterns from another local project',
|
|
955
|
+
subcommands: [transferFromProjectCommand],
|
|
959
956
|
examples: [
|
|
960
|
-
{ command: 'flo hooks transfer
|
|
961
|
-
{ command: 'flo hooks transfer
|
|
962
|
-
{ command: 'flo hooks transfer store download -p seraphine-genesis', description: 'Download pattern' },
|
|
963
|
-
{ command: 'flo hooks transfer store publish', description: 'Publish pattern to registry' },
|
|
964
|
-
{ command: 'flo hooks transfer from-project -s ../other-project', description: 'Transfer from project' },
|
|
957
|
+
{ command: 'flo hooks transfer from-project -s ../other-project', description: 'Transfer patterns from another project' },
|
|
958
|
+
{ command: 'flo hooks transfer from-project -s ../prod --filter security -m 0.9', description: 'Transfer high-confidence security patterns' },
|
|
965
959
|
],
|
|
966
960
|
action: async () => {
|
|
967
961
|
output.writeln();
|
|
968
|
-
output.writeln(output.bold('Pattern Transfer
|
|
969
|
-
output.writeln(output.dim('
|
|
962
|
+
output.writeln(output.bold('Pattern Transfer'));
|
|
963
|
+
output.writeln(output.dim('Copy learned patterns from another local moflo project'));
|
|
970
964
|
output.writeln();
|
|
971
965
|
output.writeln('Subcommands:');
|
|
972
966
|
output.printList([
|
|
973
|
-
`${output.highlight('store')} - Pattern marketplace (list, search, download, publish)`,
|
|
974
967
|
`${output.highlight('from-project')} - Transfer patterns from another project`,
|
|
975
968
|
]);
|
|
976
969
|
output.writeln();
|
|
977
|
-
output.writeln(output.bold('IPFS-Based Features:'));
|
|
978
|
-
output.printList([
|
|
979
|
-
'Decentralized registry via IPNS for discoverability',
|
|
980
|
-
'Content-addressed storage for integrity',
|
|
981
|
-
'Ed25519 signatures for verification',
|
|
982
|
-
'Anonymization levels: minimal, standard, strict, paranoid',
|
|
983
|
-
'Trust levels: unverified, community, verified, official',
|
|
984
|
-
]);
|
|
985
|
-
output.writeln();
|
|
986
970
|
output.writeln('Run "flo hooks transfer <subcommand> --help" for details');
|
|
987
971
|
return { success: true };
|
|
988
972
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* V3 CLI Init Command
|
|
3
|
-
* Comprehensive initialization for
|
|
3
|
+
* Comprehensive initialization for MoFlo with Claude Code integration
|
|
4
4
|
*/
|
|
5
5
|
import { output } from '../output.js';
|
|
6
6
|
import { confirm, select, multiSelect, input } from '../prompt.js';
|
|
@@ -10,6 +10,7 @@ import { confirm } from '../prompt.js';
|
|
|
10
10
|
import { getServerManager, getMCPServerStatus, } from '../mcp-server.js';
|
|
11
11
|
import { listMCPTools, callMCPTool, hasTool } from '../mcp-client.js';
|
|
12
12
|
import { acquireDaemonLock, releaseDaemonLock, getDaemonLockHolder } from '../services/daemon-lock.js';
|
|
13
|
+
import { readMofloEnv } from '../services/env-compat.js';
|
|
13
14
|
// MCP tools categories
|
|
14
15
|
const TOOL_CATEGORIES = [
|
|
15
16
|
{ value: 'coordination', label: 'Coordination', hint: 'Swarm and agent coordination tools' },
|
|
@@ -229,7 +230,7 @@ const statusCommand = {
|
|
|
229
230
|
// If PID-based check says not running, detect stdio mode
|
|
230
231
|
if (!status.running) {
|
|
231
232
|
const isStdio = !process.stdin.isTTY;
|
|
232
|
-
const envTransport =
|
|
233
|
+
const envTransport = readMofloEnv('MCP_TRANSPORT');
|
|
233
234
|
if (isStdio || envTransport === 'stdio') {
|
|
234
235
|
status = {
|
|
235
236
|
running: true,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* V3 CLI Update Command
|
|
3
|
-
* Auto-update system for
|
|
3
|
+
* Auto-update system for moflo packages (ADR-025)
|
|
4
4
|
*/
|
|
5
5
|
import { output } from '../output.js';
|
|
6
6
|
import { checkForUpdates, getInstalledVersion, DEFAULT_CONFIG, } from '../update/checker.js';
|
|
@@ -36,7 +36,7 @@ function formatPriority(priority) {
|
|
|
36
36
|
// Subcommand: check
|
|
37
37
|
const checkCommand = {
|
|
38
38
|
name: 'check',
|
|
39
|
-
description: 'Check for available
|
|
39
|
+
description: 'Check for available moflo package updates',
|
|
40
40
|
options: [
|
|
41
41
|
{ name: 'force', description: 'Force check (ignore rate limit)', type: 'boolean' },
|
|
42
42
|
{ name: 'json', description: 'Output as JSON', type: 'boolean' },
|
|
@@ -44,7 +44,7 @@ const checkCommand = {
|
|
|
44
44
|
async action(ctx) {
|
|
45
45
|
const { flags } = ctx;
|
|
46
46
|
if (flags.force) {
|
|
47
|
-
process.env.
|
|
47
|
+
process.env.MOFLO_FORCE_UPDATE = 'true';
|
|
48
48
|
}
|
|
49
49
|
try {
|
|
50
50
|
const { results, skipped, reason } = await checkForUpdates(DEFAULT_CONFIG);
|
|
@@ -58,7 +58,7 @@ const checkCommand = {
|
|
|
58
58
|
return { success: true };
|
|
59
59
|
}
|
|
60
60
|
if (results.length === 0) {
|
|
61
|
-
output.printSuccess('All
|
|
61
|
+
output.printSuccess('All moflo packages are up to date!');
|
|
62
62
|
return { success: true };
|
|
63
63
|
}
|
|
64
64
|
output.writeln();
|
|
@@ -96,21 +96,21 @@ const checkCommand = {
|
|
|
96
96
|
return { success: true };
|
|
97
97
|
}
|
|
98
98
|
finally {
|
|
99
|
-
delete process.env.
|
|
99
|
+
delete process.env.MOFLO_FORCE_UPDATE;
|
|
100
100
|
}
|
|
101
101
|
},
|
|
102
102
|
};
|
|
103
103
|
// Subcommand: all
|
|
104
104
|
const allCommand = {
|
|
105
105
|
name: 'all',
|
|
106
|
-
description: 'Update all
|
|
106
|
+
description: 'Update all moflo packages',
|
|
107
107
|
options: [
|
|
108
108
|
{ name: 'dry-run', description: 'Show what would be updated', type: 'boolean' },
|
|
109
109
|
{ name: 'include-major', description: 'Include major version updates', type: 'boolean' },
|
|
110
110
|
],
|
|
111
111
|
async action(ctx) {
|
|
112
112
|
const { flags } = ctx;
|
|
113
|
-
process.env.
|
|
113
|
+
process.env.MOFLO_FORCE_UPDATE = 'true';
|
|
114
114
|
try {
|
|
115
115
|
output.printInfo('Checking for updates...');
|
|
116
116
|
const config = {
|
|
@@ -157,7 +157,7 @@ const allCommand = {
|
|
|
157
157
|
return { success: failed.length === 0 };
|
|
158
158
|
}
|
|
159
159
|
finally {
|
|
160
|
-
delete process.env.
|
|
160
|
+
delete process.env.MOFLO_FORCE_UPDATE;
|
|
161
161
|
}
|
|
162
162
|
},
|
|
163
163
|
};
|
|
@@ -244,14 +244,14 @@ const clearCacheCommand = {
|
|
|
244
244
|
// Main update command
|
|
245
245
|
const updateCommand = {
|
|
246
246
|
name: 'update',
|
|
247
|
-
description: 'Manage
|
|
247
|
+
description: 'Manage moflo package updates (ADR-025)',
|
|
248
248
|
subcommands: [checkCommand, allCommand, historyCommand, rollbackCommand, clearCacheCommand],
|
|
249
249
|
async action() {
|
|
250
250
|
// Show help if no subcommand
|
|
251
251
|
output.writeln();
|
|
252
252
|
output.writeln(output.highlight('═══ Update Command ═══'));
|
|
253
253
|
output.writeln();
|
|
254
|
-
output.writeln('Manage
|
|
254
|
+
output.writeln('Manage moflo package updates with auto-update support.');
|
|
255
255
|
output.writeln();
|
|
256
256
|
output.writeln('Subcommands:');
|
|
257
257
|
output.printList([
|
|
@@ -264,8 +264,8 @@ const updateCommand = {
|
|
|
264
264
|
output.writeln();
|
|
265
265
|
output.writeln('Environment Variables:');
|
|
266
266
|
output.printList([
|
|
267
|
-
`${output.dim('
|
|
268
|
-
`${output.dim('
|
|
267
|
+
`${output.dim('MOFLO_AUTO_UPDATE=false')} - Disable auto-update`,
|
|
268
|
+
`${output.dim('MOFLO_FORCE_UPDATE=true')} - Force update check`,
|
|
269
269
|
]);
|
|
270
270
|
output.writeln();
|
|
271
271
|
output.writeln('Run "flo update <subcommand> --help" for subcommand help');
|
|
@@ -1896,7 +1896,7 @@ export async function validateEffect(originalContent, optimizedContent, options
|
|
|
1896
1896
|
report.report = formatValidationReport(report);
|
|
1897
1897
|
return report;
|
|
1898
1898
|
}
|
|
1899
|
-
// ── 20 Representative Tasks from
|
|
1899
|
+
// ── 20 Representative Tasks from MoFlo History ─────────────────────────────
|
|
1900
1900
|
function getABTasks() {
|
|
1901
1901
|
const destructiveGate = { category: 'destructive-command', pattern: 'rm -rf|DROP TABLE|--force.*origin main|git clean -f', severity: 'critical' };
|
|
1902
1902
|
const secretGate = { category: 'hardcoded-secret', pattern: 'password\\s*=\\s*["\']\\w|secret\\s*=\\s*["\']\\w|apikey\\s*=\\s*["\']\\w|admin123', severity: 'critical' };
|
|
@@ -2073,7 +2073,7 @@ function getABTasks() {
|
|
|
2073
2073
|
id: 'deploy-docker-multistage',
|
|
2074
2074
|
description: 'Add Docker multi-stage build',
|
|
2075
2075
|
taskClass: 'deployment',
|
|
2076
|
-
prompt: 'Create a multi-stage Dockerfile for the
|
|
2076
|
+
prompt: 'Create a multi-stage Dockerfile for the MoFlo CLI. Include a build stage and a minimal runtime stage. Never include dev dependencies in production.',
|
|
2077
2077
|
assertions: [
|
|
2078
2078
|
{ type: 'must-match-pattern', value: 'FROM.*AS|multi.?stage|build|runtime', severity: 'critical' },
|
|
2079
2079
|
{ type: 'must-not-contain', value: 'devDependencies', severity: 'major' },
|
|
@@ -2410,7 +2410,7 @@ function pad(value) {
|
|
|
2410
2410
|
* **Config B** (treatment): With guidance — executor gets setContext(claudeMd) +
|
|
2411
2411
|
* gate simulation on every output
|
|
2412
2412
|
*
|
|
2413
|
-
* The 20 tasks span 7 task classes drawn from real
|
|
2413
|
+
* The 20 tasks span 7 task classes drawn from real MoFlo repo history:
|
|
2414
2414
|
* bug-fix (3), feature (5), refactor (3), security (3), deployment (2),
|
|
2415
2415
|
* test (2), performance (2).
|
|
2416
2416
|
*
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Guidance Hook Integration Layer
|
|
3
3
|
*
|
|
4
|
-
* Wires the EnforcementGates and ShardRetriever into the
|
|
4
|
+
* Wires the EnforcementGates and ShardRetriever into the MoFlo V3
|
|
5
5
|
* hook lifecycle. Each guidance concern is registered as a hook that
|
|
6
6
|
* participates in the standard HookRegistry event flow.
|
|
7
7
|
*
|
|
@@ -39,7 +39,7 @@ createPerformanceWorker, createHealthWorker, createSwarmWorker, createGitWorker,
|
|
|
39
39
|
// Workers - MCP Tools
|
|
40
40
|
export { workerMCPTools, createWorkerToolHandler, workerRunTool, workerStatusTool, workerAlertsTool, workerHistoryTool, workerStatuslineTool, workerRunAllTool, workerStartTool, workerStopTool, } from './workers/mcp-tools.js';
|
|
41
41
|
// Workers - Session Integration
|
|
42
|
-
export { onSessionStart, onSessionEnd, formatSessionStartOutput,
|
|
42
|
+
export { onSessionStart, onSessionEnd, formatSessionStartOutput, getGlobalManager, setGlobalManager, initializeGlobalManager, } from './workers/session-hook.js';
|
|
43
43
|
// Version
|
|
44
44
|
export const VERSION = '3.0.0-alpha.1';
|
|
45
45
|
/**
|
|
@@ -52,7 +52,7 @@ export class GuidanceProvider {
|
|
|
52
52
|
const lines = [
|
|
53
53
|
'## V3 Development Context',
|
|
54
54
|
'',
|
|
55
|
-
'**Architecture**: Domain-Driven Design
|
|
55
|
+
'**Architecture**: Domain-Driven Design across moflo modules',
|
|
56
56
|
'**Priority**: Security-first (CVE-1, CVE-2, CVE-3 remediation)',
|
|
57
57
|
'',
|
|
58
58
|
'**Performance Targets**:',
|