claude-flow 3.48.0 → 3.50.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/.claude/.proven-config-version +1 -0
  2. package/.claude/helpers/hook-handler.cjs +20 -4
  3. package/.claude/helpers/memory.cjs +1 -1
  4. package/.claude/helpers/router.cjs +1 -1
  5. package/.claude/helpers/session.cjs +1 -1
  6. package/.claude/proven-config.json +42 -0
  7. package/.claude-plugin/marketplace.json +26 -1
  8. package/README.md +1 -53
  9. package/README.zh-CN.md +1 -53
  10. package/node_modules/@claude-flow/codex/package.json +1 -1
  11. package/node_modules/@claude-flow/security/dist/policy/engine.d.ts +2 -6
  12. package/node_modules/@claude-flow/security/dist/policy/engine.d.ts.map +1 -1
  13. package/node_modules/@claude-flow/security/dist/policy/engine.js +35 -1
  14. package/node_modules/@claude-flow/security/dist/policy/engine.js.map +1 -1
  15. package/node_modules/@claude-flow/security/dist/policy/types.d.ts +18 -0
  16. package/node_modules/@claude-flow/security/dist/policy/types.d.ts.map +1 -1
  17. package/node_modules/@claude-flow/security/package.json +1 -1
  18. package/package.json +2 -2
  19. package/v3/@claude-flow/cli/README.md +3 -53
  20. package/v3/@claude-flow/cli/catalog-manifest.json +4 -4
  21. package/v3/@claude-flow/cli/dist/src/commands/doctor.d.ts +19 -1
  22. package/v3/@claude-flow/cli/dist/src/commands/doctor.js +88 -9
  23. package/v3/@claude-flow/cli/dist/src/commands/hooks.js +7 -4
  24. package/v3/@claude-flow/cli/dist/src/commands/index.js +2 -0
  25. package/v3/@claude-flow/cli/dist/src/commands/init.js +20 -0
  26. package/v3/@claude-flow/cli/dist/src/commands/memory.js +30 -7
  27. package/v3/@claude-flow/cli/dist/src/commands/mods.d.ts +13 -0
  28. package/v3/@claude-flow/cli/dist/src/commands/mods.js +126 -0
  29. package/v3/@claude-flow/cli/dist/src/commands/plugins.js +44 -7
  30. package/v3/@claude-flow/cli/dist/src/commands/policy.js +5 -2
  31. package/v3/@claude-flow/cli/dist/src/commands/session.js +128 -21
  32. package/v3/@claude-flow/cli/dist/src/commands/swarm.js +11 -11
  33. package/v3/@claude-flow/cli/dist/src/index.js +10 -1
  34. package/v3/@claude-flow/cli/dist/src/init/executor.js +11 -5
  35. package/v3/@claude-flow/cli/dist/src/init/helper-companions.d.ts +3 -0
  36. package/v3/@claude-flow/cli/dist/src/init/helper-companions.js +34 -0
  37. package/v3/@claude-flow/cli/dist/src/init/helper-integrity.d.ts +21 -0
  38. package/v3/@claude-flow/cli/dist/src/init/helper-integrity.js +62 -0
  39. package/v3/@claude-flow/cli/dist/src/init/helper-refresh.d.ts +18 -11
  40. package/v3/@claude-flow/cli/dist/src/init/helper-refresh.js +56 -13
  41. package/v3/@claude-flow/cli/dist/src/init/helpers-generator.js +22 -10
  42. package/v3/@claude-flow/cli/dist/src/mcp-tools/hooks-tools.d.ts +16 -3
  43. package/v3/@claude-flow/cli/dist/src/mcp-tools/hooks-tools.js +87 -14
  44. package/v3/@claude-flow/cli/dist/src/mcp-tools/memory-tools.d.ts +7 -0
  45. package/v3/@claude-flow/cli/dist/src/mcp-tools/memory-tools.js +43 -6
  46. package/v3/@claude-flow/cli/dist/src/mcp-tools/session-tools.d.ts +15 -0
  47. package/v3/@claude-flow/cli/dist/src/mcp-tools/session-tools.js +236 -50
  48. package/v3/@claude-flow/cli/dist/src/memory/memory-initializer.js +6 -3
  49. package/v3/@claude-flow/cli/dist/src/mods/claude-installs.d.ts +29 -0
  50. package/v3/@claude-flow/cli/dist/src/mods/claude-installs.js +83 -0
  51. package/v3/@claude-flow/cli/dist/src/mods/install.d.ts +64 -0
  52. package/v3/@claude-flow/cli/dist/src/mods/install.js +135 -0
  53. package/v3/@claude-flow/cli/dist/src/mods/policy-projection.d.ts +39 -0
  54. package/v3/@claude-flow/cli/dist/src/mods/policy-projection.js +65 -0
  55. package/v3/@claude-flow/cli/dist/src/mods/probe.d.ts +28 -0
  56. package/v3/@claude-flow/cli/dist/src/mods/probe.js +118 -0
  57. package/v3/@claude-flow/cli/dist/src/plugins/manager.d.ts +37 -10
  58. package/v3/@claude-flow/cli/dist/src/plugins/manager.js +106 -20
  59. package/v3/@claude-flow/cli/dist/src/plugins/trust-policy.d.ts +61 -0
  60. package/v3/@claude-flow/cli/dist/src/plugins/trust-policy.js +84 -0
  61. package/v3/@claude-flow/cli/dist/src/services/policy-runtime.d.ts +6 -0
  62. package/v3/@claude-flow/cli/dist/src/services/policy-runtime.js +42 -2
  63. package/v3/@claude-flow/cli/package.json +2 -2
@@ -2,15 +2,12 @@
2
2
 
