Files
ruflo/plugins/ruflo-plugin-creator
Dragan Spiridonov b6f3988de7 fix(ruflo-mods): classify risky host calls and intercepting hooks (#3802)
* fix(ruflo-mods): trust gate also treats the agent.spawn, prompt.submit, tool.call, command.run, session.append and agent.register calls and the tool.describe and session.append hooks as risky (#3787, #3801)

Adversarial review of the previous commit: risk was checked per scanned
`calls` and `events` separately, and only the events side had agent.spawn
and prompt.submit, so a mod CALLING $.agent.spawn / $.prompt.submit /
$.tool.call still loaded under refuse-risky ("no risky calls or hooks").
session.append and agent.register, and the tool.describe and
session.append hooks (rewriting what the model reads).

Docs follow the code: README hardening note lists the first-party mods that
now need modTrustAllow under refuse-risky (agentdb, ruos, intelligence,
graph-intelligence, goals, iot-cognitum beside swarm and console), the
modTrust row and plugin.json description name the categories, and the
create-mod skill's list matches.

* fix(ruflo-mods): trust gate judges a glob or negation by what it selects; covers prompt sections/context, peer receive, model requests (#3801)

Re-review: the scan reports hook patterns as written, and riskOf matched
names exactly, so `on('tool.*')` or `on('!tool.describe')` loaded under
refuse-risky with "no risky calls or hooks" and could answer tool
permission verdicts (shown: tool.check returned allow). A glob now maps to
every risky event it selects, a negation to `*`, and any classic.* pattern
to the classic risk. Lookups use Object.hasOwn.

Same-category hooks added: prompt.section, prompt.context,
prompt.attachment, skill.prompt, session.receive, turn.step, and hooks on
process.run / http.fetch / mcp.call (they can answer other mods' calls).

Docs: every first-party mod with function hooks (43 of 43) is risky under
the gate's sets, so the README now says to allow-list each ruflo mod you
use under refuse-risky and points to `claude plugin validate`.

* fix(ruflo-mods): a hook on a risky call is a risky hook (fs.write, env.set, process.spawn, ...) (#3801)

Third review: every $ call is also an event a hook above the caller can
rewrite or answer for every other mod. Under refuse-risky a mod hooking only
fs.write loaded clean and redirected ruflo-mods' own heartbeat write to
.claude/settings.json; one hooking env.set rewrote RUFLO_MODS_OWNS; one on
process.spawn rewrote an allow-listed mod's argv; fs.* loaded too.

Hook risk is now derived: every RISKY_CALLS entry is also a risky event
("can rewrite or answer another mod's <call>"), on top of RISKY_EVENTS
(+ tool.register). Globs match against the union. Tests: fs.write, env.set
and fs.* refused (each fails on the previous head); the harmless-glob
control is clock.*, since command.run is a risky call. README and the
create-mod skill say so.

* fix(ruflo-mods): trust gate treats hooks that forge what other mods read, or replace the conversation, as risky (#3801)

Fourth review: a mod hooking only fs.stat (answer ENOENT for the policy
projection) or fs.read (return an empty observe projection) loaded under
refuse-risky and turned ruflo's own enforced deny into allow; one hooking
session.compact replaced the whole conversation. Added fs.read, fs.stat,
fs.exists, fs.list, settings.read, env.get, session.compact, prompt.edit
and attribution.text to the risky hooks; three tests (each fails on the
previous head). README states the gate is a list over the 2.1.289 engine
API, so an event added later is not judged until listed.

* fix(ruflo-mods): session.root, session.cwd, fs.ancestors and command.register hooks are risky; docs name ruflo-ruos (#3801)

Fifth review: a mod hooking only session.root moved ruflo-mods' project
root, so the enforced policy projection was read from elsewhere and a deny
became allow (the round-4 exploit through another input). Added those
inputs and command.register (consistent with tool.register), with a
root-forger test (fails on the previous head). Derived messages read
"another mod's fs.write, which writes files ..." instead of nesting
parentheses.

Docs: ruflo-ruos hooks only prompt.submit, so it is newly risky under
refuse-risky (#3787); README and PR body said every first-party mod was
already risky via a status file or tool.call, which was false for it.

* test(router): seed Thompson sampler convergence trajectory

* ci: retain one plugin smoke execution and its report

* Register smoke workflow test with its dedicated Node runner

* Prevent same-millisecond hierarchy embedding index collisions
2026-10-19 21:48:15 -04:00
..

ruflo-plugin-creator

Scaffold, validate, and publish new Claude Code plugins with proper structure, MCP tool wiring, AND the canonical plugin contract (ADR + smoke + Compatibility + namespace coordination).

Install

/plugin marketplace add ruvnet/ruflo
/plugin install ruflo-plugin-creator@ruflo

Features

  • Scaffold plugins: Generate complete plugin directory structure in seconds, including the canonical contract (ADR-0001, scripts/smoke.sh, README contract sections)
  • Validate format: Check plugin.json, SKILL.md frontmatter, and file references
  • MCP tool wiring: Auto-discover and wire ruflo MCP tools into skills, with drift warnings for known-broken tool references (embeddings_embed, namespace passed to agentdb_hierarchical-* / agentdb_pattern-*)
  • Marketplace integration: Update marketplace.json for distribution

Commands

  • /create-plugin -- Interactively scaffold a new Claude Code plugin

Skills

  • create-plugin -- Generate plugin structure with skills, commands, agents, ADR-0001, smoke test, and contract sections
  • validate-plugin -- Validate plugin format and catch issues before publishing
  • create-mod -- Scaffold a Claude Code mod (function hooks) from templates/mod/ (scripts/scaffold-mod.mjs <name> <dir>; the manifest is plugin.json.tmpl so validators do not read it as a nested plugin): hybrid hooks.json with a classic fallback that stands down while the module runs, a host adapter over literal $ calls, userConfig, engine-kit tests, tsconfig. Validated with claude plugin validate and claude plugin test (ruflo ADR-404)

Compatibility

  • CLI: pinned to @claude-flow/cli v3.6 major+minor.
  • Verification: bash plugins/ruflo-plugin-creator/scripts/smoke.sh is the contract.

Canonical plugin contract (what gets scaffolded)

Every plugin scaffolded by this plugin inherits the same shape every other plugin in the ruflo family adopted via its own ADR-0001:

plugins/<name>/
├── .claude-plugin/plugin.json     # version, keywords, mcp keyword
├── skills/<skill>/SKILL.md         # name + description + allowed-tools (no wildcards)
├── commands/<command>.md           # name + description + dispatch logic
├── agents/<agent>.md               # name + description + model
├── docs/adrs/0001-<name>-contract.md   # ADR (Proposed) — pinning, namespace, smoke scope
├── scripts/smoke.sh                # Structural contract, ≥8 checks
└── README.md                       # Compatibility + Namespace coordination + Verification + ADR

MCP-tool drift to avoid

Lessons learned from sibling-ADR fixes — the scaffolder warns about these:

Bug Real tool / pattern
embeddings_embed referenced as a tool Use embeddings_generate (the _embed name does not exist)
namespace arg passed to agentdb_hierarchical-* Use tier (working/episodic/semantic), or use memory_* for namespaced reads/writes
namespace arg passed to agentdb_pattern-* Don't pass it — ReasoningBank routes; fallback writes to pattern reserved
pattern and patterns confused They are different reserved namespaces
Hard-coded "19 AgentDB controllers" Defer to agentdb_controllers runtime; real count varies (20 MCP tools, 29 controller names)

Verification

bash plugins/ruflo-plugin-creator/scripts/smoke.sh
# Expected: "11 passed, 0 failed"

Architecture Decisions

  • ruflo-agentdb — namespace convention owner; agentdb_controllers runtime is the canonical controller list
  • ruflo-cost-tracker, ruflo-market-data, ruflo-migrations, ruflo-observability — each fixed namespace-routing bugs the scaffolder now warns about
  • ruflo-knowledge-graph, ruflo-market-data — each fixed embeddings_embed references the scaffolder now warns about

As a mod

Function-hook mod (ADR-445, pattern of ruflo-agentdb), loaded from hooks/hooks.json → hooks/register.ts. This plugin's own tools only search the plugin store, so there is no guard and no userConfig: the mod is a local command and a status file.

  • Command: /creator-mod answers locally with no model call, read-only. Verbs: status; reserved <plugin-dir> lists the command and skill names a plugin already uses (a mod command must not reuse one); check <plugin-dir> summarises its manifest, hooks.json modules and userConfig options. Paths with .. are refused.
  • Status file: .claude-flow/creator-mod/status.json ({version, updatedMs, checked, reserved, lastTarget}).
  • Safety: no network, no process spawning.
  • Scaffold status contract (0.4.1): /create-mod scaffolds hooks/status.ts, which writes .claude-flow/<name>-mod/status.json with the fields the console's Mods section reads: version: 1, summary, modVersion, guard, calls (a number), blocked, lastDenied (only once the mod has refused something), plus updatedMs/startedMs. The scaffold's tests/status.test.ts asserts that shape; the steps are in skills/create-mod/SKILL.md.
  • Test: claude plugin validate plugins/ruflo-plugin-creator, claude plugin test plugins/ruflo-plugin-creator, bash plugins/ruflo-plugin-creator/scripts/smoke.sh. The runner sweeps in templates/mod/tests (the scaffold's own test, which only passes against a scaffolded mod), so expect that one file to fail from the plugin root. node plugins/ruflo-plugin-creator/scripts/test-scaffold-mod.mjs scaffolds a mod from the template and holds it to claude plugin validate and claude plugin test.