While researching a cross-ecosystem architecture question, found that
witness-receipt-contract.md §9 and the ruflo-anchor-record-v1 schema
both still say "PROPOSED-EXTENSION... nothing above is decided" —
but the two consumers that placeholder was gating (ruvnet/rvm#35,
ruvnet/autogenous#10) already merged real implementations against
exactly this shape:
- ruvnet/rvm#37 (merged): verify and anchor ruflo ADR-322C receipts
into the witness chain
- ruvnet/autogenous#13 (merged): export promotion evidence in the
ruflo ADR-322C receipt shape
- ruvnet/ruflo#3066, the PIR tracking issue that scoped this work,
is itself closed with a status comment confirming the schema
(PR #3067) is canonical on main and both conformance audits cleared
No schema or behavior change — this is a documentation-accuracy fix.
ADR-322D formally promotes the record from PROPOSED-EXTENSION to
Accepted to match what's already shipped and consumed, and explicitly
scopes it against a different, still-unwired "RVM" relationship
(LatentMesh's own aspiration to have RVF/RVM gate its candidate
cognition) so the two aren't conflated in future reads.
Claude-Session: https://claude.ai/code/session_01G3Fkc9qcZ2AkwGPce83yTa
Adds a JSON report from a structural validation pass over the 108 agent
definition files under .claude/agents/. All 108 files have valid
frontmatter (name + description) and non-empty system-prompt bodies.
Also surfaces 11 agent names that are defined twice under different
paths (e.g. code-analyzer, backend-dev, pr-manager, python-specialist,
typescript-specialist, tdd-london-swarm, production-validator,
goal-planner, sublinear-goal-planner, database-specialist,
project-coordinator) - mostly a legacy flat file plus a newer
categorized copy. Not a parse failure, but worth a look since it's
ambiguous which definition wins when agents are loaded by name.
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: rUv <ruv@ruv.net>
annotation-review.md's readOnly/destructive/idempotent/openWorld classification
table only listed 12 of the 14 live MCP tools. team_board (added at 0.2.0) and
run_complete (added at 0.2.1) were never added to the table, even though the
doc claims "the server tests compare the live tool list with this
classification" — nothing actually enforced that until now.
Both tools' real annotations (read from src/server.mjs) fit the existing rows
exactly: team_board uses READ(...) like the other read-only tools, and
run_complete uses WRITE(..., true) like the other idempotent upserts.
scripts/smoke.mjs now asserts the doc's documented tool set matches the live
server tool set exactly, so this can't silently drift again. Verified by
reverting the doc fix alone and confirming smoke failed with the exact
"documents every live tool" check, then restoring it.
Co-authored-by: r <r@x>
ruflo init wrote memory.persistPath: .claude-flow/data into .claude-flow/config.yaml, but getMemoryRoot() only reads the JSON config candidates (claude-flow.config.json and .claude-flow/config.json) and otherwise uses .swarm. The YAML key was dead text: editing it moved nothing and could mislead users into thinking a new store location was active (#3193 part b).\n\nRemove the ignored key and point the YAML at the JSON key that actually controls the store. Add an init regression test that fails on the old YAML and asserts the generated file no longer advertises persistPath.
* fix(memory): distinguish recent dead locks from live writers
inspectAgentdbLock() labeled a dead holder as 'live' when the lock was younger than the 10s stale threshold. The same state then produced 'another process is writing this database' guidance, so callers could wait for a writer that did not exist or leave a recoverable lock in place indefinitely (#3979 item 5).\n\nAdd a distinct 'recent' LockState for dead-PID locks still inside the stale window, clamp clock-skewed ages to zero, and give withAgentdbLockRecovery a precise retry message. The guard still refuses to clear the lock until it is old enough; only the diagnosis changes.
* chore(memory): bump @claude-flow/memory to 3.0.9
The leaf-package-publish audit correctly requires a version bump whenever publishable @claude-flow/memory source changes. Bump the leaf and both CLI dependency pins, and update the pnpm importer specifier, matching the #4279/#4152 release pattern.
Move the external agentic-qe-fleet entry from v3.15.10 (e5814a2f) to
v3.15.11 (221d6de15c73bb385961e4e649d8fcc1f5dc2dea). The plugin folder
changed only its version and its MCP pin (agentic-qe@3.15.11). Updates
the README links, the external-plugins test fixture and ADR-484.
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The parser stores `--no-browser` as `browser: false`, and the declared
`no-browser` option defaults `noBrowser` to `false`, so
`noBrowser ?? ['no-browser']` was always false and the login used the
loopback browser flow unless the host looked headless. Treat
`browser: false` as the opt-out too.
The parser stores `--no-color` as `color: false`, and the declared
`no-color` option leaves `noColor` at its default of `false`. The
statusline action only read `no-color` and `noColor`, so
`hooks statusline --full --no-color` and `ruflo --no-color hooks
statusline` still printed ANSI escape codes. Also check
`color === false`, as index.ts and init.ts already do.
Standalone publish for #4385 (stop injecting retracted performance
claims in GuidanceProvider.generateSessionContext()) — @claude-flow/cli
does not depend on @claude-flow/hooks directly, but @claude-flow/guidance
and @claude-flow/plugins do (both via ^3.0.0, so no pin bump needed there).
Co-authored-by: r <r@x>
GuidanceProvider.generateSessionContext() still emitted the exact multipliers the May 2026 intelligence audit retracted: HNSW '150x-12,500x' and Flash Attention '2.49x-7.47x'. This string is the SessionStart additionalContext path, so every session received claims the repo's own docs had already corrected. The existing test even pinned '150x' (#4381 memory-scan finding).\n\nReplace them with honest status lines: HNSW points at the memory package's measured benchmarks instead of a universal multiplier, Flash Attention is labeled as pending an in-tree benchmark, and the memory reduction is labeled a target. Update the regression test to assert the retracted substrings never reappear.
* fix(daemon): read config_set's values envelope in config.json reader
Fixes#3192
config_set's default-scope handler writes {"values": {key: val}, ...},
but the daemon's readDaemonConfigFromFile() only ever checked
scopes.project or the file's flat root — never `values`, which is where
config_set actually puts default-scope writes. As a result,
config_set daemon.idleSecs 0 silently had no effect on the running
daemon, even though it "succeeded" and updated config.json.
Add raw?.values as a fallback between scopes.project and the flat-root
read, matching the fix the issue reporter verified against the pinned
upstream sha.
* fix(daemon): resolve each config key independently (scopes.project > values > root)
Addresses review feedback on #3239 (Copilot): picking one whole object
as "cfg" meant a key only present in the values envelope was still
missed whenever scopes.project existed at all, even for unrelated
keys, because cfg resolved to scopes.project and the per-key fallback
only ever dropped to raw (root), never to raw.values.
Resolve each daemon.* key independently through
scopes.project -> values -> root so a config.json with keys spread
across more than one shape (e.g. one project-scoped config_set call
and one default-scope call) still resolves correctly.
---------
Co-authored-by: SID-6921 <99672439+SID-6921@users.noreply.github.com>
2.0.0-alpha.5's own optionalDependencies claimed
@agntcy/slim-bindings-linux-arm64-musl@2.0.0-alpha.5, but that platform
package was never published upstream (confirmed via the live npm registry:
every 2.0.0-alpha.*/2.0.0/2.1.0 release has the same defect; the musl
package's own version history starts at 2.1.1-rc.2). npm tolerates an
unresolvable optional dependency and silently skips it, so this never
surfaced in CI, but strict resolvers (mise's aube backend) fail the whole
install.
2.1.1 is the earliest release where every platform's optional package
actually exists at that exact version (verified against the registry for
all 8 platforms), chosen over the current latest (3.3.8) to minimize
API-drift risk relative to the previously verified 2.0.0-alpha.5. Verified
live: the dynamic import resolves under plain Node against the real
installed 2.1.1 package, and the full agntcy-commands.test.ts suite (19
tests, including the real-resolution "configured: true" case) passes.
Also found and filed #4376: transport.ts/swarm-join.ts/publish.ts call
createSlimTransport/joinGroup/publishAgentRecord, none of which exist on the
real SDK at either version — a pre-existing gap this pin change does not
introduce or worsen.
Co-authored-by: r <r@x>
Bumps @claude-flow/cli, claude-flow, and ruflo to 3.59.14, carrying:
- fix(mcp): let memory tools target an explicit store (#4350)
- fix(cli): report every stored workflow in workflow_list total, not just the limited page (#4372)
Co-authored-by: r <r@x>
* fix(cost-tracker): match Claude Code project slug for all non-alphanumerics
The #1927 fix replaced only /, \\\\ and :, but Claude Code replaces every non-alphanumeric character in a project path with '-'. Paths containing '_' or '.' (for example C:\\Users\\john_doe\\my.project) therefore resolved to a directory that never exists, and the Stop hook silently persisted nothing (#4366).\n\nMove the encoder into a shared _encode-project-path.mjs, use it from track.mjs and the two test harnesses that were duplicating the old regex, and add unit coverage plus an end-to-end smoke case for an underscore/dot Windows path. The regression is visible in the unit test and in the 'looked under' diagnostic: the old code leaves 'my_project.v2' literal; the new code matches Claude Code's 'my-project-v2'.
* chore(witness): repoint #1927 marker to _encode-project-path.mjs (#4366)
#4366 moved encodeProjectPath out of track.mjs into a new shared
_encode-project-path.mjs, taking the #1927 marker comment with it in
different wording. Re-point all three OS manifests' #1927 entry at the
new file/marker text and regenerate so witness-marker-drift-smoke and
witness-verify-precondition-smoke pass again.
Co-authored-by: 马建彤 <majiantong666@qq.com>
Co-Authored-By: RuFlo <ruv@ruv.net>
* fix(witness): don't append a history snapshot from an unbuilt checkout
regen.mjs's --history flag records the current verification state as a
new history entry. My prior regen ran in a source-only worktree (no
pnpm build), so it recorded ~99 dist-dependent fixes as "missing" that
CI's built-tree verify never saw as missing -- history.mjs then read
that back as a mass regression against prior (post-build) snapshots.
The actual fix (#1927 marker repointed to _encode-project-path.mjs) only
needed the manifest's marker/file/sha256 corrected and re-signed, not a
new history entry. Revert history.jsonl to its pre-regen state in all
three OS directories; manifest.md.json is unchanged by this commit.
Co-Authored-By: RuFlo <ruv@ruv.net>
---------
Co-authored-by: 马建彤 <majiantong666@qq.com>
Co-authored-by: r <r@x>
The CLI accepts an explicit --path, and memory_stats/import/export already accept dbPath, but memory_store, memory_retrieve, memory_search, memory_delete and memory_list ignored an explicit store and always used the MCP default (the AgentDB sibling). An MCP caller that knew the project store could not select it, so reads and writes landed in a different backend (#3196).\n\nAdd an optional dbPath to those five tool schemas, skip default initialization when it is supplied, and thread it through storeEntry/getEntry/searchEntries/deleteEntry/listEntries (including SmartRetrieval's raw search and near-miss paths). No dbPath keeps the existing MCP default; this is deliberately not an implicit CLAUDE_FLOW_DB_PATH override because the native/encrypted CLI store and the AgentDB sibling are separate backends.\n\nAdd sql.js fixture tests with a populated default store so selecting the wrong store is observable. Baseline (source stashed) fails 4/4; the fix passes 4/4. Existing related memory suites pass 37/37.
Bumps @claude-flow/cli, claude-flow, and ruflo to 3.59.13, carrying:
- fix(claims): anchor MCP claims store to project cwd (#3210)
- fix(hooks): resolve globally-installed ruflo before falling back to npx (#3368) (#4368)
Co-authored-by: r <r@x>
* fix(hooks): resolve globally-installed ruflo before falling back to npx (#3368)
hook-handler.cjs's resolveCliBinForHook() only probed project-relative
locations, so on a machine with `npm install -g ruflo` every SessionStart
refresh (refresh-funnel, refresh-advisor) and the first-run spinner enable
fell back to `npx --prefer-offline @claude-flow/cli`: a second copy of the
CLI that follows the registry's latest instead of the pinned global, and
that cannot start offline on a cold npx cache.
After the existing candidates miss, look for a global install:
1. npm's global prefix, resolved in npm's own precedence (npm_config_prefix
env, user npmrc, npm's builtin npmrc, default) without spawning npm. It
matches `npm prefix -g` in 12 of 14 configurations compared; the other
two are an npmrc npm itself rejects and an undefined ${VAR}, which this
keeps literal so the relative value is refused rather than followed.
2. `ruflo` / `claude-flow` on PATH, which covers yarn and bun globals. Only
a symlink into a package that declares one of the three published names
counts, so a wrapper script or a Volta shim is not mistaken for an
install, and npm's Windows folder layout is only probed on win32 — on
POSIX it would follow a Windows install through WSL's appended PATH.
Each package root maps to its CLI the way ruflo/bin/ruflo.js does, stopping
at the enclosing node_modules so a stray ~/node_modules is never reached,
and every hit must still pass the existing dist/src/index.js guard.
Project-local installs keep priority, and npx stays the last resort. The
lookup runs once per hook process (session-restore asks up to three times):
about 0.6 ms for that call, 0.000 ms after, against 70-80 ms for a
`npm prefix -g` spawn.
Both committed copies stay byte-identical (hook-handler-artifact-parity).
helpers.manifest.json is deliberately untouched: the new hook-handler.cjs
hash needs the maintainer's re-sign, and helper-signing.test.ts reports the
drift until then.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KTedGmPKpEdArVBMMn3JaP
* fix(hooks): also look in ~/.npm-global after the configured prefix and PATH (#3368)
resolveGlobalCliBin() still returned null on a real install: ruflo lived in
~/.npm-global (npm's documented no-sudo prefix), but the setting pointing npm
there sat where the hook's node never reads it, and the only `ruflo` on PATH
was a wrapper script, which the PATH lookup skips by design. Every hook then
fell back to `npx @claude-flow/cli@latest`.
~/.npm-global is now a third, last-resort lookup, checked only after the
configured prefix and PATH both miss, so it can never override either. The
prefix loop moves into cliUnderPrefix() so both lookups share it.
Tests: an end-to-end case for that exact setup (no npmrc prefix, wrapper
script on PATH), a unit pinning the order (configured prefix wins), and a
unit that an unbuilt ~/.npm-global install is ignored. The first two fail
on the previous helper; 35/35 pass with the fix. Both committed copies stay
byte-identical.
Co-Authored-By: ruflo <ruv@ruv.net>
* test(hooks): expect the npx fallback to carry --prefix <helper root> (#3368)
Since e625b7104 (3.56.4) cliSpawnArgs() resolves the npx fallback from the
helper's own install root: `npx --prefer-offline --prefix <helperRoot>
@claude-flow/cli hooks refresh-*`. The three "still falls back to npx" cases
asserted the older argv without --prefix and failed after rebasing onto
v3.56.9. For the sandbox's project-level helper the root is the project, so
expectFellBackToNpx() now takes the sandbox and checks that exact prefix.
21/21 pass.
Co-Authored-By: ruflo <ruv@ruv.net>
* chore(helpers): re-sign manifest for the global-CLI hook fix (#3368)
hook-handler.cjs changed in the prior commits; re-sign
v3/@claude-flow/cli/.claude/helpers/helpers.manifest.json so the
shipped-helpers integrity gate (helper-signing.test.ts) matches the new
content hash.
Co-authored-by: Mr. Mak <218673441+HF-teamdev@users.noreply.github.com>
Co-Authored-By: RuFlo <ruv@ruv.net>
---------
Co-authored-by: Mr. Mak <218673441+HF-teamdev@users.noreply.github.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Mak Allen <mak@heroforge.ai>
Co-authored-by: r <r@x>
Ships #3341 (restores the `ruflo pair` command the bundled
pair-programming skill has depended on since v2 — every project with
that skill previously hit "Unknown command: pair" when activating any
mode).
Co-authored-by: r <r@x>
* feat(cli): restore the `pair` command the pair-programming skill depends on
The bundled pair-programming skill tells Claude Code to run
`claude-flow pair --start --mode tdd`, but the v3 CLI never shipped `pair`
(it was a v2 simple-command), so `ruflo pair` fails with "Unknown command"
and none of the skill's modes can be activated or switched.
Add a minimal, non-interactive `ruflo pair` that keeps session state in
`.claude-flow/sessions/pair/current.json`: start/status/switch/mode/end/
history, driver/navigator roles, switch-mode interval with a `switchDue`
signal. The v2 spellings (`pair --start`, `--end`, `--history`) still
work. The skill docs now describe how the agent reads `pair status --json`
each turn and act on it, and point at the new syntax.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* feat(cli): accept the pair mode as a bare word
`ruflo pair start tdd` silently started driver mode because only
--mode was read. A positional now wins over the option default.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
---------
Co-authored-by: Gilles Major <gilles@lizy.be>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: rUv <ruv@ruv.net>
`writeFileSync(victim, 'precious', { mode: 0o644 })` is still subject
to the process umask, so on a host with a restrictive umask (0077)
the file actually lands at 0600. The test then asserted the touch
script's symlink guard had left victim untouched at 0644 — true, but
victim was never at 0644 to begin with on such a host, so the
assertion failed independent of the guard's real behavior (verified
manually: the guard correctly refuses and leaves the target
unmodified).
Force the mode with chmodSync right after the write so the assertion
reflects the guard's actual behavior rather than this host's umask.
Co-authored-by: r <r@x>
Ships #4362 (appliance build/sign/update: read the parser's camelCase
flag keys instead of the raw kebab-case strings, same bug class as
#4330/#4349/#4353 in 3.59.10).
Co-authored-by: r <r@x>
The parser stores every long flag under its camelCase key and turns
`--no-foo` into `foo = false`, but the appliance commands read
kebab-case keys, so `--api-keys`, `--generate-keys`, `--key-dir`,
`--public-key` and `--no-backup` were silently ignored. With
`--public-key` ignored, `appliance update` never verified a patch
signature.
`appliance build` also passed `outputPath`/`apiKeysPath` where
RvfaBuilder reads `output`/`apiKeys`, so every build failed with
`The "paths[0]" argument must be of type string`. It now passes the
names the builder reads, leaves `models` unset when none were given so
the builder's per-profile default applies (an empty list gave hybrid no
local model), and prints `result.size` instead of the nonexistent
`totalSize`.
Same bug class as #2952.
Co-authored-by: rUv <ruv@ruv.net>
The parser stores long flags under camelCase keys and `--no-X` as
`X: false`, but `daemon start` read `ctx.flags['max-cpu-load']`,
`ctx.flags['min-free-memory']` and `ctx.flags['no-distill']`, which are
never set. All three flags were ignored, in the foreground daemon and in
the forked background child.
Read `maxCpuLoad` and `minFreeMemory`, and treat `distill: false` as the
distillation opt-out while still accepting the literal `no-distill` key.
Co-authored-by: rUv <ruv@ruv.net>
* fix(ruflo-adr): find ADRs on Windows and report POSIX paths (#2413)
findAdrs matched literal /docs/adr/ substrings, so join()'s backslashes on Windows silently produced 0 ADRs. Match either separator and report the file field relative to the scan root with forward slashes so bySource and stored records are stable across platforms.
* fix(ruflo-adr): spawn npm npx-cli.js on Windows (#2413)
spawnSync cannot launch npx.cmd without a shell, so the no-installed-CLI fallback returned ENOENT and every store failed on Windows. Resolve npm's npx-cli.js (npm_execpath, PATH shims, or Node's bundled npm) and run it with process.execPath so argv stays shell-safe.
---------
Co-authored-by: rUv <ruv@ruv.net>
The parser stores `--no-signup` as `signup: false`, and the declared
`no-signup` option gives `noSignup` its default of `false`. The
enrollment step only read `ctx.flags['no-signup']` and
`ctx.flags.noSignup`, so the flag never took effect: in an interactive
terminal `ruflo init --no-signup` still asked "Create a free Cognitum
account now?" and saved the answer as the enrollment decision and an
`account` consent receipt.
Also treat `signup: false` as the opt-out, as init already does for
`--no-global`, `--no-codex-detect` and `--no-skills-sh`.
Co-authored-by: rUv <ruv@ruv.net>
Bundles two merged fixes:
- #4357: removed an unused toml dependency carrying two unpatched
high-severity CVEs (GHSA-82x6-q7mm-w9cf, GHSA-v5mp-jgw5-2x6j).
@iarna/toml is the TOML parser actually used; toml had zero
require/import sites in the codebase.
- #4359: picked up @metaharness/darwin@0.12.1, which fixes the
contrastive development loop's timeoutMs restarting per callback
instead of enforcing one deadline across a round (metaharness#424).
Co-authored-by: r <r@x>
metaharness#424 (merged 9c5e8e61, published as @metaharness/darwin@0.12.1
in ruvnet/metaharness#425) fixed the contrastive development loop's
timeoutMs restarting per callback instead of enforcing one deadline
across a round. @claude-flow/cli's existing "~0.12.0" pin already
covers 0.12.1, so only the lockfile's resolved version needs updating
to actually pick it up — no package.json change needed.
Hand-edited the two affected pnpm-lock.yaml entries rather than
regenerating: a full `pnpm update` cascaded into unrelated major
transitive bumps (vite 7->8, zod 3->4) across the workspace. Verified
with `pnpm install --frozen-lockfile` and confirmed the resolved
package contains the fix.
Co-authored-by: r <r@x>
toml@3.0.0 was declared as a direct dependency in three manifests
(root package.json, @claude-flow/cli, @claude-flow/codex) but never
imported anywhere in the codebase — @iarna/toml is the TOML parser
actually used (e.g. @claude-flow/codex/src/dual-mode/orchestrator.ts).
Confirmed via a real consumer install (`npm install ruflo@3.59.8` in
a scratch project) that toml reaches end users through this unused
dependency, carrying two unpatched high-severity advisories with no
non-breaking fix available:
- GHSA-82x6-q7mm-w9cf (Uncontrolled Recursion)
- GHSA-v5mp-jgw5-2x6j (Prototype Pollution via __proto__ key-path
desynchronization)
Since the dependency is dead weight, removing it is strictly safer
than bumping to a breaking major version of a package nothing calls.
Co-authored-by: r <r@x>
Three sites hardcoded `base.startsWith('/')` / `root.startsWith('/')`
as their "is the project folder known" guard. On Windows `state.cwd`
is a drive path (`C:\Users\...`), which never starts with `/`, so
each one refused regardless of settings:
- adr.ts discover() — the ADRs page never found a project's folder
- event-export.ts resolveTarget() — Events/Timeline export
- wf-file.ts resolveExportPath() — the Workflows page's saved-view
write path, and (transitively) the AI terminal's transcript save
All three now use the existing isAbsolutePath() helper (#4175 already
fixed the same bug class in register.ts). Two follow-on fixes were
needed once the guard was fixed:
- resolveTarget()/resolveExportPath() both forced a POSIX-style
leading `/` onto the reconstructed path after splitting on `/`,
discarding a Windows drive or UNC prefix. Now the leading `/` is
only added back when the original absolute path was POSIX-style.
- wf-file.ts's own containment filter (`inside`) had the identical
startsWith('/') bug, independently excluding every Windows root.
- wf-convo-live.ts's saveTranscript() pre-joined state.cwd into the
string passed to resolveExportPath(), which refuses any backslash
in its input — so a Windows cwd tripped that guard even after the
above fixes. It now passes the relative file name only, matching
resolveTarget()'s cleaner two-argument contract.
Fixes#4356 (reported by @marioja, with exact file/line citations and
a correct root-cause diagnosis for the ADRs page; this also fixes the
two related sites the report flagged as worth checking).
Validated: new windows-drive-path-sites.spec.ts (7/7), the full
plugin unit suite (219 files / 3340 tests), and the ADRs + export-
adjacent UI harness suites (adrs, labs, missions, watch — 27/27).
Co-authored-by: r <r@x>
Bundles two merged fixes:
- #4351: memory_delete / purgeNamespace now scrub the tombstone payload
(content, embedding, tags, metadata) instead of leaving it recoverable,
and enable PRAGMA secure_delete on the sql.js and native delete paths.
- #4354: config_list's default-scope merge loop no longer short-circuits
the explicit scope's own precedence branch, so a value set only at a
non-default scope stops being silently dropped in the default view.
Co-authored-by: r <r@x>
memory delete is a soft delete so the UNIQUE(namespace,key) slot stays occupied and a later upsert can resurrect the row. Both write paths kept the payload on disk: the native bridge left content, embedding, tags and metadata intact, and the sql.js fallback only nulled embedding. A deleted value therefore remained readable by anyone opening memory.db/agentdb-memory.db directly (#3785 residual).\n\nScrub content, embedding, embedding_model, embedding_dimensions, tags and metadata in both the bridge SQL and the sql.js image path. content is NOT NULL in the schema, so use the empty string rather than SQL NULL; identity, namespace, status, provenance and timestamps remain for resurrection and audit. Enable PRAGMA secure_delete=ON before both soft-delete paths and namespace purges so SQLite overwrites the old cells instead of leaving the payload in free pages.\n\nAdd sql.js delete/purge regression tests that also scan the raw image, plus a native better-sqlite3 case that checks the columns and the image after a WAL truncate checkpoint. Baseline local cases fail on the raw markers; the fix passes, and related delete/purge/upsert/concurrency tests pass 25/26 (native case skipped locally).
Bundles PR #4343 (policy lock fails fast on path errors instead of
timing out). acquireLock() treated every openSync() error as lock
contention, so a permission error, missing parent directory, or a
directory/symlink/FIFO planted at the lock path waited the full 5s
lock budget on every MCP tool call before surfacing a misleading
policy-state-lock-timeout. The create now uses O_NOFOLLOW (where
supported) to close the create-through-a-symlink path; ELOOP and
any non-EEXIST errno surface immediately; an EEXIST path is lstat'd
and a non-regular target (directory/FIFO/symlink) fails immediately
rather than being treated as a reclaimable lock. Live/dead regular-
file lock handling is unchanged.
Validated before tagging:
- audit-umbrella-version-lockstep.mjs: ok (all three at 3.59.7, ruflo pins cli)
- audit-leaf-versions-published.mjs: ok (4/4 non-bundled leaves resolve on the registry)
- pnpm -r build: all 25 workspace packages build clean (one transient
tsc segfault on @claude-flow/cli, confirmed non-reproducing on retry)
- policy-lock-dead-owner.test.ts, memory-export-path-3196.test.ts,
distill-both-stores-3446.test.ts, hive-mind-init-persist-contract.test.ts,
memory-list-teardown-3226.test.ts, task-cancel.test.ts,
task-status-header.test.ts, tui-command.test.ts: 59 passed, 3 skipped
Co-authored-by: r <r@x>
acquireLock treated every openSync error as lock contention, so permission/path/IO failures and planted directories/symlinks waited the full LOCK_WAIT_MS and surfaced as policy-state-lock-timeout on every MCP call. It also used openSync('wx'), which can follow a dangling symlink at the lock path and create the target.
Create with O_EXCL|O_NOFOLLOW where supported, treat only EEXIST as contention, map ELOOP and non-regular lstat results to an explicit path-type error, and throw other codes immediately as policy-state-lock-error. Normal live/dead regular-lock behavior is unchanged. Extend the lock tests with directory and dangling-symlink cases and update the symlinked-FIFO case to assert an immediate path error with no target removal.
Bundles PR #4341 (memory export can target an explicit store,
#3196): memory export was the only primary memory subcommand
without --path, and memory_export had no dbPath input, so a
specific snapshot (agentdb-memory.db, an archive, a restored
backup) could not be exported from the CLI. An explicit dbPath
is now a true single-store read: it does not initialize or merge
the sibling default store. Default (no --path) behavior is
unchanged.
Validated before tagging:
- audit-umbrella-version-lockstep.mjs: ok (all three at 3.59.6, ruflo pins cli)
- audit-leaf-versions-published.mjs: ok (4/4 non-bundled leaves resolve on the registry)
- pnpm -r build: all 25 workspace packages build clean
- memory-export-path-3196.test.ts, distill-both-stores-3446.test.ts,
hive-mind-init-persist-contract.test.ts, memory-list-teardown-3226.test.ts,
task-cancel.test.ts, task-status-header.test.ts, tui-command.test.ts:
37 passed, 3 skipped
Co-authored-by: r <r@x>
memory export was the only primary memory subcommand without --path, and memory_export had no dbPath input. The handler always opened the default stores, so a specific snapshot (agentdb-memory.db, an archive, or a backup) could not be exported from the CLI; an explicit selection was silently impossible.
Add DB_PATH_OPTION to export, pass resolveDbPath() to memory_export, and teach the handler to treat an explicit dbPath as a single-store read: it skips default initialization/migration and does not merge the sibling. With no dbPath, the existing both-store merge is unchanged. Add CLI/schema and sql.js handler regression tests (explicit file only; missing file error).
Bundles PR #4338 (memory distillation now covers both project
stores, #3446): memory distill run and the daemon's consolidate
worker previously resolved only memory.db, which in an MCP-only
project often lacks the AgentDB target tables, so every tick
skipped while the actual corpus accumulated un-distilled in the
sibling agentdb-memory.db. Both stores are now resolved and
distilled (a per-store failure can't block the other); an explicit
--db still pins a single store.
Validated before tagging:
- audit-umbrella-version-lockstep.mjs: ok (all three at 3.59.5, ruflo pins cli)
- audit-leaf-versions-published.mjs: ok (4/4 non-bundled leaves resolve on the registry)
- pnpm -r build: all 25 workspace packages build clean
- distill-both-stores-3446.test.ts, hive-mind-init-persist-contract.test.ts,
memory-list-teardown-3226.test.ts, task-cancel.test.ts,
task-status-header.test.ts, tui-command.test.ts: 34 passed, 3 skipped
CI note: PR #4338's Test Suite (ubuntu-latest) needed 5 re-runs before
passing, each failure on a different file unrelated to the PR's diff
(mods-cli-bin.test.ts, swarm-status-id-and-stop.test.ts,
missions/store-replay.test.ts) — pre-existing CI flakiness, not a
regression from this change. Documented on issue #4307.
Co-authored-by: r <r@x>
memory distill run and the daemon's consolidate worker defaulted to .swarm/memory.db only. After the #2786 store split, MCP writes land in .swarm/agentdb-memory.db; in an MCP-only project memory.db has no AgentDB target tables, so the worker skipped every tick while the real corpus went un-distilled (#3446).
Resolve both stores from the same memory root (honoring CLAUDE_FLOW_MEMORY_PATH and CLAUDE_FLOW_DB_PATH), run the service against each, aggregate the reports without hiding a per-store skip, and expose per-store results in the CLI output and consolidation metrics. Explicit --db remains single-store. Add pure orchestration tests plus native CLI/daemon coverage for an agentdb-only project.
Bundles PR #4336 (hive-mind_init honors its advertised persistence
contract): declares maxAgents/memoryBackend/persist in the MCP schema,
validates all input before any filesystem write, skips saveHiveState()
and the bootstrap-secret write entirely when persist:false, and fixes
the legacy status fallback (byzantine -> raft, matching init's
documented default).
Validated before tagging:
- audit-umbrella-version-lockstep.mjs: ok (all three at 3.59.4, ruflo pins cli)
- audit-leaf-versions-published.mjs: ok (4/4 non-bundled leaves resolve on the registry)
- pnpm -r build: all 25 workspace packages build clean
- hive-mind-init-persist-contract.test.ts, memory-list-teardown-3226.test.ts,
task-cancel.test.ts, task-status-header.test.ts, tui-command.test.ts:
28 passed, 3 skipped
Co-authored-by: r <r@x>