3
3
  [![Ruflo Banner](ruflo/assets/ruflo-small.jpeg)](https://cognitum.one/agentic-engineering)
4
4
 
5
- <!-- Try Ruflo — the 4 badges first-time visitors actually act on -->
6
- [![Try the UI Beta — flo.ruv.io](https://img.shields.io/badge/_Try_the_UI_Beta-flo.ruv.io-6366f1?style=for-the-badge&logoColor=white&logo=svelte)](https://flo.ruv.io/)
5
+ <!-- Try Ruflo — the 3 badges first-time visitors actually act on -->
7
6
  [![npm version (ruflo)](https://img.shields.io/npm/v/ruflo?label=npx%20ruflo&style=for-the-badge&logo=npm&color=cb3837)](https://www.npmjs.com/package/ruflo)
8
7
  [![MIT License](https://img.shields.io/badge/License-MIT-yellow?style=for-the-badge)](https://opensource.org/licenses/MIT)
9
8
  [![Star on GitHub](https://img.shields.io/github/stars/ruvnet/claude-flow?style=for-the-badge&logo=github&color=gold)](https://github.com/ruvnet/claude-flow)
10
9
 
11
10
  <!-- Ecosystem strip (collapsed visually with flat-square) -->
12
- [![Goal Planner](https://img.shields.io/badge/_Goal_Planner-goal.ruv.io-8b5cf6?style=flat-square&logoColor=white&logo=react)](https://goal.ruv.io/)
13
- [![Live Agents](https://img.shields.io/badge/_Live_Agents-goal.ruv.io%2Fagents-10b981?style=flat-square&logoColor=white&logo=react)](https://goal.ruv.io/agents)
14
11
  [![🕸️ RuVector Agentic DB](https://img.shields.io/badge/RuVector_Agentic-DB-06b6d4?style=flat-square&logoColor=white&logo=graphql)](https://github.com/ruvnet/ruvector)
15
12
  [![Ecosystem downloads](https://img.shields.io/badge/ecosystem%20downloads-8.1M%2B-blue?style=flat-square&logo=npm)](https://github.com/ruvnet/ruflo/blob/main/data/clone-data.proof.json)
16
13
  [![Git clones (14d)](https://img.shields.io/badge/git%20clones%2014d-106k-blueviolet?style=flat-square&logo=github)](https://github.com/ruvnet/ruflo/blob/main/data/clone-data.ledger.json)
@@ -19,6 +16,8 @@
19
16
 
20
17
  # Ruflo
21
18
 
19
+ [English](README.md) · [简体中文](README.zh-CN.md)
20
+
22
21
  **An agent meta-harness for Claude Code and Codex.**
23
22
 
24
23
  [![RuFlo Explained — build an AI team that plans, remembers, tests, and improves](docs/assets/ruflo-explained/ch14.jpg)](docs/ruflo-explained.md)
@@ -213,55 +212,6 @@ claude mcp add claude-flow -- npx ruflo@latest mcp start
213
212
  | 🛡️ **Security** | AIDefence, input validation, CVE remediation, path traversal prevention |
214
213
  | 🌐 **Agent Federation** | Cross-installation agent collaboration with zero-trust security |
215
214
  | 🔬 **[MetaHarness](docs/metaharness-user-guide.md)** | Audit your AI agent setup before you ship. Grade readiness (1-100), scan tool configs for security issues, snapshot the whole project to catch regressions over time, and find templates that match your repo. `ruflo eject` turns a ruflo project into a standalone agent toolkit with its own name. [Full guide](docs/metaharness-user-guide.md). |
216
- | 💬 **[Web UI Beta](https://flo.ruv.io/)** | Multi-model chat at flo.ruv.io with parallel MCP tool calling and an in-browser WASM tool gallery |
217
- | 🎯 **[RuFlo Research](https://goal.ruv.io/)** | GOAP A\* planner at goal.ruv.io — plain-English goals → executable agent plans, with a live agent dashboard at [/agents](https://goal.ruv.io/agents) |
218
-
219
- <p align="center">
220
- <a href="https://flo.ruv.io/">
221
- <img src="v3/docs/assets/ruVocal.png" alt="RuFlo Web UI executing parallel MCP tool calls at flo.ruv.io — ruflo__memory_store and ruflo__memory_search firing in a single model turn with the 'Step 1 — 2 tools completed' parallel-execution indicator, thinking process panel visible, Qwen 3.6 Max as the active model. Multi-agent AI chat with Model Context Protocol (MCP) tool calling, persistent vector memory via AgentDB + HNSW, swarm coordination, and 6 frontier models including Claude Sonnet 4.6, Gemini 2.5 Pro, and OpenAI through OpenRouter." width="100%" />
222
- </a>
223
- </p>
224
-
225
- ### Web UI (Beta) — self-hostable, hosted demo at [flo.ruv.io](https://flo.ruv.io/)
226
-
227
- **RuFlo's web UI is a multi-model AI chat with built-in Model Context Protocol (MCP) tool calling.** Talk to Qwen, Claude, Gemini, or OpenAI while RuFlo invokes the same MCP tools the CLI uses — agent orchestration, persistent memory, swarm coordination, code review, GitHub ops — directly from chat. No install, no API key needed to try it.
228
-
229
- | | What it is | Why it matters |
230
- |---|------------|----------------|
231
- | 🧠 | **Any model, local or remote** | 6 curated frontier models out-of-the-box — Qwen 3.6 Max (default), Claude Sonnet 4.6, Claude Haiku 4.5, Gemini 2.5 Pro, Gemini 2.5 Flash, OpenAI — via OpenRouter. Add your own: any OpenAI-compatible endpoint (vLLM, Ollama, LM Studio, Together, Groq, self-hosted). |
232
- | 🦾 | **ruvLLM self-learning AI** | Native support for [ruvLLM](https://github.com/ruvnet/RuVector/tree/main/examples/ruvLLM) (lives in `ruvnet/RuVector/examples/ruvLLM`) — RuFlo's self-improving local model layer. Routes to MicroLoRA adapters, learns from your trajectories via SONA, and stays on your machine. Pair with the cloud models or run fully offline. |
233
- | 🛠️ | **~210 tools, ready to call** | 5 server groups (Core, Intelligence, Agents, Memory, DevTools) plus an 18-tool gallery that runs entirely in your browser — works offline. |
234
- | 🔌 | **Bring your own MCP servers** | Click the **MCP (n)** pill in the chat input → *Add Server* and paste any MCP endpoint (HTTP, SSE, or stdio). Your tools join RuFlo's native ones in the same parallel-execution flow. Run a local MCP server on `localhost:3000` and it just works. |
235
- | ⚡ | **Tools run in parallel** | One model response can fire 4–6+ tools at the same time. The UI shows them as cards with a *Step 1 — 2 tools completed* badge so you can see exactly what ran. |
236
- | 💾 | **Memory that sticks** | Say *"remember my favorite color is indigo"* and ask weeks later — RuFlo recalls it. Backed by AgentDB + HNSW vector search (measured ~1.9x–4.7x faster than brute force above the crossover, recall@10 ~0.99). |
237
- | 📘 | **Built-in capabilities tour** | Click the question-mark icon in the sidebar — a "RuFlo Capabilities" modal opens with the full tool list, model strengths, architecture, and keyboard shortcuts. |
238
- | 🏠 | **Self-hostable** | Web UI is shipped as Docker (`ruflo/src/ruvocal/Dockerfile`) with embedded Mongo. Deploy to your own Cloud Run / Fly / Kubernetes / docker-compose. The hosted [flo.ruv.io](https://flo.ruv.io/) demo is one option; running your own is fully supported. |
239
- | 🚀 | **Zero install to try** | Open the hosted URL, pick a model, type a question. That's the whole onboarding. |
240
-
241
- **Try the hosted demo:** [https://flo.ruv.io/](https://flo.ruv.io/) — no account, no API key. **Run your own:** the source lives in [`ruflo/src/ruvocal/`](ruflo/src/ruvocal/) with a multi-stage Dockerfile (`INCLUDE_DB=true` builds in MongoDB) and a `cloudbuild.yaml` for Google Cloud Run. See [ADR-033](ruflo/docs/adr/ADR-033-RUVOCAL-WASM-MCP-INTEGRATION.md) for the architecture and [issue #1689](https://github.com/ruvnet/ruflo/issues/1689) for the roadmap.
242
-
243
- <p align="center">
244
- <a href="https://goal.ruv.io/agents">
245
- <img src="v3/docs/assets/goal.png" alt="goal.ruv.io/agents — RuFlo Goal-Oriented Action Planning (GOAP) UI for autonomous AI agents. Visual goal decomposition, A* search through state spaces, multi-agent task assignment, and live agent telemetry." width="100%" />
246
- </a>
247
- </p>
248
-
249
- ### Goal Planner UI — autonomous agents at [goal.ruv.io](https://goal.ruv.io/)
250
-
251
- **Turn high-level goals into executable agent plans.** `goal.ruv.io` is RuFlo's hosted Goal-Oriented Action Planning (GOAP) front-end — describe an outcome in plain English and watch RuFlo decompose it into preconditions, actions, and an A* path through state space, then dispatch the work to live agents at [`/agents`](https://goal.ruv.io/agents).
252
-
253
- | | What it is | Why it matters |
254
- |---|------------|----------------|
255
- | 🎯 | **Plain-English goals** | Type *"ship the auth refactor with tests and a PR"* — RuFlo extracts the success criteria, the constraints, and the implicit preconditions. No JSON, no DSL. |
256
- | 🧭 | **GOAP A\* planner** | Classic gaming-AI planning ported to software work: state-space search through actions with preconditions/effects to find the shortest viable path. Replans on the fly when state changes. |
257
- | 🤖 | **Live agent dashboard** | [goal.ruv.io/agents](https://goal.ruv.io/agents) shows every spawned agent — role, current step, memory namespace, token budget, status. Click in to inspect trajectories, kill runaway workers, or reassign. |
258
- | 🌳 | **Visual plan tree** | Goals render as collapsible action trees with progress, blocked branches, and rollbacks highlighted. See *exactly* why an agent picked a path — no opaque chain-of-thought. |
259
- | ♻️ | **Adaptive replanning** | When an action fails or new info arrives, the planner re-runs A\* from the current state instead of restarting. Failures become learning, not loops. |
260
- | 🧠 | **Shared memory + SONA** | Plans, trajectories, and outcomes flow into AgentDB. Future plans retrieve past solutions via HNSW — the planner gets smarter with every run. |
261
- | 🔗 | **Wired to MCP tools** | Every action node maps to a tool call (RuFlo's ~210 MCP tools, your custom servers, or shell). The planner schedules them in parallel where the dependency graph allows. |
262
- | 🚀 | **Zero install to try** | Open [goal.ruv.io](https://goal.ruv.io/), describe a goal, watch it run. Source lives in [`v3/goal_ui/`](v3/goal_ui/) — Vite + Supabase, self-hostable. |
263
-
264
- **Try it:** [https://goal.ruv.io/](https://goal.ruv.io/) for goals · [https://goal.ruv.io/agents](https://goal.ruv.io/agents) for live agents. **Run your own:** clone the `goal` branch and `cd v3/goal_ui && npm install && npm run dev`.
265
215
 
266
216
  ### Agent Federation — Slack for Agents
267
217
 
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "generation": 6,
4
- "generatedAt": "2026-09-17T21:28:47.530Z",
5
- "gitSha": "702d1461",
3
+ "generation": 7,
4
+ "generatedAt": "2026-10-02T00:19:27.102Z",
5
+ "gitSha": "27982983",
6
6
  "catalog": {
7
- "agents": 167,
7
+ "agents": 176,
8
8
  "tools": 418,
9
9
  "skills": 34
10
10
  },
@@ -14,7 +14,25 @@ interface HealthCheck {
14
14
  /** ADR-122 Phase 0: probe only the CLI version, without launching a browser. */
15
15
  export declare function evaluateAgentBrowserVersion(rawOutput: string): HealthCheck;
16
16
  export declare function checkAgentBrowserVersion(probe?: () => Promise<string>): Promise<HealthCheck>;
17
- export declare function checkMemoryPersistenceDriver(): Promise<HealthCheck>;
17
+ /**
18
+ * #3565: report signed critical helpers whose on-disk content no longer
19
+ * matches the signed manifest. CLI startup heals such files, so this matters
20
+ * for the paths startup does not heal: `.LOCKED` or `RUFLO_HELPERS_LOCKED`
21
+ * (auto-restore skipped by design), an unverifiable package manifest, and an
22
+ * unresolvable package source.
23
+ */
24
+ export declare function checkHelperIntegrity(opts?: {
25
+ cwd?: string;
26
+ homeDir?: string;
27
+ sourceDirOverride?: string | null;
28
+ pubkeyPemOverride?: string;
29
+ }): Promise<HealthCheck>;
30
+ export type MemoryPersistenceDriverDeps = {
31
+ loadBetterSqlite3?: () => Promise<{
32
+ default: any;
33
+ }>;
34
+ };
35
+ export declare function checkMemoryPersistenceDriver(deps?: MemoryPersistenceDriverDeps): Promise<HealthCheck>;
18
36
  /**
19
37
  * #3392: pure verdict for "does the @claude-flow/memory the CLI loads satisfy
20
38
  * the range the CLI declares?". `npx @claude-flow/cli@latest` reuses one npx
@@ -233,6 +233,69 @@ async function checkStaleSettingsNpx() {
233
233
  fix: 'Re-run `npx ruflo init` to migrate (the v3.13.3+ init migrator regenerates these to local-helper form). On macOS this prevents the process-storm / kernel-panic class reported in #2448.',
234
234
  };
235
235
  }
236
+ /**
237
+ * #3565: report signed critical helpers whose on-disk content no longer
238
+ * matches the signed manifest. CLI startup heals such files, so this matters
239
+ * for the paths startup does not heal: `.LOCKED` or `RUFLO_HELPERS_LOCKED`
240
+ * (auto-restore skipped by design), an unverifiable package manifest, and an
241
+ * unresolvable package source.
242
+ */
243
+ export async function checkHelperIntegrity(opts = {}) {
244
+ const name = 'Helper Integrity (#3565)';
245
+ const { verifyInstalledCriticalHelpers } = await import('../init/helper-integrity.js');
246
+ const { CRITICAL_HELPERS, findPackageHelpersDir } = await import('../init/helper-refresh.js');
247
+ const envLocked = /^(1|true|on|yes)$/i.test(String(process.env.RUFLO_HELPERS_LOCKED || ''));
248
+ const home = opts.homeDir ?? process.env.HOME ?? '';
249
+ const dirs = [
250
+ join(opts.cwd ?? process.cwd(), '.claude', 'helpers'),
251
+ ...(home ? [join(home, '.claude', 'helpers')] : []),
252
+ ].filter((d, i, a) => a.indexOf(d) === i && existsSync(join(d, 'hook-handler.cjs')));
253
+ if (dirs.length === 0) {
254
+ return { name, status: 'pass', message: 'no installed ruflo helpers to verify' };
255
+ }
256
+ const source = opts.sourceDirOverride === undefined ? findPackageHelpersDir() : opts.sourceDirOverride;
257
+ if (!source) {
258
+ return {
259
+ name, status: 'warn',
260
+ message: 'cannot verify installed helpers: the package helper source was not found',
261
+ fix: 'Reinstall @claude-flow/cli',
262
+ };
263
+ }
264
+ const tamperedLines = [];
265
+ const lockedLines = [];
266
+ for (const dir of dirs) {
267
+ const r = verifyInstalledCriticalHelpers(dir, source, CRITICAL_HELPERS, opts.pubkeyPemOverride);
268
+ if (r.blocked) {
269
+ return {
270
+ name, status: 'fail',
271
+ message: `cannot verify installed helpers: ${r.blocked}`,
272
+ fix: 'Reinstall @claude-flow/cli from a trusted source',
273
+ };
274
+ }
275
+ if (r.tampered.length === 0)
276
+ continue;
277
+ const line = `${dir}: ${r.tampered.join(', ')}`;
278
+ if (envLocked || existsSync(join(dir, '.LOCKED')))
279
+ lockedLines.push(line);
280
+ else
281
+ tamperedLines.push(line);
282
+ }
283
+ if (tamperedLines.length > 0) {
284
+ return {
285
+ name, status: 'fail',
286
+ message: `critical helpers do not match the signed manifest — ${tamperedLines.join('; ')}`,
287
+ fix: 'Run any ruflo command to restore verified copies (startup heals them), or `npx ruflo init --force`; then find out what modified them',
288
+ };
289
+ }
290
+ if (lockedLines.length > 0) {
291
+ return {
292
+ name, status: 'warn',
293
+ message: `locally modified helpers (auto-restore disabled by .LOCKED / RUFLO_HELPERS_LOCKED) — ${lockedLines.join('; ')}`,
294
+ fix: 'Expected if you edit helpers deliberately; otherwise remove .LOCKED and run any ruflo command to restore them',
295
+ };
296
+ }
297
+ return { name, status: 'pass', message: `${dirs.length} helper dir(s) match the signed manifest` };
298
+ }
236
299
  async function checkDaemonStatus() {
237
300
  try {
238
301
  const pidFile = '.claude-flow/daemon.pid';
@@ -630,13 +693,9 @@ async function checkNativeAgentDbStructuralIntegrity(dbPath) {
630
693
  catch { /* best-effort */ }
631
694
  }
632
695
  }
633
- // #2968/#3321 — read-only native SQLite capability probe. A skipped
634
- // postinstall can leave the wrapper importable but its binding unavailable.
635
- // Schema size cannot identify the runtime driver or the database's history:
636
- // memory init creates its schema with sql.js even when native is available.
637
- // This probe does not verify schema compatibility or cross-process writes;
638
- // integrity checks and memory store's persistWarning retain their own roles.
639
- export async function checkMemoryPersistenceDriver() {
696
+ export async function checkMemoryPersistenceDriver(deps = {}) {
697
+ const loadBetterSqlite3 = deps.loadBetterSqlite3
698
+ ?? (() => import('better-sqlite3'));
640
699
  const NAME = 'Memory Persistence Driver';
641
700
  const dbPath = await resolveMemoryDbPath();
642
701
  if (!dbPath) {
@@ -655,7 +714,7 @@ export async function checkMemoryPersistenceDriver() {
655
714
  }
656
715
  let Database;
657
716
  try {
658
- Database = (await import('better-sqlite3')).default;
717
+ Database = (await loadBetterSqlite3()).default;
659
718
  }
660
719
  catch {
661
720
  Database = null;
@@ -2247,6 +2306,22 @@ async function checkMetaharness() {
2247
2306
  };
2248
2307
  }
2249
2308
  }
2309
+ // ADR-404 — ruflo as a Claude Code mod (function hooks, early access). One
2310
+ // line in a bare `doctor`: whether the mod is enabled, can load (function
2311
+ // hooks on, not refused by allowManagedModsOnly) and has started; never a
2312
+ // failure, since the classic hooks are the default and the fallback.
2313
+ // `ruflo mods doctor` prints every finding.
2314
+ async function checkMods() {
2315
+ const { probeMods } = await import('../mods/probe.js');
2316
+ const findings = probeMods({ projectRoot: process.cwd() });
2317
+ const enabled = findings.find((f) => f.name === 'ruflo-mods plugin')?.status === 'pass';
2318
+ if (!enabled)
2319
+ return { name: 'ruflo mods (ADR-404)', status: 'pass', message: 'not enabled; classic hooks handle every event' };
2320
+ const warnings = findings.filter((f) => f.status !== 'pass');
2321
+ return warnings.length === 0
2322
+ ? { name: 'ruflo mods (ADR-404)', status: 'pass', message: findings.find((f) => f.name === 'last mod start')?.message ?? 'enabled' }
2323
+ : { name: 'ruflo mods (ADR-404)', status: 'warn', message: warnings.map((f) => `${f.name}: ${f.message}`).join('; '), fix: 'ruflo mods doctor' };
2324
+ }
2250
2325
  // Opt-in @ruvector/typesafe task router (optional peer). `--component typesafe` only.
2251
2326
  async function checkTypesafeRouter() {
2252
2327
  const name = '@ruvector/typesafe router';
@@ -2460,7 +2535,7 @@ export const doctorCommand = {
2460
2535
  {
2461
2536
  name: 'component',
2462
2537
  short: 'c',
2463
- description: 'Check specific component (version, node, npm, config, daemon, memory, api, git, mcp, mcp-overhead, claude, browser, disk, typescript, agentic-flow, encryption, federation, funnel, proxy, auth, typesafe, metaharness)',
2538
+ description: 'Check specific component (version, node, npm, config, daemon, memory, api, git, mcp, mcp-overhead, claude, browser, disk, typescript, agentic-flow, encryption, federation, funnel, proxy, auth, typesafe, mods, metaharness)',
2464
2539
  type: 'string'
2465
2540
  },
2466
2541
  {
@@ -2579,6 +2654,7 @@ export const doctorCommand = {
2579
2654
  checkGitRepo,
2580
2655
  checkConfigFile,
2581
2656
  checkStaleSettingsNpx, // #2448/#2677 — runaway `npx @latest` in settings
2657
+ () => checkHelperIntegrity(), // #3565 — installed signed helpers vs manifest
2582
2658
  checkDaemonStatus,
2583
2659
  checkMemoryDatabase,
2584
2660
  checkMemoryStructuralIntegrity, // #2737 — bounded, native quick_check on every default run
@@ -2601,6 +2677,7 @@ export const doctorCommand = {
2601
2677
  checkFunnel, // ADR-305 — effective funnel state + deciding precedence source
2602
2678
  checkProxySponsoredConsent, // ADR-313 — Meta LLM Proxy sponsored-downtime health
2603
2679
  checkAuth, // ADR-306 — Cognitum identity (warn-only; never fails bare `ruflo doctor`)
2680
+ checkMods, // ADR-404 — Claude Code mod path (warn-only)
2604
2681
  ];
2605
2682
  // #2677: `--component memory` now runs the whole memory-health suite,
2606
2683
  // not just the existence check. Values can be a single check or an
@@ -2619,6 +2696,7 @@ export const doctorCommand = {
2619
2696
  'browser': checkAgentBrowserVersion,
2620
2697
  'config': checkConfigFile,
2621
2698
  'stale-settings': checkStaleSettingsNpx, // #2448
2699
+ 'helpers': () => checkHelperIntegrity(), // #3565
2622
2700
  'daemon': checkDaemonStatus,
2623
2701
  'memory': [
2624
2702
  checkMemoryDatabase, // existing: exists + statable (unchanged)
@@ -2653,6 +2731,7 @@ export const doctorCommand = {
2653
2731
  'proxy': [checkProxySponsoredConsent, checkProxyBinary, checkProxyProcess, checkProxyBindAddress],
2654
2732
  'auth': checkAuth, // ADR-306
2655
2733
  'typesafe': checkTypesafeRouter, // opt-in @ruvector/typesafe task router
2734
+ 'mods': checkMods, // ADR-404 — ruflo as a Claude Code mod
2656
2735
  };
2657
2736
  let checksToRun = allChecks;
2658
2737
  if (component && componentMap[component]) {
@@ -713,7 +713,7 @@ const routeCommand = {
713
713
  ?? 3;
714
714
  const parallel = Math.max(2, parallelRaw);
715
715
  const consensus = ctx.flags.consensus || 'majority-vote';
716
- if (!task) {
716
+ if (!task || !task.trim()) {
717
717
  output.printError('Task description is required. Use --task or -t flag.');
718
718
  return { success: false, exitCode: 1 };
719
719
  }
@@ -782,11 +782,12 @@ const routeCommand = {
782
782
  }
783
783
  }
784
784
  output.writeln();
785
+ const noMatch = result.matched === false;
785
786
  output.printBox([
786
787
  `Agent: ${output.highlight(result.primaryAgent.type)}`,
787
- `Confidence: ${(result.primaryAgent.confidence * 100).toFixed(1)}%`,
788
+ `Confidence: ${(result.primaryAgent.confidence * 100).toFixed(1)}%${noMatch ? ' (no match: default only)' : ''}`,
788
789
  `Reason: ${result.primaryAgent.reason}`
789
- ].join('\n'), 'Primary Recommendation');
790
+ ].join('\n'), noMatch ? 'Default Suggestion (nothing matched)' : 'Primary Recommendation');
790
791
  if (result.alternativeAgents.length > 0) {
791
792
  output.writeln();
792
793
  output.writeln(output.bold('Alternative Agents'));
@@ -803,7 +804,9 @@ const routeCommand = {
803
804
  output.writeln();
804
805
  output.writeln(output.bold('Estimated Metrics'));
805
806
  output.printList([
806
- `Success Probability: ${(result.estimatedMetrics.successProbability * 100).toFixed(1)}%`,
807
+ `Success Probability: ${result.estimatedMetrics.successProbability === null
808
+ ? 'unknown (nothing matched)'
809
+ : `${(result.estimatedMetrics.successProbability * 100).toFixed(1)}%`}`,
807
810
  `Estimated Duration: ${result.estimatedMetrics.estimatedDuration}`,
808
811
  `Complexity: ${result.estimatedMetrics.complexity.toUpperCase()}`
809
812
  ]);
@@ -88,6 +88,8 @@ const commandLoaders = {
88
88
  advisor: () => import('./advisor.js'),
89
89
  // Ruflo verbs in Claude Code's spinnerVerbs rotation (ADR-318)
90
90
  spinner: () => import('./spinner.js'),
91
+ // ruflo as a Claude Code mod: function hooks, early access (ADR-404)
92
+ mods: () => import('./mods.js'),
91
93
  // Ruflo entries in Claude Code's companyAnnouncements startup rotation (ADR-319)
92
94
  announcements: () => import('./announcements.js'),
93
95
  // AGNTCY/Outshift runtime transport selection (ADR-324 §2) — optional,
@@ -775,6 +775,19 @@ const initClaudeAction = async (ctx) => {
775
775
  output.writeln(output.warning(' Embedding initialization skipped (run manually)'));
776
776
  }
777
777
  }
778
+ // ADR-404 — opt into the Claude Code mod path. Additive: the classic
779
+ // hooks just written stay the default and the fallback.
780
+ if (ctx.flags.mods === true) {
781
+ output.writeln();
782
+ try {
783
+ const { installMod } = await import('../mods/install.js');
784
+ const installed = installMod(ctx.cwd, 'local');
785
+ output.writeln(output.success(` ✓ ruflo-mods enabled in ${installed.settingsFile} (early access; run "ruflo mods doctor")`));
786
+ }
787
+ catch (err) {
788
+ output.writeln(output.warning(` ruflo-mods not enabled: ${err instanceof Error ? err.message : String(err)}`));
789
+ }
790
+ }
778
791
  if (!startDaemon && !startAll) {
779
792
  const bin = (process.argv[1] || '').includes('ruflo') ? 'ruflo' : 'claude-flow';
780
793
  output.writeln(output.bold('Next steps:'));
@@ -1500,6 +1513,13 @@ export const initCommand = {
1500
1513
  type: 'boolean',
1501
1514
  default: false,
1502
1515
  },
1516
+ {
1517
+ // ADR-404 — Claude Code function hooks are early access; opt-in only.
1518
+ name: 'mods',
1519
+ description: 'Also enable the ruflo Claude Code mod (function hooks, early access); classic hooks stay as fallback',
1520
+ type: 'boolean',
1521
+ default: false,
1522
+ },
1503
1523
  {
1504
1524
  name: 'with-embeddings',
1505
1525
  description: 'Initialize ONNX embedding subsystem with hyperbolic support',
@@ -11,6 +11,8 @@ import { countSiblingStoreRows } from '../memory/sibling-store.js';
11
11
  import { resolveDbPath } from '../memory/memory-initializer.js';
12
12
  import { existsSync } from 'node:fs';
13
13
  import { siblingAgentDbPath } from '../memory/memory-bridge.js';
14
+ import { validateIdentifier } from '../mcp-tools/validate-input.js';
15
+ import { memoryKeyError } from '../mcp-tools/memory-tools.js';
14
16
  /**
15
17
  * #3228: a miss in one store is not a miss in the memory.
16
18
  *
@@ -21,10 +23,15 @@ import { siblingAgentDbPath } from '../memory/memory-bridge.js';
21
23
  * `found:false`. A confident negative is worse than an error, because nothing
22
24
  * prompts anyone to look further.
23
25
  */
24
- async function warnIfSiblingHasRows(pathFlag) {
26
+ async function warnIfSiblingHasRows(pathFlag, hits) {
25
27
  const unread = await countSiblingStoreRows(resolveDbPath(pathFlag));
26
28
  if (unread && unread.rows > 0) {
27
- output.printWarning(`This read covered one store. ${unread.rows} entries are in ${unread.path} and were not searched. ` +
29
+ // #3566: disclose on hits too. A partial positive ("Found 1 results") invites
30
+ // no second look, so it is the more dangerous case, not the safer one.
31
+ const lead = hits === undefined
32
+ ? 'This read covered one store.'
33
+ : `Partial result: ${hits} ${hits === 1 ? 'match' : 'matches'} came from the store read here.`;
34
+ output.printWarning(`${lead} ${unread.rows} entries are in ${unread.path} and were not searched. ` +
28
35
  `That store is written by the MCP/AgentDB path; read it with --path ${unread.path}.`);
29
36
  }
30
37
  }
@@ -48,7 +55,7 @@ function removalDbTargets(pathFlag) {
48
55
  }
49
56
  // Memory backends
50
57
  const BACKENDS = [
51
- { value: 'agentdb', label: 'AgentDB', hint: 'Vector database with HNSW indexing (150x-12,500x faster)' },
58
+ { value: 'agentdb', label: 'AgentDB', hint: 'Vector database with HNSW indexing' },
52
59
  { value: 'sqlite', label: 'SQLite', hint: 'Lightweight local storage' },
53
60
  { value: 'hybrid', label: 'Hybrid', hint: 'SQLite + AgentDB (recommended)' },
54
61
  { value: 'memory', label: 'In-Memory', hint: 'Fast but non-persistent' }
@@ -182,6 +189,18 @@ const storeCommand = {
182
189
  output.printError('Value is required. Use --value');
183
190
  return { success: false, exitCode: 1 };
184
191
  }
192
+ // #3570: reject a traversal namespace before persisting, as export/purge do.
193
+ const vNs = validateIdentifier(namespace, 'namespace');
194
+ if (!vNs.valid) {
195
+ output.printError(vNs.error);
196
+ return { success: false, exitCode: 1 };
197
+ }
198
+ // #3570 follow-up: the same key rule MCP memory_store enforces.
199
+ const keyError = memoryKeyError(key);
200
+ if (keyError) {
201
+ output.printError(keyError);
202
+ return { success: false, exitCode: 1 };
203
+ }
185
204
  // #2752 MemPoison gate — scan before persist when opted in.
186
205
  // CLI flag ctx.flags.scanContent takes precedence over RUFLO_MEMORY_SCAN_ON_WRITE env var
187
206
  // (ADR-125 §"CLI flag wins" / ADR-130 §env-var-config-precedence — fix for #2794).
@@ -328,6 +347,9 @@ const retrieveCommand = {
328
347
  return { success: false, exitCode: 1, data: { key, found: false } };
329
348
  }
330
349
  const entry = result.entry;
350
+ // #3566: the same key may also live in the sibling store. Goes to stderr,
351
+ // so --value-only / --format json stdout stays parseable.
352
+ await warnIfSiblingHasRows(ctx.flags.path, 1);
331
353
  // #2073: --value-only emits just the raw value (no decoration) for
332
354
  // piping into JSON.parse / jq / other downstream parsers without
333
355
  // any cleanup.
@@ -411,7 +433,7 @@ const searchCommand = {
411
433
  },
412
434
  {
413
435
  name: 'build-hnsw',
414
- description: 'Build/rebuild HNSW index before searching (enables 150x-12,500x speedup)',
436
+ description: 'Build/rebuild HNSW index before searching',
415
437
  type: 'boolean',
416
438
  default: false
417
439
  },
@@ -512,7 +534,6 @@ const searchCommand = {
512
534
  const status = getHNSWStatus();
513
535
  output.printSuccess(`HNSW index built (${status.entryCount} vectors, ${buildTime}ms)`);
514
536
  output.writeln(output.dim(` Dimensions: ${status.dimensions}, Metric: cosine`));
515
- output.writeln(output.dim(` Search speedup: ${status.entryCount > 10000 ? '12,500x' : status.entryCount > 1000 ? '150x' : '10x'}`));
516
537
  }
517
538
  else {
518
539
  output.printWarning('HNSW index not available (install @ruvector/core for acceleration)');
@@ -576,6 +597,7 @@ const searchCommand = {
576
597
  // Pure-keyword mode returns directly; skip the semantic path entirely.
577
598
  if (ctx.flags.format === 'json') {
578
599
  output.printJson({ query, searchType, results: keywordResults, searchTime: '0ms' });
600
+ await warnIfSiblingHasRows(ctx.flags.path, keywordResults.length);
579
601
  return { success: true, data: keywordResults };
580
602
  }
581
603
  output.writeln();
@@ -583,6 +605,7 @@ const searchCommand = {
583
605
  for (const r of keywordResults) {
584
606
  output.writeln(` ${r.key} (${r.namespace}, score=${r.score.toFixed(2)}) — ${r.preview.slice(0, 80)}${r.preview.length > 80 ? '…' : ''}`);
585
607
  }
608
+ await warnIfSiblingHasRows(ctx.flags.path, keywordResults.length);
586
609
  return { success: true, data: keywordResults };
587
610
  }
588
611
  // Hybrid mode: keyword hits will be MERGED after semantic runs below.
@@ -694,6 +717,7 @@ const searchCommand = {
694
717
  }
695
718
  if (ctx.flags.format === 'json') {
696
719
  output.printJson({ query, searchType, results, searchTime: `${searchTimeMs}ms`, ...(smartStats ? { stats: smartStats } : {}) });
720
+ await warnIfSiblingHasRows(ctx.flags.path, results.length);
697
721
  return { success: true, data: results };
698
722
  }
699
723
  // Performance stats
@@ -720,6 +744,7 @@ const searchCommand = {
720
744
  });
721
745
  output.writeln();
722
746
  output.printInfo(`Found ${results.length} results`);
747
+ await warnIfSiblingHasRows(ctx.flags.path, results.length);
723
748
  return { success: true, data: results };
724
749
  }
725
750
  catch (error) {
@@ -1160,8 +1185,6 @@ const statsCommand = {
1160
1185
  output.writeln(output.bold('Embedding'));
1161
1186
  output.printInfo(`Provider info unavailable: ${e instanceof Error ? e.message : String(e)}`);
1162
1187
  }
1163
- output.writeln();
1164
- output.printInfo('V3 Performance: 150x-12,500x faster search with HNSW indexing');
1165
1188
  return { success: true, data: stats };
1166
1189
  }
1167
1190
  catch (error) {
@@ -0,0 +1,13 @@
1
+ /**
2
+ * `ruflo mods` — opt into running ruflo as a Claude Code mod (ADR-404,
3
+ * Claude Code function hooks, early access).
4
+ *
5
+ * install/uninstall edit one settings file and record what they added;
6
+ * status/doctor report what can be known from outside a session; sync-policy
7
+ * rewrites the policy projection the mod's tool check reads. Classic hooks
8
+ * are never removed: they stay the default and the fallback.
9
+ */
10
+ import type { Command } from '../types.js';
11
+ export declare const modsCommand: Command;
12
+ export default modsCommand;
13
+ //# sourceMappingURL=mods.d.ts.map
@@ -0,0 +1,126 @@
1
+ /**
2
+ * `ruflo mods` — opt into running ruflo as a Claude Code mod (ADR-404,
3
+ * Claude Code function hooks, early access).
4
+ *
5
+ * install/uninstall edit one settings file and record what they added;
6
+ * status/doctor report what can be known from outside a session; sync-policy
7
+ * rewrites the policy projection the mod's tool check reads. Classic hooks
8
+ * are never removed: they stay the default and the fallback.
9
+ */
10
+ import { output } from '../output.js';
11
+ import { installMod, uninstallMod } from '../mods/install.js';
12
+ import { probeMods } from '../mods/probe.js';
13
+ function projectRoot(ctx) {
14
+ return ctx.flags.projectRoot ?? ctx.flags['project-root'] ?? ctx.cwd ?? process.cwd();
15
+ }
16
+ function printFindings(findings) {
17
+ for (const f of findings) {
18
+ const mark = f.status === 'pass' ? output.success('✓') : f.status === 'warn' ? output.warning('!') : output.error('✗');
19
+ output.writeln(`${mark} ${f.name}: ${f.message}`);
20
+ if (f.fix && f.status !== 'pass')
21
+ output.writeln(output.dim(` fix: ${f.fix}`));
22
+ }
23
+ }
24
+ const rootOption = { name: 'project-root', description: 'Project root (default: current directory)', type: 'string' };
25
+ const installSub = {
26
+ name: 'install',
27
+ description: 'Enable the ruflo-mods plugin for this project (opt-in; classic hooks stay as fallback)',
28
+ options: [
29
+ rootOption,
30
+ { name: 'scope', description: 'local (.claude/settings.local.json, default) | project (.claude/settings.json)', type: 'string', default: 'local' },
31
+ { name: 'dry-run', description: 'Show the settings that would be written', type: 'boolean', default: false },
32
+ ],
33
+ action: async (ctx) => {
34
+ const scope = ctx.flags.scope ?? 'local';
35
+ if (scope !== 'local' && scope !== 'project') {
36
+ output.printError(`--scope must be local or project, got ${scope}`);
37
+ return { success: false, exitCode: 1 };
38
+ }
39
+ const root = projectRoot(ctx);
40
+ const result = installMod(root, scope, (ctx.flags.dryRun === true || ctx.flags['dry-run'] === true));
41
+ if (result.dryRun) {
42
+ output.writeln(`Would write ${result.settingsFile}:`);
43
+ output.printJson(result.next);
44
+ return { success: true, data: result };
45
+ }
46
+ await syncPolicy(root, true);
47
+ output.printSuccess(`ruflo-mods enabled in ${result.settingsFile}${result.backup ? ` (backup: ${result.backup})` : ''}`);
48
+ output.writeln('Restart Claude Code, then run /ruflo-mods in a session to see what the mod owns.');
49
+ output.writeln(output.dim('Early access: Claude Code loads it only where function hooks are on. Run `ruflo mods doctor`.'));
50
+ return { success: true, data: result };
51
+ },
52
+ };
53
+ const uninstallSub = {
54
+ name: 'uninstall',
55
+ description: 'Remove what `ruflo mods install` added (classic hooks take every event back)',
56
+ options: [rootOption, { name: 'dry-run', description: 'Show what would be removed', type: 'boolean', default: false }],
57
+ action: async (ctx) => {
58
+ const result = uninstallMod(projectRoot(ctx), (ctx.flags.dryRun === true || ctx.flags['dry-run'] === true));
59
+ if (!result.removed) {
60
+ output.printWarning('No install record (.claude-flow/mods/install.json): nothing ruflo added to remove.');
61
+ return { success: true, data: result };
62
+ }
63
+ output.printSuccess(`${result.dryRun ? 'Would remove' : 'Removed'} ruflo-mods from ${result.settingsFile}`);
64
+ return { success: true, data: result };
65
+ },
66
+ };
67
+ function findingsCommand(name, description) {
68
+ return {
69
+ name,
70
+ description,
71
+ options: [rootOption, { name: 'json', description: 'Output as JSON', type: 'boolean', default: false }],
72
+ action: async (ctx) => {
73
+ const findings = probeMods({ projectRoot: projectRoot(ctx) });
74
+ if (ctx.flags.json)
75
+ output.printJson(findings);
76
+ else
77
+ printFindings(findings);
78
+ const failed = findings.some((f) => f.status === 'fail');
79
+ // status reports; doctor gates (warnings are expected while early access is off).
80
+ return { success: !failed, exitCode: name === 'doctor' && failed ? 1 : 0, data: findings };
81
+ },
82
+ };
83
+ }
84
+ async function syncPolicy(root, quiet) {
85
+ try {
86
+ const { loadPolicyState } = await import('../services/policy-runtime.js');
87
+ const { syncPolicyProjection } = await import('../mods/policy-projection.js');
88
+ const result = syncPolicyProjection(root, loadPolicyState(root));
89
+ if (!quiet || result.action !== 'unchanged')
90
+ output.writeln(`policy projection: ${result.action} (${result.path})`);
91
+ return true;
92
+ }
93
+ catch (error) {
94
+ output.printWarning(`policy projection not synced: ${error.message}`);
95
+ return false;
96
+ }
97
+ }
98
+ const syncPolicySub = {
99
+ name: 'sync-policy',
100
+ description: 'Rewrite the Claude Code policy projection from .claude-flow/policy/state.json',
101
+ options: [rootOption],
102
+ action: async (ctx) => {
103
+ const ok = await syncPolicy(projectRoot(ctx), false);
104
+ return { success: ok, exitCode: ok ? 0 : 1 };
105
+ },
106
+ };
107
+ const statusSub = findingsCommand('status', 'Show whether the mod is enabled, can load, and what it owns');
108
+ export const modsCommand = {
109
+ name: 'mods',
110
+ description: 'Run ruflo as a Claude Code mod (function hooks, early access, ADR-404)',
111
+ subcommands: [
112
+ installSub,
113
+ uninstallSub,
114
+ statusSub,
115
+ findingsCommand('doctor', 'Check the mod path; exits 1 only on a failure'),
116
+ syncPolicySub,
117
+ ],
118
+ examples: [
119
+ { command: 'ruflo mods install', description: 'Enable for this project (settings.local.json)' },
120
+ { command: 'ruflo mods doctor', description: 'Function hooks on? Refused by policy? Handshake supported?' },
121
+ { command: 'ruflo mods uninstall', description: 'Remove only what install added' },
122
+ ],
123
+ action: statusSub.action,
124
+ };
125
+ export default modsCommand;
126
+ //# sourceMappingURL=mods.js.map