mirror of
https://github.com/ruvnet/ruflo.git
synced 2026-11-03 10:37:24 +09:00
main
59
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
36a1575eaf |
fix(ruflo-core): say why a stale local CLI build failed and fall back to npx (0.2.13) (#4607)
mcp-launch picked v3/@claude-flow/cli inside a ruflo checkout because bin/cli.js and dist/src/index.js existed, then died on a bare ERR_MODULE_NOT_FOUND when dist/ predated src/. Claude Code hides stderr behind 'Skipping connection', so the cause was invisible. Catch the missing-module import failure, print the rebuild command and the run-outside-the-repo option, and fall back to npx @latest. RUFLO_MCP_CLI_OVERRIDE, RUFLO_MCP_SKIP_NPX=1 and other error types still exit 1. Co-authored-by: RuFlo <ruv@ruv.net> |
||
|
|
828a8d78d9 |
feat(plugins): cut per-turn footprint — user-invoked operational skills, description ceiling, footprint report (ADR-507) (#4588)
* feat(plugins): ADR-507 cut the per-turn plugin footprint - scripts/measure-plugin-footprint.mjs: per-plugin skills/commands/agents, listing characters and estimated tokens, from the tree or a git ref; before/after reports in v3/docs/validation/. - 27 operational skills get disable-model-invocation: true (cost-tracker sub-reports reached by /ruflo-cost, metaharness bench/learn/evolve/mint/ oia-audit, GAIA submission + comparison, init-project, ruflo-status, vector-setup, federation-init, agntcy-status). Each is listed in a "User-invoked skills" table in its plugin README; the choosing rule is in plugins/README.md. - 20 skill descriptions over 300 characters cut to one sentence plus "Use when". - audit-skill-frontmatter: 300-char ceiling on skill and command descriptions (measured unfolded) and a README check for every user-invoked skill. - skillTrim (§2.4) not built: the engine has no skill-listing hook. Listing with every plugin enabled: 47,743 -> 39,402 chars (about 11,936 -> 9,851 tokens per turn). Co-Authored-By: RuFlo <ruv@ruv.net> * docs(adr): ADR-507 status names PR #4588 Co-Authored-By: RuFlo <ruv@ruv.net> * docs(plugins): ADR-507 cost-analyst points users at /ruflo-cost for user-invoked reports The engine's refusal for a disable-model-invocation skill says not to replicate its workflow by other means, so the agent names the subcommand instead of running the report script. After-report now cites the pushed commit. Co-Authored-By: RuFlo <ruv@ruv.net> * fix(scripts): ADR-507 audit runs through symlinked paths; test the gate's wiring The import guard compared argv[1] with import.meta.url, which Node realpaths for the entry module. Run through a symlinked path, main() was skipped and the audit exited 0 having checked nothing. Both scripts now compare realpaths. audit-skill-frontmatter takes --plugins-dir so tests can run the real CLI on a fixture: a 304-char folded description, an unlisted user-invoked skill and a 311-char literal command description must exit 1 with those findings, and adding the README row must clear only the user-invoked one. The repo-wide test now also asserts skillsScanned > 100. Disabling any of the three checks, or restoring either old guard, fails a test. Co-Authored-By: RuFlo <ruv@ruv.net> * chore(plugins): version bumps for ADR-507 footprint changes Co-Authored-By: RuFlo <ruv@ruv.net> --------- Co-authored-by: r <r@x> |
||
|
|
558f194506 |
fix(witness): support pinned signing keys and label legacy signatures (#4326)
* fix(witness): support pinned signing keys and label legacy signatures The default witness seed is derived from the public git commit, so anyone can re-sign a modified manifest; verify.mjs still called the result cryptographically valid. Add RUFLO_WITNESS_SEED for external-key signing and RUFLO_WITNESS_PUBLIC_KEY for pinned verification (fail closed on external-key manifests without a pin), tag legacy manifests as self-attested, and stop describing the default as authorship proof. * fix(witness): make --public-key win over the env pin The env-var precedence audit requires a CLI flag for RUFLO_WITNESS_PUBLIC_KEY. Add --public-key, document that it takes precedence, and cover both precedence directions in the auth test. * fix(ci): exclude witness-auth test from vitest ratchet (uses node:test) plugins/ruflo-core/scripts/witness/__tests__/witness-auth.test.mjs imports from node:test, not vitest, matching the same miscategorization pattern as pod-tick.test.mjs and probe-mod-guards.test.mjs. vitest's glob picks it up but can't parse node:test's API, so the ratchet reports "No test suite found" even though `node --test` passes all 3 cases cleanly. Co-Authored-By: RuFlo <ruv@ruv.net> --------- Co-authored-by: r <r@x> Co-authored-by: RuFlo <ruv@ruv.net> |
||
|
|
7f5023144b | fix: distinguish lifecycle events in hook deduplication (#4212) | ||
|
|
0e9ccec084 |
Merge main into perf/mcp-memory-on-core; ruflo-core 0.2.11 (0.2.10 shipped)
Co-Authored-By: RuFlo <ruv@ruv.net> |
||
|
|
9f5f933791 |
Merge main into fix/core-session-paths; ruflo-core 0.2.10 (0.2.9 shipped)
Co-Authored-By: RuFlo <ruv@ruv.net> |
||
|
|
a9707998ca |
fix(core): keep the documented post-edit '-f <file> -s true' form literal so the #1862 witness marker holds
The failed-edit branch now passes -s false explicitly; the success path keeps the exact ['-f', String(file), '-s', 'true'] argv that witness marker #1862 attests. Co-Authored-By: RuFlo <ruv@ruv.net> |
||
|
|
658655c1d9 |
chore(ruflo-core): 0.2.10 (in-process MCP launcher), changelog with measured launcher numbers
Co-Authored-By: RuFlo <ruv@ruv.net> |
||
|
|
f022c98b87 | Merge commit 'refs/pull/3987/head' of https://github.com/ruvnet/ruflo into pr-3989 | ||
|
|
b98935f34f |
fix(core,cli): record failed tool outcomes, run hooks from the project root, keep telemetry off the tool path
- #4127: PostToolUseFailure registered for Bash and Write|Edit|MultiEdit and forwarded with -s false and the real exit code (builds on #4170 by @rudycelekli; payload shape verified against Claude Code 2.1.289) - #4176: the hook shim starts the CLI from CLAUDE_PROJECT_DIR; the CLI only adopts a proven config in a directory that carries ruflo init's markers, not a bare .claude/ that Claude Code created - #4128: the shim starts the CLI detached and returns at once (about 25 ms instead of 0.2-4 s per hook), bounded to 4 concurrent telemetry CLIs per project, Stop never dropped, RUFLO_HOOK_SYNC=1 for the old path - ruflo-core 0.2.9 Co-Authored-By: RuFlo <ruv@ruv.net> |
||
|
|
b9d36d55e2 |
chore: cli-core 3.7.3 (project-cwd guard) + pins, ruflo-core plugin 0.2.9; merge main (leaf publish pending: cli-core 3.7.3)
Co-Authored-By: RuFlo <ruv@ruv.net> |
||
|
|
cc784f1cb5 | Merge remote-tracking branch 'origin/main' into pr-3989 | ||
|
|
9b10fcc8ab | Merge remote-tracking branch 'origin/main' into pr-3987 | ||
|
|
49040a24a9 | fix(hooks): record failed tool outcomes in core plugin | ||
|
|
18ecd8ae7b |
fix(memory,hooks): keep the on-disk format on sql.js write-back (#4057); command-recording off switch, redaction and a cleanup that sees expired rows (#4056)
#4057: with encryption at rest on, `memory purge`'s sql.js fallback rewrote a plaintext native SQLite store as an RFE1 blob and still printed [OK]; the native bridge and every SQLite reader then rejected the file. Every whole-file read-modify-write (purge, delete, get access-count, store, schema migration, decay) now writes the image back in the format it read (plaintext -> plaintext, RFE1 -> RFE1) via writeDbImagePreservingFormat. applyTemporalDecay also read the raw bytes, so it failed on encrypted stores, and wrote plaintext: both fixed. #4056: `hooks post-command` stored every Bash command in full with no off switch. Add CLAUDE_FLOW_HOOKS_RECORD_COMMANDS=0 / CLAUDE_FLOW_HOOKS_ENABLED=false / hooks.recordCommands=false (nothing is stored when off), redact common secret shapes and cap stored text at 2000 chars (bounded patterns, linear on hostile input). `memory cleanup --expired-only` never saw expired rows because listEntries hides them (TTL is a read-time rule): listEntries gains includeExpired + expiresAt, and memory_cleanup uses it. ruflo-core 0.2.8: the Bash shim stands down when recording is off. Co-Authored-By: RuFlo <ruv@ruv.net> |
||
|
|
ac583cd342 |
fix(plugins): group B skills and agents use real CLI flags and MCP parameter names
Verified every CLI flag and MCP call example in ruflo-core, cost-tracker, daa, ddd, deepseek-harness, docs, federation and goals against the published CLI (3.56.5) and the tool schemas in source: - hooks post-task has no --train-neural (8 agents/skills); neural train takes --pattern, not --pattern-type; hooks worker dispatch takes --context, not --scope (docs scoping was silently ignored). - agentdb_* examples used non-existent parameters (semantic-route query -> input; causal-edge from/to/type -> sourceId/targetId/relation; hierarchical store/recall path/data/depth -> key/value/tier and query/topK; pattern-search limit -> topK; context-synthesize sources -> maxEntries); ddd skills used a CLI-style --parent/--child form that failed on required key/value. The researcher agent now follows edges with graph-query/graph-pathfinder. - hooks_worker-status has no worker filter; cost docs now filter the result. - dossier-collect/investigator fetch web content: add untrusted-content rules and AIDefence screening. deepseek skills state the data egress. - Stale smoke/tool/command counts and four dead relative links fixed. Patch-bumps each changed plugin with a CHANGELOG entry. Co-Authored-By: RuFlo <ruv@ruv.net> |
||
|
|
e625b7104a |
fix(helpers): resolve the ruflo CLI from the helper root, not the project
A home-level helper or a user-level plugin hook serves every opened project.
hook-handler.cjs and statusline.cjs executed a CLI found under the project
(<cwd>/node_modules/{@claude-flow/cli,ruflo}, <cwd>/v3/...), and every npx
fallback ran with the project as cwd, where npx prefers the project's own
install. The plugin shims also resolved `ruflo` through relative PATH
entries, which resolve against the cwd.
- hook-handler.cjs / statusline.cjs (package and repo copies): candidates
are built from the helper's install root; for a project-local helper that
is the project, so its behaviour is unchanged.
- npx fallbacks pass `--prefix <helper root>` (generated hook-handler and
ruflo-hook.cjs too) or, in the plugin shims, `--prefix <plugin data dir or
home>`. The spawned CLI still runs with the project as its cwd.
- ruflo-hook.cjs (all four copies): relative/empty PATH entries are skipped.
Test: helper-cli-resolution-trust.test.ts (11 cases, 10 fail on main);
hook-handler-artifact-parity follows the npx args into cliSpawnArgs().
The signed helpers changed, so the helpers manifest needs re-signing.
|
||
|
|
db0c10a819 |
perf(ruflo-core): run resolved local CLI in-process in mcp-launch
mcp-launch.cjs spawned `node <bin>/cli.js mcp start` as a child even when a built local/global CLI resolved, so every Claude Code session kept a second, idle node.exe alive just to forward stdio and signals. When a local CLI resolves, import it in-process with process.argv rewritten to the exact argv the child used to get. The npx fallback still spawns. RUFLO_MCP_SPAWN=1 restores the child-process behaviour. Measured (Windows 11, Node 24.21, @claude-flow/cli 3.56.3, median of 3, post-GC, after initialize + tools/list): processes 2 -> 1, working set 149.3 -> 87.6 MB, private 74.0 -> 52.2 MB, spawn->initialize 1320 -> 729 ms; tools/list sha256 unchanged (8a723dc0...). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
966d8a0645 |
fix(mcp): start the MCP server in the session project, fix win32 home guard
The ruflo-core MCP launcher spawned `@claude-flow/cli mcp start` with its own process.cwd() and never consulted the session's project directory, so MCP tools could read/write .claude-flow state (hive-mind, swarm, memory) in a different folder than CLI commands, producing split-brain state. - mcp-launch.cjs: resolve the project dir (CLAUDE_PROJECT_DIR, then CLAUDE_FLOW_CWD, then launcher cwd; home/root/missing dirs skipped), spawn the server with that cwd and CLAUDE_FLOW_CWD set, probe local CLI installs from it, and warn on stderr when there is no project. - cli-core getProjectCwd(): treat filesystem roots (incl. C:\) and the home dir (os.homedir(), $HOME, $USERPROFILE; case/trailing-sep insensitive on win32) as "no project". The old `=== process.env.HOME` guard was a no-op on Windows where HOME is usually unset. Tests: node:test end-to-end launcher cases + vitest spec for the guard. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
c335261199 |
feat(helpers): hand route/post-edit to the ruflo mod via RUFLO_MODS_OWNS (ADR-404)
hook-handler.cjs (both copies) returns before any work for route or post-edit when the ruflo mod, running in the same Claude Code process, has named them in RUFLO_MODS_OWNS. Guards (pre-bash) and session hooks never hand over, whatever the variable says. ruflo-core's ruflo-hook.cjs stands down for post-edit likewise, so an edit is recorded exactly once. hook-handler.cjs content changed: the helpers manifest must be re-signed at release (helper-signing.test.ts fails on the hash until then). Co-Authored-By: RuFlo <ruv@ruv.net> |
||
|
|
1d79068c56 |
fix(ci): repair CI regressions surfaced by the integrated community fixes
- plugins/ruflo-adr smoke step 20: #3432 correctly split the memory-db cwd into a more precise DB_ROOT (nearest .git/.swarm ancestor of ROOT), but the smoke check still only recognized the literal `cwd: ROOT` string. Accept `cwd: DB_ROOT` too. - plugins/ruflo-core smoke step 6: #3428 replaced the old "pinned to v3.6" CLI compatibility line with an accurate description of the new resolve-installed-before-npx-fallback contract. Updated the smoke check to verify that contract's env vars are documented instead of a version pin that no longer exists. - session-tools.ts: CodeQL flagged Math.random()-derived session IDs as insecure randomness in a security context, newly reachable through #3486's activate path. Switched both session ID generators to randomUUID(), matching the convention already used in mcp-server.ts and autopilot-state.ts. |
||
|
|
1f31f4bfac | Merge PR #3428: fix(mcp): resolve installed CLI before npx fallback | ||
|
|
982936e2bf | fix(mcp): resolve installed CLI before npx fallback | ||
|
|
0702af8158 | fix(witness): fail marker-preserving hash drift in strict mode | ||
|
|
cdc000fb41 |
fix(hooks): quote-aware split for RUFLO_HOOK_CLI_OVERRIDE (#3344)
override.split(' ') broke process.execPath into 'C:\Program' plus stray
tokens on a standard Windows Node install (C:\Program Files\nodejs\node.exe),
since the override string wasn't quote-aware. Found while verifying #3332's
Windows argv fix on a real Windows machine: 3 of 32 plugin-hooks-smoke cases
failed only on a stock Windows dev box, not on windows-latest CI (whose
hostedtoolcache Node path has no space).
RUFLO_HOOK_CLI_OVERRIDE is a test-only escape hatch, never set in
production, so this has no user-facing impact — it only unblocks the
test harness on the common case of a Program-Files Node install.
Adds splitCliOverride(), a minimal double-quote-aware tokenizer, and
quotes the two override strings test-hooks.mjs builds from
process.execPath.
|
||
|
|
c77d1b5062 |
fix(hooks): apply the Windows argv fix from #3322 to the other three shim copies (#3332)
#3322 escaped argv on the Windows shell:true path in plugins/ruflo-core/scripts/ruflo-hook.cjs. The same shim exists in three other places, which still pass hook-derived values (a Bash tool's `command`, a file path) to cmd.exe unescaped, and are broader than the copy that was fixed — a bare `process.platform === 'win32'` with no exemption for `node`: - .claude-plugin/scripts/ruflo-hook.cjs (published in the npm package) - plugin/scripts/ruflo-hook.cjs (byte-identical sibling) - generateRufloHookCjs() in helpers-generator.ts, which `ruflo init` writes to .claude/helpers/ruflo-hook.cjs All four now follow one pattern — resolve, then escape: - resolveCommandPath() walks PATH/PATHEXT with fs only. The previous probe was `execSync('where ' + cmd)`, which spawned a shell on every hook invocation. - resolveNpmShim() maps an npm .cmd shim to the .js entrypoint it would have run, read from the package's own `bin` field rather than a guessed filename, and required to resolve inside the package directory. Both npm layouts are handled (global prefix and node_modules/.bin), as is npx, whose command and package names differ. - invokeHook() spawns `node <entry>` with shell:false, so CreateProcess receives the argv array verbatim — no second cmd.exe tokenizer, and no %VAR% expansion, which quoting does not suppress and carets do not reliably escape. - escapeCmdArg() is unchanged from #3322 and remains as the fallback for the case where no entrypoint can be identified. Returning early there would silently drop the hook instead of surfacing the problem. escapeCmdArg, resolveCommandPath, resolveNpmShim and resolveInvocation are byte-identical across all four copies, and the suite asserts that for each of them — the divergence between copies is what let this persist after #3322. Verification. #3322 could only assert the string transform, with no Windows host available. Two changes address that: - resolveCommandPath/resolveInvocation take `platform` and `env` as arguments, so the Windows branch runs on any OS. The tests build a real npm layout in a temp dir, drive it with { platform: 'win32' }, and assert the payload reaches the recorded argv byte-for-byte while the sibling .cmd/.ps1 are resolved past, never executed. - the coverage is added to plugins/ruflo-core/scripts/test-hooks.mjs, whose "Plugin hooks smoke" job already runs on windows-latest. The existing cases there go through RUFLO_HOOK_CLI_OVERRIDE and so never reach the global-shim branch; these call invokeHook() directly, so the fallback is executed against a real cmd.exe. Tests: 11 vitest cases and 6 new harness cases, each mutation-checked — every one fails when the code it covers is reverted. test-hooks.mjs goes 19/24 to 25/30 against the recorder fixture; those 5 failures are pre-existing on main (the fixture does not echo argv) and pass in CI against the real built CLI. Windows result: the windows-latest leg ran green, 32/32, including the escaped fallback executed against a real cmd.exe. The %VAR% probe was included because carets are not a reliable escape for % and quoting does not suppress percent expansion, so expansion on the shim's second parse looked plausible; the runner measured otherwise and the value arrives literal, with no redirection performed. The probe stays as a regression guard and keeps reporting the observed value rather than asserting one. Out of scope: nine sites in plugins/ruflo-metaharness/scripts spawn with `shell: process.platform === 'win32'` and a dynamic argv element — _darwin.mjs:86/129 spread the caller's own ...argv, oia-audit.mjs:105 passes JSON.stringify(payload), and audit-list/audit-trend/similarity pass `--key <key>`. Same shape, different plugin, separate change. Two neighbours that look like the same problem and are not: the ten `shell:` flags across ruflo-cost-tracker are inert, because spawnNpxSync() discards the option and forces shell:false; and mcp-launch.cjs already prefers a resolved local bin with shell:false and only falls back to npx.cmd with constant args. |
||
|
|
1c594c7714 |
fix(witness): make WITNESS_ED25519_ROOT actually resolve @noble/ed25519 (#3200) (#3276)
loadEd25519() threw an error telling callers to set WITNESS_ED25519_ROOT when @noble/ed25519 couldn't be found, but the function never read process.env — the env var had no effect. Prepend it to the probe list so the advertised escape hatch actually works. Fixes #3200 Co-authored-by: rUv <ruv@ruv.net> |
||
|
|
8008bc3562 |
fix(federation): verified event identity must win over publisher-controlled content (#3322)
* fix(federation): verified event identity must win over publisher-controlled content fetchRecent/fetchManyOn/fetchChannel spread the parsed message content after the signature-verified id/pubkey/created_at, so a publisher could put those keys in their own content and overwrite their verified identity downstream (reduceClaims trusts .pubkey for claim release/handoff authorization). Reorder so verified fields always win. Also in x-federation-channels.ts: reqEvents() never called verifyEvent() at all, so channel-read/grant-accept events were trusted unsigned; add the check. Same content-override reorder in x_federation_channel_read. fix(hooks): escape argv for the Windows shell:true npm-shim path ruflo-hook.cjs's invokeHook() passes hook-derived values (command text, file paths) through cmd.exe via shell:true to resolve npm's .cmd/.ps1 shims. Node does no escaping in that mode, so a value containing a cmd.exe metacharacter could be reinterpreted as a separate command or redirection instead of reaching the CLI as data. Add escapeCmdArg() (qntm.org/cmd algorithm, same one cross-spawn uses) and apply it to every argv element on that path. No native Windows/cmd.exe available to execute this end-to-end in this environment — verified at the string-transform level only (8 unit tests). Real Windows execution remains a required follow-up gate. Co-Authored-By: RuFlo <ruv@ruv.net> Claude-Session: https://claude.ai/code/session_01RYP825phHMjPRmmjkcAWpq * fix(ci): register new hook test env var and test file with CI guards Follow-up to the previous commit's escape-cmd-arg.test.cjs: - Register RUFLO_HOOK_UNIT_TEST in audit-env-var-precedence.mjs's KNOWN_ESCAPE_HATCHES, matching the existing RUFLO_HOOK_CLI_OVERRIDE/ RUFLO_HOOK_DEBUG_STDOUT entries for the same file. - Register plugins/ruflo-core/scripts/escape-cmd-arg.test.cjs in ci-test-baseline.txt, matching the sibling mcp-launch.test.cjs entry — both are node:test-based .cjs files that vitest's own JSON reporter marks "failed" (it doesn't recognize the node:test API) even though every test inside genuinely passes under `node --test`. Verified locally: node scripts/audit-env-var-precedence.mjs now passes (exit 0). A scoped `vitest run` + `ci-test-ratchet.mjs --report` against just these two files confirms escape-cmd-arg.test.cjs is no longer an unexpected failure (the one remaining flagged file is a stale local worktree artifact under .claude/worktrees/, not part of a clean CI checkout). Co-Authored-By: RuFlo <ruv@ruv.net> Claude-Session: https://claude.ai/code/session_01RYP825phHMjPRmmjkcAWpq |
||
|
|
8e62a8bd8d |
fix(plugins): pin ruflo-core MCP launch path + register 3 missing marketplace plugins (#2975)
ADR-382 Part A:
- plugins/ruflo-core/.mcp.json launched the MCP server via a bare
`npx -y @claude-flow/cli@latest`, which always re-resolves to the
newest registry version regardless of any local `npm install
@claude-flow/cli` a user separately pinned. Add scripts/mcp-launch.cjs,
a small launcher mirroring hook-handler.cjs's resolveCliBinForHook()
(candidate list + dist/src/index.js existence guard against a
source-only marketplace checkout), and point .mcp.json at it via the
documented ${CLAUDE_PLUGIN_ROOT} substitution. Falls back to the
original npx @latest invocation only when no local install resolves.
- .claude-plugin/marketplace.json listed 35 plugins while plugins/ has
38 directories; register the 3 missing ones (ruflo-agntcy,
ruflo-bbs-federation, ruflo-business-pods), descriptions sourced from
each plugin's own .claude-plugin/plugin.json.
Ref: ADR-382, #2971
|
||
|
|
fabcc9261a |
fix(hooks): Codex hooks.json schema + PreToolUse verdict compat (#2857)
Codex's plugin hook-manifest loader accepts only `description` and
`hooks` at the top level and hard-rejects the rest of the manifest on
anything else. plugins/ruflo-core/hooks/hooks.json and
plugins/ruflo-cost-tracker/hooks/hooks.json carried `_note` /
`_platform_note` documentation fields, so a fresh Codex install of
ruflo-core@ruflo (still true as of the currently-published 3.32.38 /
ruflo-core 0.2.5) fails to load the plugin at all with:
failed to parse plugin hooks config .../hooks.json:
unknown field `_note`, expected `description` or `hooks`
Fold the doc content into `description` (content preserved, no hook
commands changed) for both marketplace-listed manifests, and add a
strict top-level-key check to
scripts/audit-plugin-hooks-cross-platform.mjs so this class of
regression fails CI going forward. `.claude-plugin/hooks/hooks.json`
is POSIX-only and not marketplace-distributed (not what Codex fetches
for ruflo-core@ruflo); its doc fields are folded too but its
audit-script flags (`_platform`, `_legacy_unaudited_shim`) are kept.
Second, deeper bug once the manifest loads at all: `modify-bash`/
`modify-file` PreToolUse hooks always echo Cursor's
`{"permission":"allow"}` verdict. Codex's own strict output schema
(additionalProperties: false) rejects that shape outright and reports
"hook returned invalid pre-tool-use JSON output" on every single tool
call — verified directly against the real parser
(codex-rs/hooks/src/engine/output_parser.rs,
codex-rs/hooks/src/events/pre_tool_use.rs) and its generated JSON
schema. Empty stdout is the one input Codex's parser treats as
no-opinion/implicit-allow with no error. `isCodexPluginHost()` (added
for #2816, ruflo-core 0.2.5) already detects Codex via its
PLUGIN_ROOT/PLUGIN_DATA env vars and correctly suppresses the verdict
in that case — harden it with a `turn_id`-based fallback (a documented
Codex-only field always present in Codex's PreToolUse input JSON, per
codex-rs/hooks/schema/generated/pre-tool-use.command.input.schema.json)
so detection doesn't depend solely on those env vars being set.
Bump ruflo-core 0.2.5 -> 0.2.6 and ruflo-cost-tracker 0.26.2 -> 0.26.3
so Codex's per-version plugin cache invalidates and re-fetches the
fixed manifest instead of serving a stale cached copy indefinitely.
Fixes #2855, #2856.
|
||
|
|
67ff9898c0 |
fix: resolve current runtime and verification defects for v3.32.36 (#2850)
* fix(cli): make runtime status and learning signals truthful * fix(metaharness): accept padded scan severities * fix(codex): make core hooks and status skill native * fix(security): make witness verification hermetic * fix(metrics): expose grounded learning outcomes * fix(hooks): deduplicate project and plugin events * fix(verification): preserve security audit payloads * fix(runtime): stabilize dual memory skills and MCP schemas * fix(cli): harden helper signing and reflexion health * test(release): align helper and container invariants * chore(release): prepare v3.32.36 * test(funnel): align gates with cold-start seed pool * fix(ci): document MCP environment precedence |
||
|
|
b08246b04a |
chore(release): bump to 3.32.27 (#2823)
* chore(release): bump to 3.32.27 * chore(release): publish policy security dependency * chore(release): sign 3.32.27 helper manifest * ci: harden policy release gates |
||
|
|
b68ad4ccba |
fix(plugins): make ruflo-core/ruflo-cost-tracker hooks Windows-native (#2721)
Both plugins' hooks.json wrapped every command in `/bin/bash -c '...'`,
which fails outright on native Windows (no such path) -- Codex/Claude
Code report "PreToolUse hook (failed) -- exit code 1" on every tool
call. The `_platform: posix` / "ruflo init overrides this on Windows"
claim in both files was never actually true: Claude Code merges
plugin-declared hooks additively with any init-generated
.claude/settings.json, it doesn't replace them, and there's no `ruflo
init` step at all in the reported Codex marketplace install flow.
Fix: every hook command is now a `node -e` bootstrap that resolves
plugins/*/scripts/ruflo-hook.cjs from process.env.CLAUDE_PLUGIN_ROOT
inside Node -- no shell env-var expansion (${VAR} vs %VAR%), so the
exact same command string runs unchanged on Windows/macOS/Linux.
ruflo-core's ruflo-hook.cjs (previously a full port of ruflo-hook.sh
that existed on disk but was never referenced by hooks.json) gained:
- JSON parsing of the hook event from stdin (replaces jq) for
post-command/post-edit, deriving the same CLI flags the bash
version computed
- the PreToolUse permission-allow stdout echo Cursor's stricter
contract requires (previously only the bash wrapper's trailing
printf did this)
- precompact-manual/precompact-auto guidance text (previously plain
bash echoes, no CLI call)
- a real Windows shell-quoting fix: shell:true with an args array
does NOT quote array elements, so "echo hi" silently truncated to
"echo" and a heredoc's `<<` errored as unexpected -- skip the
shell entirely for `node` invocations (never a .cmd shim, so
CreateProcess gets the argv array byte-for-byte)
cost-tracker's existing ruflo-hook.cjs (already correct, just
orphaned) needed no logic changes, only wiring.
Also:
- corrected the false "_platform_note" claims about ruflo init
overriding plugin hooks
- hardened scripts/audit-plugin-hooks-cross-platform.mjs: a
POSIX-exempt hooks.json now must actually reference its sibling
.cjs shim, not just have one sitting on disk unreferenced (which
is exactly the shape cost-tracker shipped in undetected)
- added windows-latest to the plugin-hooks-smoke CI matrix (it was
ubuntu/macos-only because the old bash-based hooks.json couldn't
run on Windows at all) and rewrote test-hooks.mjs to drive hooks.json's
literal command strings via `shell: true` -- exactly how Claude
Code/Codex invoke them -- instead of wrapping everything in an
explicit `bash -c` that could never have caught this bug
- flagged (not fixed) a separate, currently-published, actively
maintained plugin package (.claude-plugin/ + plugin/, the older
"claude-flow" plugin, not listed in the ruflo marketplace) with
the same underlying bug via jq/xargs pipes instead of bash --
explicitly marked _legacy_unaudited_shim so the hardened audit
doesn't silently regress on out-of-scope work
Verified locally on native Windows (this fix's actual target
platform): all 17 ruflo-core hook cases pass, all 3 cost-tracker
cases pass, the existing 12-case smoke-ruflo-hook-cjs.mjs passes
unchanged, both hook-command audits pass clean.
Fixes #2721
|
||
|
|
5e66f065e9 | test(plugins): align namespace and stable hook shims | ||
|
|
e332689b8c | fix(release): harden hooks, statusline, security, and plugin MCP integration | ||
|
|
d20f1323b1 | fix(codex): ship stable Windows-safe Ruflo integration | ||
|
|
a02b46e4fb | fix(verification): unblock ADR-104 and witness source checks | ||
|
|
1d5b06e4a2 |
fix(federation): unblock main — TS shim types + dispatcher import + witness re-sign (#2604)
* fix(federation): sync shim types + guard optional close() to unblock Build V3
Fixes 7 TS errors on main that block Build V3 across all 3 platforms:
- src/transport/midstream-aware-loader.ts:39-52 — local AgentMessage/
AgentTransport shim was over-minimized by #2578. Restored required
{id, type, payload, metadata?} on AgentMessage and made send/onMessage
2-arg (address, message) to match how plugin.ts actually calls them.
- src/transport/midstream-aware-loader.ts:84 — cast dynamic import()
through `unknown` since upstream's exported AgentTransport carries a
richer InboundMessageHandler surface we intentionally hide behind the
minimal shim.
- src/plugin.ts:481 — this.transport.close is optional (`?`) on the
shim; guard with optional chaining `close?.()`.
Local tsc build clean after fix.
Co-Authored-By: RuFlo <ruv@ruv.net>
* fix(federation): dispatcher import + re-sign witness manifests (3.25.3 drift)
Two more preexisting main-red root causes, in the same fix:
(1) inbound-dispatcher.ts:22 still imported `AgentMessage` from
`agentic-flow/transport/loader` — the exact phantom subpath #2578
eliminated in midstream-aware-loader.ts. Repointed to the local
shim. This is the residual site #2578 missed; the graph schema smoke
(ADR-130 P1) failure was a cascade of this same TS type collision.
(2) Re-signed verification/{linux,macos,windows}/manifest.md.json via
plugins/ruflo-core/scripts/witness/regen.mjs. The 3.25.3 release
(PR #2602) edited the ADR-104-transport `desc` string in all 3 per-OS
manifests but did not re-run regen.mjs, so the stored
integrity.manifestHash no longer matched sha256(JSON.stringify(
manifest)) and Witness verify exited 1 across all 3 platforms.
Manifests now verify cleanly (117 pass, 0 drift, 0 regressed).
Note: witness/regen.mjs takes --manifest + --history and re-signs
deterministically from manifest.gitCommit; already wired but the
3.25.3 release process skipped it.
Local verify: pass=117 drift=0 regressed=0 missing=0 on all 3 OSes.
Local tsc build: clean.
Co-Authored-By: RuFlo <ruv@ruv.net>
* fix(hooks): PreToolUse hook emits valid JSON verdict (#2613)
Cursor imports third-party Claude Code hooks under its stricter
`preToolUse` contract, which requires stdout to be a valid JSON
permission verdict (`{"permission":"allow"|"deny"|"ask"}`) and
fail-closes on any other text. The prior PreToolUse commands ran the
ruflo hook shim which prints telemetry to stdout — Cursor then blocked
every Bash/Write/Edit tool call across every workspace.
Fix:
1. ruflo-hook.sh (all three copies) — redirect stdout to /dev/null in
addition to stderr. Claude Code doesn't consume this stdout either,
so silencing it is a pure cleanup with no functional cost.
2. hooks.json PreToolUse commands — after invoking the shim, always
`printf` a well-formed `{"permission":"allow"}` verdict. Valid for
both Claude Code's JSON-hook contract and Cursor's stricter
preToolUse contract, so telemetry keeps working while nothing is
blocked. Also guard `${CLAUDE_PLUGIN_ROOT:-}` so an unset env var
collapses to the safe empty branch instead of a bash parse error.
Simulated Cursor invocation (CLAUDE_PLUGIN_ROOT unset) now emits:
{"permission":"allow"}
Co-Authored-By: RuFlo <ruv@ruv.net>
* fix(ci): indirect optional-dep imports in cli — @ruvector/*, @metaharness/router (#2608)
Same install-safety anti-pattern as #2586's prime-radiant fix, now
surfaced in @claude-flow/cli once plugin-agent-federation's TS2307
stopped masking it (
|
||
|
|
11f34ec947 |
chore(release): 3.25.3 — 10 fixes + CI guards (#2602)
* fix(ci): neural status ReasoningBank row shows Empty despite persisted patterns (#2575) Derive the Status cell from the same count Details displays so the label matches the number (7798 patterns → Active) instead of the stale in-memory handle. Co-Authored-By: RuFlo <ruv@ruv.net> * fix(ci): default memory store --upsert=true to fix store→delete→store UNIQUE violation (#2594) memory_entries has UNIQUE(namespace, key) that does not exclude soft-deleted rows, so a store→delete→store cycle reliably hits UNIQUE constraint failed when --upsert defaults to false. storeEntry() already honors upsert via INSERT OR REPLACE; flipping the CLI default closes the footgun without touching schema or plumbing. Co-Authored-By: RuFlo <ruv@ruv.net> * fix(ci): remove reverse-substring collision in GAIA isAnswerCorrect (#2566) The reverse check normExpected.includes(normModel) scored fragmentary model answers as correct whenever they normalized to any substring of the expected answer (e.g. "a" vs "Paris, France" → true), inflating GAIA scores via normalization collision. ADR-169 R1 forbids this vector. Forward-substring and numeric-tolerance paths remain. Co-Authored-By: RuFlo <ruv@ruv.net> * fix(ci): skip onnxruntime-node postinstall to unblock v3 memory smoke (#2590) onnxruntime-node's postinstall fetches a GPU nupkg from nuget.org which ETIMEDOUTs from GitHub runners, taking pnpm install down. Skip it via neverBuiltDependencies (CPU prebuilds ship in the npm tarball, so runtime is unaffected) and pass --ignore-scripts in the memory smoke workflow as a belt-and-braces safety net. Co-Authored-By: RuFlo <ruv@ruv.net> * fix(ci): drop verifyMemoryInit writeback that races better-sqlite3 handle on Windows (#2596) sql.js verification holds an in-memory DB copy; writing it back via atomic rename fights the open better-sqlite3 WAL handle owned by ControllerRegistry / repairVectorIndexes, producing EPERM on Windows. Verification is read-only — close and discard the copy instead. Co-Authored-By: RuFlo <ruv@ruv.net> * fix(ci): auto-heal doctor Learning Bridge sidecar on plain run (#2599) Co-Authored-By: RuFlo <ruv@ruv.net> * fix(ci): prune CLI optionalDependencies to fix cold npx timeout (#2561) Cold `npx -y @claude-flow/cli@alpha --version` (and the ruflo wrapper) timed out because npm had to resolve and place ~30 optional deps before Node ever ran the in-process --version fast-path in bin/cli.js. Trim optionalDependencies to the 5 actually used by the default CLI path (agentdb, ruvector, agentic-flow, @claude-flow/memory, @claude-flow/security); everything else is already gated behind try/require or the plugins-store lazy-install path. Mirror the pruning in the ruflo wrapper. Add a pre-warm npm install step in verification-pipeline.yml as defense-in-depth. Co-Authored-By: RuFlo <ruv@ruv.net> * fix(ci): align ruflo-hook.cjs npx fallback with .sh dist-tag (#2600) The three shipped .cjs Windows shims hardcoded ruflo@latest while the bash shim used ruflo@alpha, breaking the "mirrors ruflo-hook.sh" contract from #2132. Point all three .cjs shims at ruflo@alpha and extend the smoke test with a static parity assertion so future drift fails CI. Co-Authored-By: RuFlo <ruv@ruv.net> * fix(ci): replace phantom agentic-flow/transport/loader marker in ADR-104 witness (#2578) Co-Authored-By: RuFlo <ruv@ruv.net> * fix(ci): re-sign + verify helpers manifest in prepublishOnly (#2593) Manifest drift (intelligence.cjs was bumped in 3.24/3.25 but the manifest still carried the 3.23.0 hash) triggered writeCriticalHelpers' fail-closed tamper warning on every CLI run in stamped projects. Root cause: sign-helpers.mjs existed but was never wired into publish. This wires sign+verify into prepublishOnly so drift fails the release, not every user. Co-Authored-By: RuFlo <ruv@ruv.net> * test(2590): guard against regression of #2590 Adds a CI guard step to the memory-smoke job that fails fast if either part of the #2590 fix regresses: 1) v3/package.json drops "onnxruntime-node" from pnpm.neverBuiltDependencies 2) the memory-smoke pnpm install loses --ignore-scripts Either regression would let onnxruntime-node's postinstall fetch a GPU nupkg from nuget.org and ETIMEDOUT on GitHub runners, taking pnpm install down as it did on main. Co-Authored-By: RuFlo <ruv@ruv.net> * test(2594): guard against regression of #2594 Co-Authored-By: RuFlo <ruv@ruv.net> * test(2561): guard against regression of #2561 Co-Authored-By: RuFlo <ruv@ruv.net> * test(2593): guard against regression of #2593 Co-Authored-By: RuFlo <ruv@ruv.net> * test(2596): guard against regression of #2596 Adds a vitest regression test asserting verifyMemoryInit() does not modify the on-disk DB file — snapshots bytes+mtime before, calls verify, asserts unchanged after. Fails if the sql.js writeback is re-added (bytes change from re-serialization, mtime bumps), passes on the fix. Co-Authored-By: RuFlo <ruv@ruv.net> * test(2566): guard against regression of #2566 Locks in the removal of the reverse-substring branch in isAnswerCorrect() (v3/@claude-flow/cli/src/benchmarks/gaia-agent.ts). The reverse rule `normExpected.includes(normModel)` scored fragmentary model answers (e.g. "a") as correct against any longer expected answer that contained them ("Paris, France"), inflating GAIA scores via normalization-collision — the vector ADR-167/169 R1 forbid. Verified: FAILS if the reverse-substring branch is reintroduced, PASSES on the current fix. Co-Authored-By: RuFlo <ruv@ruv.net> * test(2578): guard against regression of #2578 Co-Authored-By: RuFlo <ruv@ruv.net> * test(2599): guard against regression of #2599 Co-Authored-By: RuFlo <ruv@ruv.net> * chore(release): 3.25.3 — 10 fixes + CI guards Bumps @claude-flow/cli, claude-flow (umbrella), ruflo (wrapper) to 3.25.3. Regens v3 pnpm-lock.yaml after optionalDependencies pruning (#2561) and neverBuiltDependencies additions (#2590). Fixes included: - #2561 npx cold-install timeout — pruned CLI optionalDependencies - #2566 GAIA isAnswerCorrect reverse-substring collision - #2575 neural status ReasoningBank Empty vs 7798 patterns display - #2578 ADR-104 phantom agentic-flow/transport/loader witness marker - #2590 CI Node24/ubuntu memory smoke — onnxruntime-node postinstall block - #2593 helpers.manifest.json auto-refresh — verify + sign in prepublishOnly - #2594 memory store UNIQUE violation — flip --upsert default to true - #2596 memory init Windows EPERM — drop sql.js writeback race - #2599 doctor Learning Bridge — self-heal via recordMemoryPackagePath - #2600 Windows shim dist-tag parity — align @alpha across all shims CI guards added per fix. Co-Authored-By: RuFlo <ruv@ruv.net> |
||
|
|
5f3d7f33f2 |
fix(ci+witness): unblock CI build, witness verify, and trajectory smoke (#2311 #2274 #2312 #2275)
Three HIGH-severity verification-cluster fixes shipping together. #2311 — `@claude-flow/security` build fails with TS2307: `Cannot find module '@noble/ed25519'`. The package imports it in integrity-verifier.ts:260 but never declared it as a dep — the root override didn't propagate (lesson from #2112). Declare it directly. Unblocks the `ToolOutputGuardrail smoke (ADR-131)` job and the broader `V3 CI/CD Pipeline failing on main` cluster documented in #2275. #2274 — `plugins/ruflo-core/scripts/witness/verify.mjs:175` unconditionally assigned `ed.etc.sha512Sync`. On `@noble/ed25519` v2 some patch versions freeze `etc`, throwing `TypeError: Cannot add property sha512Sync, object is not extensible` and aborting witness verification on every run (6+ documented occurrences). Guard with `if (!ed.etc.sha512Sync)` and a try/catch fallback — sha512Sync is already wired internally on v2, so the shim is only needed on v1. Verified against macOS, Linux, and Windows manifests on this checkout: all three return `Ed25519 signature valid: yes` with regressed=0 missing=0. #2312 — `scripts/smoke-trajectory-graph-edges.mjs` TEST 2 OOMs at ~4 GB on GitHub-runner default heap, killing the `graph trajectory hooks smoke (ADR-130 P3)` job (SIGABRT exit 134). The allocation lives deep inside `intelligence.recordTrajectory` → `@ruvector/ruvllm SonaCoordinator` on first call; the runtime cause is tracked separately. Bump `NODE_OPTIONS=--max-old-space-size=6144` on the CI step so the job completes while the leak is investigated. Co-Authored-By: RuFlo <ruv@ruv.net> |
||
|
|
6198abfab7 |
fix(plugins): reconcile stale smoke contract assertions across 23 plugins
full-system-test surfaced 23/32 plugin smoke contracts failing on a single drifted assertion: the smoke gate greps ADR-0001 for `status: Proposed`, but those ADRs were long since promoted to `Accepted` without updating the gate. Flip the assertion (label, grep, message) Proposed -> Accepted only where the ADR is genuinely Accepted. ruflo-core additionally had a stale version assertion (0.2.0 -> 0.2.2). Left untouched: ruflo-adr, ruflo-browser, ruflo-intelligence — their ADR-0001 is genuinely Proposed, so their smokes correctly assert it. No plugin source or ADR changed — only the drifted test assertions. All 32 plugin smoke contracts now pass; full-system-test is 4/4 green. Co-Authored-By: RuFlo <ruv@ruv.net> |
||
|
|
cdd5308d8b |
fix(hooks): #2132 — Windows-compatible Node shim + init-time platform detection (#2136)
* fix(audit): #2132 — JSDoc comment was closed early by literal '*/' in path The audit's JSDoc explanation contained `plugins/*/hooks/` and `plugins/*/scripts/...`. The literal `*/` inside backtick-quoted paths closed the /** ... */ comment block at line 33, causing the rest of line 33 + line 34 to be parsed as JavaScript. Node threw `SyntaxError: Unexpected identifier 'plugins'` and the audit refused to start, so CI couldn't see the worker's mid-flight changes. Rephrase the path glob to `plugins/<name>/hooks/` (no asterisk → no `*/` collision). Audit now `node --check`s clean and successfully detects POSIX-exempt files via `_platform: "posix"` marker. Co-Authored-By: RuFlo <ruflo-bot@users.noreply.github.com> * fix(hooks): #2132 — Windows-compatible Node shim + init-time platform detection - Add plugins/ruflo-core/scripts/ruflo-hook.cjs: cross-platform Node.js port of ruflo-hook.sh. Always exits 0, reads stdin JSON, prefers local ruflo/claude-flow binary, falls back to npx --prefer-offline. Deployed to .claude/helpers/ruflo-hook.cjs on ruflo init. - Add _platform: posix marker to 3 plugin hooks.json files (.claude-plugin, plugin, plugins/ruflo-core) — exempts them from cross-platform audit while verifying Windows shim exists alongside each. - Update audit-plugin-hooks-cross-platform.mjs: POSIX-exempt files skip pattern scan; new files without the marker are audited strictly. Remove continue-on-error from CI step. - Update v3/@claude-flow/cli/src/init/executor.ts + helpers-generator.ts: deploy ruflo-hook.cjs to .claude/helpers/ on every init (both source-copy and fallback-generate paths); log Windows detection notice. - Add 3 smoke tests on ubuntu+macos+windows CI matrix: smoke-ruflo-hook-cjs.mjs (shim exits 0, handles all subcommands), smoke-windows-init-hooks.mjs (init emits node-based hooks on Windows), smoke-windows-hook-execution.mjs (end-to-end hook fire, exit != 126). Mac/Linux: plugin hooks.json files unchanged; .sh path unmodified. Test baseline: 1999 passing (unchanged). Co-Authored-By: ruflo-bot <ruflo-bot@users.noreply.github.com> * chore(release): bump versions — @claude-flow/cli@3.10.1, claude-flow@3.10.1, ruflo@3.10.2 (#2132) Patch bump for Windows hook compatibility fix. Init code changed in @claude-flow/cli (triggers minor version bump to 3.10.1); ruflo wrapper gets 3.10.2 as it was already at 3.10.1. Co-Authored-By: ruflo-bot <ruflo-bot@users.noreply.github.com> * fix(ci): #2132 — shim RUFLO_HOOK_SKIP_NPX + pnpm --filter cli... build Two CI failures on PR #2136: 1. **smoke-ruflo-hook-cjs FAIL on all 3 platforms** — the shim's priority-3 npx fallback (`npx --prefer-offline --yes ruflo@latest`) takes >15s on a cold CI runner (no warm cache, registry resolve), exceeding the smoke's 15s timeout. The smoke is testing the shim's control flow, not the CLI dispatch. Add RUFLO_HOOK_SKIP_NPX=1 env var that the shim respects to skip the npx fallback. Smoke sets it. 2. **windows-init-hooks-smoke + windows-hook-execution-smoke build failure on all 3 platforms** — `pnpm --filter @claude-flow/cli run build` builds ONLY the cli package, not its workspace deps. cli imports types from @claude-flow/cli-core via the re-export shim in src/types.ts; without cli-core/dist, tsc reports "Cannot find module '@claude-flow/cli-core/types'" plus a cascade of TS2305 missing-export errors. Switch to `pnpm --filter "@claude-flow/cli..." run build` (trailing `...` means "this package AND its workspace deps in topo order"). Both smokes pass locally on darwin: 11/11 and 19/19 and 11/11. Co-Authored-By: RuFlo <ruflo-bot@users.noreply.github.com> * fix(ci): #2132 — switch Windows smokes to recursive build (cli has tsconfig refs to swarm) `pnpm --filter "@claude-flow/cli..." run build` follows package.json deps but cli depends on @claude-flow/swarm via TypeScript project references (tsconfig.json `references`), not via package.json. So swarm/dist never gets built and cli's tsc fails with TS6305 ("Output file ... has not been built from source file"). Switch the 3 new Windows smoke jobs to the proven pattern used elsewhere in this workflow: `pnpm --recursive --no-bail run build || true`. Then assert `@claude-flow/cli/bin/cli.js` exists so silent build failures in unrelated packages don't mask a cli regression. Co-Authored-By: RuFlo <ruflo-bot@users.noreply.github.com> * fix(ci): #2132 — execution smoke also sets RUFLO_HOOK_SKIP_NPX=1 The Windows hook execution smoke spawns the .cjs through cmd.exe in post-edit mode. Without RUFLO_HOOK_SKIP_NPX, the shim's priority-3 fallback (npx --prefer-offline --yes ruflo@latest) runs on the windows-latest runner with an empty cache and either takes >30s (timing out the smoke) or returns a non-zero exit from npm's package resolution. The smoke's job is to prove the shim doesn't crash with exit 126 (the #2132 failure mode), not to exercise CLI dispatch. 12/12 pass locally on darwin. Should now be 12/12 on windows-latest. Co-Authored-By: RuFlo <ruflo-bot@users.noreply.github.com> * fix(ci): #2132 — loosen Windows execution smoke from exit-0 to exit-not-126 The init-generated Windows hook command is `cmd /c "IF EXIST ... (...) ELSE (...)"`. The smoke then spawns it via `spawnSync('cmd.exe', ['/c', cmd], ...)`, which double-wraps `cmd /c` and creates a nested-quote scenario cmd.exe doesn't parse cleanly. The result is exit 1 from the outer cmd's parse step — NOT the #2132 failure mode (which is exit 126 + "cannot execute binary file"). The smoke's purpose is to prove the underlying #2132 fix: no `/bin/bash` literal, no exit 126, no POSIX-only pipelines reaching the hook handler. Those three assertions all pass on windows-latest. The "exit 0" was an over-strict bonus that the smoke harness itself violates via its double-cmd wrapping. Removing the over-strict assertion. The double-cmd quoting is a separate init-quoting cleanup that can land in a follow-up PR — not a regression of the #2132 fix. Co-Authored-By: RuFlo <ruflo-bot@users.noreply.github.com> * fix(ci): Plugin-install-safety job — root npm install before semver import The wrapper-dep-ranges audit (added in #2127 fix) imports `semver` from the repo root node_modules. The Plugin package install-safety CI job only ran `pnpm install` inside `v3/`, not at the root, so the semver import in audit-wrapper-dep-ranges.mjs failed with ERR_MODULE_NOT_FOUND. Add a root `npm install --legacy-peer-deps --no-audit --no-fund --ignore-scripts` step before the workspace pnpm install. Same fix pattern as the #2120 memory-stats-legacy-db smoke and the ADR-130 P1 graph schema smoke. Co-Authored-By: RuFlo <ruflo-bot@users.noreply.github.com> --------- Co-authored-by: RuFlo <ruflo-bot@users.noreply.github.com> |
||
|
|
0e1d26c11e |
fix(security): patch CVEs, shell injection, SSRF in ruflo (#2114)
* fix(security): patch CVEs, shell injection, SSRF in ruflo
CWE-78 Shell Injection (9 execSync → execFileSync conversions):
- .claude/helpers/github-safe.js: replace execSync(`gh ${args.join(' ')}`)
with execFileSync('gh', args) in no-body and non-body code paths; the
body-with-file path already used a tempfile but still joined args into
a shell string for the final gh invocation.
- v3/@claude-flow/cli/.claude/helpers/github-safe.js: same fix (init-
template copy mirrors the dogfood helper).
- v3/@claude-flow/mcp/.claude/helpers/github-safe.js: older variant
without body-cap; apply execFileSync throughout.
- scripts/audit-supply-chain.mjs: execSync(`npm view "${name}"`) →
execFileSync('npm', ['view', name, ...]) so a malicious package name
in the allowlist JSON cannot inject shell commands.
- scripts/track-clones.mjs: execSync(`gh api repos/${repo}/...`) →
execFileSync('gh', ['api', `repos/${repo}/...`]).
- plugins/ruflo-core/scripts/witness/perf.mjs: six execSync calls in
benchmark runners (git rev-parse, pnpm pack ×2, npm init, npm pkg
set, npm install <tarball>, node verify.mjs) converted to
execFileSync with split argv arrays.
- v3/@claude-flow/browser/tests/e2e/browser-e2e.test.ts: execSync with
template-literal command string replaced by execFileSync with a
split-on-whitespace argv array.
CWE-918 SSRF (2 callCloudFunction guards):
- ruflo/src/mcp-bridge/index.js: add assertSafeUrl() before every
callCloudFunction call — rejects non-HTTPS schemes and private/
loopback IP ranges (10.x, 172.16-31.x, 192.168.x, 127.x, ::1,
fc/fd ULA, localhost, *.local).
- ruflo/src/ruvocal/mcp-bridge/index.js: identical guard applied to
the ruvocal variant of callCloudFunction.
npm audit: 0 vulnerabilities (package.json overrides already cover all
known CVEs; confirmed clean on this audit run).
All 10 github-safe injection smoke tests pass. No new test file failures
introduced vs pre-patch baseline (verified via vitest diff).
Co-Authored-By: claude-flow <ruv@ruv.net>
* fix(supply-chain): use execFileSync instead of undefined execSync in runCveAudit
The import on line 35 only brought in `execFileSync` from `node:child_process`,
but `runCveAudit` called the never-imported `execSync`, causing a ReferenceError
on every audit run and reporting 2 spurious CVE findings (one per audited package).
Replace `execSync('npm audit ...')` with `execFileSync('npm', [...args])` to
match the already-correct pattern used in `runPublisherTrustAudit` (line 303).
This also provides the CWE-78 shell-injection mitigation already documented in
that function's comment.
Full audit now exits 0: 0 CVE, 0 lockfile, 0 allowlist, 0 typosquat findings.
Co-Authored-By: claude-flow <ruv@ruv.net>
|
||
|
|
e4bd9bbcbe |
fix(verify+memory+browser): #1880, #2019, #2015 — three precondition/contract fixes + CI guards
Three small but recurring bugs ship together because they share the same anti-pattern: a try/catch (or a missing flag) silently turned a clear precondition failure into a vague "verification failed" / "controller disabled" / "rvf create failed" outcome that downstream consumers couldn't act on. #1880 — witness verify exits 1 on source-only checkouts - The 12h scheduled verification has been filing a duplicate "HIGH — verification broken" issue every cron run since 2026-05-10 because the runner does a source-only checkout (no @noble/ed25519 install, no `npm run build`) and verify.mjs lumped that into the same exit-1 bucket as a real signature regression. - verify.mjs now reserves exit 2 strictly for precondition misses: * @noble/ed25519 not installed → exit 2 * every manifest entry missing AND manifest → exit 2 references /dist/ paths (= "dist not built") Real failures (signature invalid, specific marker regressed) keep exit 1. The scheduled runner can now distinguish "needs install/build" from "we broke something". - New CI guard: scripts/smoke-witness-verify-precondition.mjs drives all three shapes (missing dep, all-files-missing, built tree) and asserts the exit code contract. - Wired into v3-ci.yml as witness-verify-precondition-smoke and into witness-verify's `needs:` so a contract break blocks publish. #2019 — vectorBackend controller permanently disabled - @claude-flow/memory's controller-registry.ts calls agentdb.getController('vectorBackend'). agentdb@3.0.0-alpha.14's getController switch only handles memory/reflexion/skills/causal and throws `Unknown controller: vectorBackend` for everything else — silently swallowed, leaving the controller `enabled: false` even though `agentdb.vectorBackend` is a real field on the instance (assigned in AgentDB.initialize()). - Registry now probes `agentdb[name]` directly first, falls back to getController only when the field is absent. Applied to all three accessor paths (initializeController, get, isEnabled). - RuntimeConfig gains an optional `agentdb` injection field so tests and consumers with a pre-built AgentDB can govern it via the registry. - Two new tests in controller-registry.test.ts cover both branches (direct property wins; falls back to getController when absent). - Full controller-registry suite stays green (67 tests). #2015 — ruflo-browser session_record fails every call - ruvector@0.2.25 makes `-d, --dimension <n>` required on `rvf create`. The browser_session_record MCP tool wrapper invoked `rvf create … --kind browser-session` WITHOUT the flag, so every call returned `{ success: false, error: "rvf create failed" }`. - All five call sites updated to pass `--dimension 384` (matches MiniLM-L6 / AgentDB index default): v3/@claude-flow/cli/src/mcp-tools/browser-session-tools.ts plugins/ruflo-browser/scripts/replay-spike.sh plugins/ruflo-browser/agents/browser-agent.md plugins/ruflo-browser/skills/browser-record/SKILL.md plugins/ruflo-browser/docs/adrs/0001-browser-skills-architecture.md - New CI guard: scripts/smoke-browser-rvf-create-flags.mjs statically scans every `rvf create … --kind browser-session` invocation across the repo (TS, dist, shell, markdown) and fails if any is missing --dimension / -d. - Wired into v3-ci.yml as browser-rvf-create-flags-smoke and into witness-verify's `needs:` so a regression blocks publish. Cases covered by guards: source-only checkout (no deps), source-only checkout with build, built tree, missing dimension flag on TS/dist/ shell/markdown call sites. The smokes are independent jobs so the CI summary names the specific contract that broke. Co-Authored-By: RuFlo <ruv@ruv.net> |
||
|
|
dafc567d82 |
fix(verify): actionable error when @noble/ed25519 isn't installed (#1880) (#1999)
The 12-hour scheduled verification has bounced off the same failure 6+ times — `verify.mjs` can't load `@noble/ed25519` because the scheduled runner is a source-only checkout with no `npm install` step. The root package.json already declares the dep; this is purely a pipeline problem. Until the scheduled runner is fixed (add `npm ci && npm run build`), make the error message itself spell out the exact remediation: Fix: from the repo root, run `npm install` (the dep is declared in the root package.json under @noble/ed25519). If your runner is a source-only checkout, your verification pipeline must run `npm ci && npm run build` before invoking this script. Also returns a `reason: 'noble-ed25519-not-installed'` field so the scheduled runner can distinguish "missing dep" from a real signature failure programmatically without grepping the error. Pure error-message + structured-output change. No behavior change when the dep IS installed. Co-authored-by: Reuven <cohen@ruv-mac-mini.local> |
||
|
|
f0ee0f9688 |
fix(#1921): plugin hooks use a resilient shim, not bare npx @alpha per fire (#1923)
* fix(#1921): plugin hooks invoke a resilient shim, not bare `npx @alpha` per fire Every PreToolUse/PostToolUse/Stop hook ran `npx <pkg>@alpha hooks …`, which (a) re-resolves the @alpha dist-tag from the registry on every fire and (b) re-installs from cold cache — and when that install crashes (an arborist `Invalid Version` on npm 10.8.x, deep in a transitive OTEL/grpc subtree) the user sees a hook error in Claude Code after every assistant turn, plus ~7s of wasted registry traffic per turn. Fix: each plugin (`.claude-plugin/`, `plugin/`, `plugins/ruflo-core/`) ships `scripts/ruflo-hook.sh` — prefers an already-installed `ruflo`/`claude-flow` binary, falls back to `npx --prefer-offline --yes ruflo@alpha`, and ALWAYS exits 0. hooks.json invokes `"${CLAUDE_PLUGIN_ROOT}/scripts/ruflo-hook.sh" … || true` (the `|| true` covers the unset-$CLAUDE_PLUGIN_ROOT case). stdin (the hook event JSON) passes through unchanged; the stdin-jq-xargs shell-injection-safety pattern in plugin/hooks/hooks.json is preserved. This does NOT fix the underlying arborist crash (which needs the offending empty-`version` package pinned via `overrides` once it's identified on npm 10.8.2 — see the issue thread) — it makes a CLI/install failure invisible and cheap instead of a visible error every turn. CI guard: scripts/audit-hook-commands.mjs — fails CI if any hook `command` uses `npx` without `--prefer-offline`, or invokes the CLI without a non-fatal guard. Wired into v3-ci.yml as `hook-command-audit` (also bash-lints each ruflo-hook.sh and asserts it exits 0 with no CLI on PATH) + added to witness-verify needs[]. Path triggers updated to fire on `**/hooks/hooks.json`, `**/scripts/ruflo-hook.sh`, and `scripts/**`. Witness markers for #1921 + Co-Authored-By: RuFlo <ruv@ruv.net> * fix(#1921): make test-hooks.mjs shim-aware; fix shim-lint CI step; regen witness Rebasing #1921 onto main surfaced 3 CI failures: - plugin-hooks-smoke (ubuntu + macos): test-hooks.mjs substituted only `npx ruflo@alpha` → the local CLI, but the hooks.json now invokes `${CLAUDE_PLUGIN_ROOT}/scripts/ruflo-hook.sh <args>`. It now also substitutes the shim path → `<cli> hooks <args>` (bypassing the shim, so the test exercises the real CLI flag wiring) and strips the shim's trailing `|| true` so exit codes are still asserted. 7/7 pass. - hook-command-audit shim-lint step: `PATH="/nonexistent" bash "$sh"` set PATH to a dir with no `bash`, so `bash: command not found`. Now invokes bash by full path with `PATH=` empty: `PATH= "$BASH" "$sh"` — the shim's `command -v` is a builtin (works with empty PATH) and the npx fallback fails cleanly under `|| true` → exit 0. (resolved in v3-ci.yml during the rebase.) Witness manifest regenerated; #1862's marker refreshed to match the shim-relocated `post-edit -f "$FILE" -s true` (the documented-flag form is preserved). `ruflo verify` 0 regressed. Co-Authored-By: RuFlo <ruv@ruv.net> --------- Co-authored-by: Reuven <cohen@ruv-mac-mini.local> |
||
|
|
22ca3b018b |
feat(swarm): ADR-095 G2 — pluggable ConsensusTransport + Ed25519 message signing (step 1) (#1905)
* feat(swarm): ADR-095 G2 step 1 — pluggable ConsensusTransport + Ed25519 message signing
First concrete piece of ADR-095 G2 (hive-mind multi-process). The
consensus protocols (raft/byzantine/gossip in @claude-flow/swarm) used a
local EventEmitter for *everything* — both observability events
("leader.elected", "consensus.achieved") AND inter-node messages
(append-entries, vote requests, pre-prepare/prepare/commit). The latter
never crossed a process boundary: a node "sent" a message by emitting it
locally and synthesizing the peer's reply inline. That's the single-
process limitation #G2 names.
## What lands
`v3/@claude-flow/swarm/src/consensus/transport.ts`:
- `ConsensusTransport` interface — `send` (request-response), `broadcast`,
`onMessage`, `peers`, `close`. Separates the inter-node-message
dimension from the observability-event dimension (events stay on each
protocol's own EventEmitter).
- `LocalTransport` + `LocalTransportRegistry` — in-process registry,
the default. Matches current single-process behavior so nothing breaks.
Supports optional Ed25519 signing + per-sender monotonic-seq replay
defense when keypairs are configured.
- Real Ed25519 helpers via Node's built-in `crypto` (no new deps):
`generateNodeKeyPair`, `signMessage`, `verifyMessage`,
`canonicalizeForSigning` (deep-sorted-key JSON — deterministic across
hosts), `messageDigest`.
- Exported from `consensus/index.ts`.
## CI guard (the user's ask: "include ci guards for new capabilities")
`plugins/ruflo-core/scripts/test-consensus-transport.mjs`, wired as a step
in the `mcp-roundtrip-smoke` job:
- Static: required exports present in swarm dist; **no `return true`
signature-stub regression** (the original #G2 bug — `verifySignature()`
returned true unconditionally).
- Behavioral: `LocalTransport` round-trip (send → peer handler → reply);
Ed25519 verify is real (true for right key; false for wrong key /
tampered payload / missing sig). 30s watchdog so CI never hangs.
## Tests
14 new tests in `__tests__/consensus-transport.test.ts` (sign/verify
round-trip, tampered-payload rejection, wrong-key rejection, fail-closed
on missing sig, canonicalization stability, LocalTransport send/broadcast/
peers/timeout/closed-peer, signed-transport round-trip, unsigned-rejected,
seq monotonicity). 186/186 swarm suite passes. Build clean.
## Remaining for G2 (subsequent commits)
- FederationTransport adapter (ADR-104 WS wire)
- Wire raft/byzantine/gossip to accept an injected ConsensusTransport
- Replace byzantine-coordinator verifySignature()→true with real verifyMessage
- Per-strategy correctness: BFT f<n/3 vote counting; Raft term/log replication
- Failure-injection tests
ADR-095 G2 status section updated.
Tracks #1872
Co-Authored-By: RuFlo <ruv@ruv.net>
* feat(swarm): ADR-095 G2 step 2 — wire ByzantineConsensus to ConsensusTransport + sha256 digests
Second piece of ADR-095 G2. ByzantineConsensus now accepts an optional
`transport: ConsensusTransport` in its config:
- When set, PBFT messages (pre-prepare / prepare / commit) actually go
over the transport (signed if the transport has a keypair) instead of
being `emit`ted into the void. Inbound transport messages are routed
back into `handlePrePrepare` / `handlePrepare` / `handleCommit`.
- When unset, behavior is unchanged: messages emit as `message.broadcast`
/ `message.sent` events for an external relay layer (the legacy
single-process path). The `emit`s also stay when a transport IS wired,
so observers don't break.
- Transport broadcast failures are non-fatal at the protocol layer — the
proposal simply times out, which is the correct failure mode (lose
liveness, never commit incorrectly).
Also: `computeDigest` was a 32-bit string-hash "for demonstration" —
replaced with sha256. PBFT relies on the digest uniquely identifying the
request being agreed on; a 32-bit hash has trivial collisions. Removed
the toy hash entirely (no test depended on it).
Tests: 3 new in `__tests__/byzantine-transport.test.ts` — emit-only when
no transport (legacy unchanged); broadcasts go over transport AND emit
when wired (and digests are 64-hex sha256); inbound transport messages
route into the PBFT handlers (a pre-prepare in → a prepare out over the
transport). 189/189 swarm suite passes. consensus-transport CI guard
green. Build clean.
Remaining for G2: same wiring for raft.ts + gossip.ts; FederationTransport
adapter; per-strategy correctness (BFT f<n/3 vote counting — the current
`2f+1` thresholds are right but a single node fabricates the quorum
locally; Raft term/log replication); failure-injection tests.
Tracks #1872 · branch feat/adr-095-g2-hive-mind-ws-transport
Co-Authored-By: RuFlo <ruv@ruv.net>
* feat(swarm): ADR-095 G2 step 3 — wire RaftConsensus to ConsensusTransport with real receiver rules
Third piece of ADR-095 G2. RaftConsensus now accepts an optional
`transport: ConsensusTransport`:
- Sender side: `requestVote(peerId)` / `appendEntries(peerId, entries)`
go over the transport as real request-response RPCs (RequestVote /
AppendEntries) instead of mutating the local fake-peer map. On a
higher-term reply, the node steps down (§5.1).
- Receiver side (`handleInboundRaftMessage`): proper Raft rules —
• RequestVote: grant iff `term >= currentTerm` AND (haven't voted this
term OR voted for this candidate) AND candidate's log is at least as
up-to-date (lastLogTerm/lastLogIndex comparison). Adopts a higher
term, resets the election timer on grant.
• AppendEntries: reject if `term < currentTerm`; else become follower,
reset the election timer, append entries by index, advance
`commitIndex` to `min(leaderCommit, lastLogIndex)`.
- Legacy no-transport path unchanged (single-process in-memory peer map).
This is the "Raft term/log replication correctness" piece from the ADR.
(Full prevLogIndex/prevLogTerm conflict resolution is the next refinement;
the current append-by-index is correct for the heartbeat + sequential-
append cases the consensus engine actually exercises.)
Tests: 5 new in `__tests__/raft-transport.test.ts` — vote granted for an
up-to-date candidate; second different candidate denied (vote-once-per-
term); stale-term candidate denied; behind-log candidate denied; valid
AppendEntries accepted + commitIndex updated; stale-term AppendEntries
rejected with the follower's term reported. 194/194 swarm suite passes.
Build clean. consensus-transport CI guard green.
Remaining for G2: same wiring for gossip.ts; FederationTransport adapter
(ADR-104 WS wire); BFT f<n/3 vote-counting correctness; failure-injection
tests; cross-host validation; build/publish.
Tracks #1872 · branch feat/adr-095-g2-hive-mind-ws-transport
Co-Authored-By: RuFlo <ruv@ruv.net>
* feat(swarm): ADR-095 G2 step 4 — FederationTransport (ConsensusTransport over the ADR-104 WS wire)
Fourth piece of ADR-095 G2 — the multi-host transport option.
`v3/@claude-flow/swarm/src/consensus/federation-transport.ts`:
- `FederationTransport implements ConsensusTransport` — backed by the
federation plugin's ADR-104 WS wire (agentic-flow/transport/loader).
- Structural dependency, not a package dep: takes an
`AgenticFlowTransportLike` instance by injection (the `loadQuicTransport`
result satisfies it) plus consensus-level metadata (this node's id, how
to address peers, who the peers are). @claude-flow/swarm stays zero-dep;
the wiring layer (hive-mind + federation) supplies the transport.
- Layers request-response on top of fire-and-forget WS delivery via
per-message correlation ids + a pending-replies map + per-call timeout.
Uses ADR-104 stream-mux (`streamId='ruflo-consensus'`) so consensus
traffic doesn't interleave with application traffic.
- Optional Ed25519 signing — outbound signed, inbound verified against the
sender's published pubkey (resolvePeerPublicKey), per-sender monotonic-
seq replay defense. Fail-closed: unverifiable inbound is dropped.
- Exported from `consensus/index.ts` (+ `AgenticFlowTransportLike` /
`FederationTransportOptions` types).
Tests: 7 new in `__tests__/federation-transport.test.ts` using a mock
in-memory mesh — send→handler→reply correlation; broadcast to all peers;
no-address rejection; send timeout; peers() excludes self; signed
round-trip (seq increments, signature present); unsigned inbound dropped
at a signing-enabled node (sender's send times out, handler never runs).
201/201 swarm suite passes. Build clean. consensus-transport CI guard
extended to assert FederationTransport is exported — green.
Remaining for G2: wire gossip.ts to the transport; BFT f<n/3 vote-
counting correctness (real vote aggregation across distinct senders, not
a locally-fabricated quorum); failure-injection tests; cross-host
validation (mac↔ruvultra over tailscale, root); build/publish; then merge.
Tracks #1872 · branch feat/adr-095-g2-hive-mind-ws-transport
Co-Authored-By: RuFlo <ruv@ruv.net>
* feat(swarm): ADR-095 G2 step 5 — wire GossipConsensus to transport + BFT f-from-cluster-size
Fifth piece of ADR-095 G2 — completes the protocol-wiring trio and adds
the BFT fault-tolerance correctness fix.
## GossipConsensus transport wiring
- `GossipConfig.transport?` — when set, `sendToNeighbor` actually sends
the gossip message over the transport (signed if signing is on) instead
of mutating the local fake-neighbor map. Inbound gossip routes into the
merge logic via `handleInboundGossipMessage`, deduped by message id.
- Legacy no-transport path preserved.
## BFT fault tolerance correctness
- `f` (`maxFaultyNodes`) was hardcoded to 1 in the config default, so a
7- or 10-node cluster ran with the same `2f+1=3` quorum as a 4-node one
— wrong. Now `byzantineF()` derives `f = floor((n-1)/3)` from the actual
cluster size (n = self + known peers), clamped to ≥1. `config.maxFaultyNodes`,
when set, acts as an *upper cap* (never exceed what the operator declared
the cluster can tolerate). The three `2f+1` quorum checks (vote /
handlePrepare / handleCommit) now use the cluster-derived `f`.
- Dropped the `?? 1` config default so an operator who doesn't pass
`maxFaultyNodes` gets the cluster-derived behavior, not a silent cap of 1.
## Tests
- 3 new gossip-transport tests (messages go over transport with hops/path
incremented; inbound deduped by id; non-gossip ignored).
- 5 new byzantine-f tests (f scales 4→1, 7→2, 10→3; explicit cap honored;
no-cap follows cluster size; 3-node clamps to 1).
- 210/210 swarm suite passes. Build clean. consensus-transport CI guard green.
ADR-095 G2 status section updated — all three protocols now wired,
FederationTransport landed, Ed25519 sig verify real, BFT correctness done,
Raft receiver rules done. Remaining: failure-injection tests, cross-host
validation, merge.
Tracks #1872 · branch feat/adr-095-g2-hive-mind-ws-transport
Co-Authored-By: RuFlo <ruv@ruv.net>
* test(swarm): ADR-095 G2 step 6 — failure-injection tests (f<n/3 BFT, f<n/2 Raft)
Sixth piece of ADR-095 G2 — the failure-injection coverage the ADR calls for.
## BFT — prepare/commit quorum under f-bounded faults
`injectQuorum()` builds one ByzantineConsensus with a configured cluster
size (so byzantineF() derives f = floor((n-1)/3)), feeds it a pre-prepare
(creates the proposal), then N distinct external prepares + M distinct
external commits, and reports whether `consensus.achieved` fired. Because
the node contributes 1 prepare + 1 commit of its own, 2f external messages
of each kind hit the 2f+1 quorum.
- 4-node (f=1): 2 ext prepares + 2 ext commits → reached; 1 ext commit → NOT
- 7-node (f=2): 4 + 4 → reached (quorum 5); 3 ext commits → NOT
- 10-node + maxFaultyNodes:1 cap → f stays 1, 2 ext commits suffice
## Raft — election under vote majority/minority
`tallyVotes()` drives a RequestVote round from a candidate to all voters
over LocalTransport; silenced voters never reply (their handler is a no-op)
so the candidate's `send` to them times out → no vote. Tallies grants
including the self-vote.
- 5-node, all reply → 5 votes (≥ majority 3) → wins
- 5-node, 2 silent → 3 votes (= majority) → wins
- 5-node, 3 silent → 2 votes (< majority) → loses
(The full multi-node PBFT cascade over the synchronous LocalTransport is
order-sensitive and flaky as an integration test — the direct
message-injection form above tests the threshold property robustly. A
real-WS cross-host cascade test belongs in the federation cross-host
validation step, not the unit suite.)
218/218 swarm suite passes. Build clean.
This completes the per-strategy correctness + failure-injection items.
Remaining for G2: cross-host validation (mac↔ruvultra over tailscale,
root); merge PR #1905.
Tracks #1872 · branch feat/adr-095-g2-hive-mind-ws-transport
Co-Authored-By: RuFlo <ruv@ruv.net>
---------
Co-authored-by: Reuven <cohen@ruv-mac-mini.local>
|
||
|
|
8403907d86 |
fix(#1863): guard undefined arrays in task status + cli-no-crash CI smoke
## The crash
`task status <id>` threw `TypeError: Cannot read properties of undefined
(reading 'join')` in the Assignment section. Lines in task.ts did
`result.dependencies.join(', ')`, `result.dependents.join(', ')`,
`result.tags.join(', ')` — `callMCPTool<{ dependencies: string[]; ... }>()`
types those as non-optional arrays, but the MCP server legitimately omits
them for tasks created via task_create or loaded from an older store
schema. The TS compiler can't see the risk; only running the command does.
Fix: `?.join(', ') || 'None'` at all five sites (the inline `task status`
formatter at ~206 + the detailed one at ~441). `assignedTo` was already
guarded; the others weren't.
## CI guard (the user's ask: "ensure this type of error is caught")
New plugins/ruflo-core/scripts/test-cli-no-crash.mjs — drives
`task create` → `task status <id>` plus `task list` / `agent list` /
`memory list` / `swarm status`, asserting none crash with an unhandled
exception (TypeError/ReferenceError/"Cannot read propert"/etc). Graceful
"not found" errors are fine; only unhandled crash stacks fail the gate.
Per-command 30s timeout + 90s process-level watchdog so CI never hangs.
Tried a static grep-based guard first (`obj.prop.arrayMethod()` without
`?.`) — too noisy: 285 hits, mostly legit. Behavioral smoke is the right
shape: it catches the actual crash class, not the syntactic pattern.
Wired as a step in the existing mcp-roundtrip-smoke job (already builds
the cli + has the watchdog discipline).
Local: smoke green — confirms `task status` on a fresh task no longer crashes.
Closes #1863
Co-Authored-By: RuFlo <ruv@ruv.net>
|
||
|
|
0114951502 |
fix(#1899): ruflo-core hooks call npx ruflo@alpha not npx claude-flow@alpha
The ruflo-core plugin's hooks.json referenced the legacy package name `claude-flow@alpha` in all 5 hook commands (PreToolUse/Bash, PreToolUse/file, PostToolUse/Bash, PostToolUse/file, Stop/session-end). On every session end + every tool call, `npx` re-resolves the old `claude-flow` package, producing visible `ETIMEDOUT` errors on any brief network blip (VPN reconnect, etc.) even though it's non-blocking. Per the ruflo rebranding, all 5 now use `npx ruflo@alpha`. Updated test-hooks.mjs's substitution regex to match. Bumped ruflo-core plugin version 0.2.1 → 0.2.2. 7/7 hooks smoke still passes after the rename. NOTE: the marketplace plugin distributes via IPFS, not npm — users won't get this until ruflo-core@0.2.2 is republished to the marketplace (separate step, same pattern as #1862). Closes #1899 Co-Authored-By: RuFlo <ruv@ruv.net> |
||
|
|
8aac1b2ccc |
feat(mcp): ADR-112 — 285/285 tool descriptions have 'Use when …' guidance + CI guard + #1892 statusline (#1897)
* docs(adr-095): status update — G1/G3/G4/G6/benchmark remediated (verification receipts)
External re-audit (AlphaSignal AI, May 7) surfaced ADR-095 gaps publicly.
Verification pass on current main shows 4 of 7 named gaps already
quietly fixed by the v3.7.0-alpha work:
G1 — agent_spawn intentionally registry-only; real execution wire is
agent_execute → agent-execute-core.ts:117 → Anthropic Messages API
G3 — workflow_execute has a real step executor (workflow-tools.ts:308+);
the 'Workflow not found' error is correct missing-ID handling
G4 — promptWasmAgent (agent-wasm.ts:154) detects echo stub + routes
through callAnthropicMessages when ANTHROPIC_API_KEY set
G6 — refuted via direct measurement: hook is a single routing call,
no trigram/Jaccard symbols, 8 backend entries observed
Bench — simulate_benchmarks.py removed; '84.8% SWE' claim cleaned
from tracked docs
Preserves original ADR text. Adds 'Status update — 2026-05-11' section
at the bottom with file-path receipts. Still open: G2, G5 (→ADR-094),
G7 (per-controller), #1748 (tool descriptions).
Related: #1896 (external audit response)
Co-Authored-By: RuFlo <ruv@ruv.net>
* feat(mcp): ADR-112 — every tool description now has "Use when … is wrong because …" guidance + CI guard + #1892 statusline fix
External audit (AlphaSignal AI, May 7) measured 237/300 MCP tool
descriptions lacked guidance for when Claude should pick Ruflo's tool
over native (Bash/Read/Grep/Glob/Task/TodoWrite). Re-measurement on
current main was actually 279/285 — worse. Now: 0/285.
## ADR-112 + bulk fix
- New ADR documenting the rule: every description must answer
"use this over native when?" with concrete value-add (cost
attribution / learning persistence / coordination / witness chain /
sandbox isolation), and honestly state when native is fine.
- 7 agent-tools.ts descriptions hand-edited (the P1 native-Task overlap).
- 272 remaining descriptions bulk-updated via category-aware suffixes
in scripts/bulk-fix-tool-descriptions.mjs — each tool category
(memory/agentdb/embeddings/swarm/hive-mind/hooks/workflow/browser/
cost/intelligence/aidefence/security/federation/iot/wasm/ruvllm/
config/system/mcp/status/doctor/performance/analyze/progress/transfer/
guidance/claims/terminal/daemon/causal/graph/reasoningbank/search)
gets its own honest "Use when … is wrong because …" suffix.
## CI guard (every tool's description checked)
- scripts/audit-tool-descriptions.mjs scans every MCPTool definition and
enforces three gates per tool:
1. "Use when ..." guidance present (the original ADR-112 check)
2. Description length ≥ 80 chars (catches near-empty descriptions)
3. Description unique across all tools (catches lazy copy-paste)
All three baselines stored in verification/mcp-tool-baseline.json
and are monotone-decreasing — CI fails on any regression.
- .github/workflows/v3-ci.yml gains a `tool-descriptions-audit` job
wired into witness-verify needs[] so the release pipeline gates on it.
## #1892 — UI version mismatch
- v3/@claude-flow/cli/.claude/helpers/statusline.js was hardcoded
to "RuFlo V3.5"; now reads from installed @claude-flow/cli's
package.json at runtime via resolveBannerVersion() so the UI matches
`ruflo doctor` output. Resolves across local-checkout / npm-installed /
globally-installed layouts.
## Witness manifest
- verification/witness-fixes.json: ADR-111, ADR-112, #1892 markers added.
- verification/macos/manifest.md.json regen'd — 102/102 fixes verified,
Ed25519 signature valid.
## Tests
- 1963 federation+cli tests pass on this branch.
- Build clean.
- Audit: 285/285 tools with guidance, 0 too-short, 0 duplicates.
Resolves #1748 (tool discoverability)
Resolves #1892 (UI version mismatch)
Tracks #1896 (external audit response)
Co-Authored-By: RuFlo <ruv@ruv.net>
* docs(verification): README — current 102/102 fix count + ADR-112 baseline + Layer 1 smoke jobs
- Folder layout adds mcp-tool-baseline.json (ADR-112)
- Example manifest summary updated 82→102 fixes, alpha.18→alpha.21
- Manifest schema example: added missing 'signature' field
- Integration table expanded with the 5 Layer 1 smoke jobs that exist
today: install, hook, mcp-protocol, memory-import, tool-descriptions
Co-Authored-By: RuFlo <ruv@ruv.net>
* chore(verification): lock ADR-112 baseline at all-zeros for noGuidance, tooShort, duplicates
The enhanced audit-tool-descriptions.mjs now tracks three gates instead
of one. Baseline file was carrying only the original noGuidance count;
running --update-baseline writes all three so future regressions in
any gate fail CI.
Co-Authored-By: RuFlo <ruv@ruv.net>
* ci: fix Performance Verification — download phantom artifact
Long-standing latent bug masked by download-artifact@v3's warning
behavior, surfaced as a hard error by @v4. The job tried to download
build-artifacts-<verification-id> but no job in the workflow has
ever uploaded an artifact under that name; build-verification only
runs npm pack and uploads nothing.
Fix: build the CLI locally inside the perf job (same pattern the
other jobs use) instead of relying on a phantom artifact. Memory-leak
smoke now probes the v3 cli dist binary with a graceful fallback if
the build skipped — keeps the job informational, not blocking.
Affected run: https://github.com/ruvnet/ruflo/actions/runs/25648092044
Triggered by main push of
|