claude-flow 3.48.0 → 3.49.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 (51) hide show
  1. package/.claude/.proven-config-version +1 -0
  2. package/.claude/helpers/hook-handler.cjs +7 -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 +16 -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 +69 -8
  23. package/v3/@claude-flow/cli/dist/src/commands/hooks.js +7 -4
  24. package/v3/@claude-flow/cli/dist/src/commands/memory.js +30 -7
  25. package/v3/@claude-flow/cli/dist/src/commands/plugins.js +44 -7
  26. package/v3/@claude-flow/cli/dist/src/commands/policy.js +5 -2
  27. package/v3/@claude-flow/cli/dist/src/commands/session.js +128 -21
  28. package/v3/@claude-flow/cli/dist/src/commands/swarm.js +11 -11
  29. package/v3/@claude-flow/cli/dist/src/index.js +10 -1
  30. package/v3/@claude-flow/cli/dist/src/init/executor.js +11 -5
  31. package/v3/@claude-flow/cli/dist/src/init/helper-companions.d.ts +3 -0
  32. package/v3/@claude-flow/cli/dist/src/init/helper-companions.js +34 -0
  33. package/v3/@claude-flow/cli/dist/src/init/helper-integrity.d.ts +21 -0
  34. package/v3/@claude-flow/cli/dist/src/init/helper-integrity.js +62 -0
  35. package/v3/@claude-flow/cli/dist/src/init/helper-refresh.d.ts +18 -11
  36. package/v3/@claude-flow/cli/dist/src/init/helper-refresh.js +56 -13
  37. package/v3/@claude-flow/cli/dist/src/init/helpers-generator.js +10 -10
  38. package/v3/@claude-flow/cli/dist/src/mcp-tools/hooks-tools.d.ts +16 -3
  39. package/v3/@claude-flow/cli/dist/src/mcp-tools/hooks-tools.js +87 -14
  40. package/v3/@claude-flow/cli/dist/src/mcp-tools/memory-tools.d.ts +7 -0
  41. package/v3/@claude-flow/cli/dist/src/mcp-tools/memory-tools.js +43 -6
  42. package/v3/@claude-flow/cli/dist/src/mcp-tools/session-tools.d.ts +15 -0
  43. package/v3/@claude-flow/cli/dist/src/mcp-tools/session-tools.js +236 -50
  44. package/v3/@claude-flow/cli/dist/src/memory/memory-initializer.js +6 -3
  45. package/v3/@claude-flow/cli/dist/src/plugins/manager.d.ts +37 -10
  46. package/v3/@claude-flow/cli/dist/src/plugins/manager.js +106 -20
  47. package/v3/@claude-flow/cli/dist/src/plugins/trust-policy.d.ts +61 -0
  48. package/v3/@claude-flow/cli/dist/src/plugins/trust-policy.js +84 -0
  49. package/v3/@claude-flow/cli/dist/src/services/policy-runtime.d.ts +6 -0
  50. package/v3/@claude-flow/cli/dist/src/services/policy-runtime.js +27 -2
  51. 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-01T00:04:08.841Z",
5
+ "gitSha": "0bd8e314",
6
6
  "catalog": {
7
- "agents": 167,
7
+ "agents": 175,
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;
@@ -2579,6 +2638,7 @@ export const doctorCommand = {
2579
2638
  checkGitRepo,
2580
2639
  checkConfigFile,
2581
2640
  checkStaleSettingsNpx, // #2448/#2677 — runaway `npx @latest` in settings
2641
+ () => checkHelperIntegrity(), // #3565 — installed signed helpers vs manifest
2582
2642
  checkDaemonStatus,
2583
2643
  checkMemoryDatabase,
2584
2644
  checkMemoryStructuralIntegrity, // #2737 — bounded, native quick_check on every default run
@@ -2619,6 +2679,7 @@ export const doctorCommand = {
2619
2679
  'browser': checkAgentBrowserVersion,
2620
2680
  'config': checkConfigFile,
2621
2681
  'stale-settings': checkStaleSettingsNpx, // #2448
2682
+ 'helpers': () => checkHelperIntegrity(), // #3565
2622
2683
  'daemon': checkDaemonStatus,
2623
2684
  'memory': [
2624
2685
  checkMemoryDatabase, // existing: exists + statable (unchanged)
@@ -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
  ]);
@@ -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) {
@@ -8,6 +8,7 @@
8
8
  import { output } from '../output.js';
9
9
  import { createPluginDiscoveryService, searchPlugins, getPluginSearchSuggestions, getFeaturedPlugins, getOfficialPlugins, } from '../plugins/store/index.js';
10
10
  import { getPluginManager } from '../plugins/manager.js';
11
+ import { DEFAULT_PLUGIN_PERMISSIONS } from '../plugins/trust-policy.js';
11
12
  import { getBulkRatings } from '../services/registry-api.js';
12
13
  // List subcommand - Now uses IPFS-based registry
13
14
  const listCommand = {
@@ -185,18 +186,29 @@ const installCommand = {
185
186
  { name: 'version', short: 'v', type: 'string', description: 'Specific version to install' },
186
187
  { name: 'global', short: 'g', type: 'boolean', description: 'Install globally' },
187
188
  { name: 'dev', short: 'd', type: 'boolean', description: 'Install as dev dependency' },
188
- { name: 'verify', type: 'boolean', description: 'Verify checksum (default: true)', default: true },
189
+ {
190
+ name: 'verify',
191
+ type: 'boolean',
192
+ description: 'Verify before registering (default: true). Registry installs must match the registry sha256 checksum. ' +
193
+ 'Hooks and commands are withheld from a plugin that declares trustLevel "untrusted"/"unverified" or ' +
194
+ `permissions beyond the default set (${DEFAULT_PLUGIN_PERMISSIONS.join(', ')}) unless --trust is given. ` +
195
+ '--no-verify skips all of this.',
196
+ default: true,
197
+ },
198
+ { name: 'trust', type: 'boolean', description: 'Register hooks and commands even if the plugin is untrusted or declares elevated permissions', default: false },
189
199
  { name: 'registry', short: 'r', type: 'string', description: 'Registry to use' },
190
200
  ],
191
201
  examples: [
192
202
  { command: 'claude-flow plugins install -n community-analytics', description: 'Install plugin from IPFS' },
193
203
  { command: 'claude-flow plugins install -n ./my-plugin --dev', description: 'Install local plugin' },
204
+ { command: 'claude-flow plugins install -n ./my-plugin --trust', description: 'Install a local plugin that declares elevated permissions, registering its hooks' },
194
205
  ],
195
206
  action: async (ctx) => {
196
207
  const name = ctx.flags.name;
197
208
  const version = ctx.flags.version || 'latest';
198
209
  const registryName = ctx.flags.registry;
199
210
  const verify = ctx.flags.verify !== false;
211
+ const trust = ctx.flags.trust === true;
200
212
  if (!name) {
201
213
  output.printError('Plugin name is required');
202
214
  return { success: false, exitCode: 1 };
@@ -227,7 +239,7 @@ const installCommand = {
227
239
  if (isLocalPath) {
228
240
  // Install from local path
229
241
  spinner.setText(`Installing from ${name}...`);
230
- result = await manager.installFromLocal(name);
242
+ result = await manager.installFromLocal(name, { verify, trust });
231
243
  }
232
244
  else {
233
245
  // First, try to find in registry for metadata
@@ -242,7 +254,13 @@ const installCommand = {
242
254
  }
243
255
  // Install from npm (since IPFS is demo mode)
244
256
  spinner.setText(`Installing ${name} from npm...`);
245
- result = await manager.installFromNpm(name, version !== 'latest' ? version : undefined);
257
+ result = await manager.installFromNpm(name, version !== 'latest' ? version : undefined, {
258
+ verify,
259
+ trust,
260
+ expectedChecksum: plugin?.checksum,
261
+ registryTrustLevel: plugin?.trustLevel,
262
+ registryPermissions: plugin?.permissions,
263
+ });
246
264
  }
247
265
  if (!result.success) {
248
266
  spinner.fail(`Installation failed: ${result.error}`);
@@ -251,20 +269,39 @@ const installCommand = {
251
269
  const installed = result.plugin;
252
270
  spinner.succeed(`Installed ${installed.name}@${installed.version}`);
253
271
  output.writeln();
272
+ if (result.decision?.verificationSkipped) {
273
+ output.printWarning('Verification skipped (--no-verify): hooks and commands were registered without a trust or checksum check.');
274
+ }
275
+ for (const warning of result.warnings ?? []) {
276
+ output.printWarning(warning);
277
+ }
254
278
  const boxContent = [
255
279
  `Plugin: ${installed.name}`,
256
280
  `Version: ${installed.version}`,
257
281
  `Source: ${installed.source}`,
258
282
  `Path: ${installed.path || 'N/A'}`,
259
283
  ``,
284
+ `Trust: ${installed.trustLevel ?? 'not declared'}`,
285
+ `Permissions: ${installed.permissions?.join(', ') || 'none declared'}`,
286
+ `Verification: ${installed.verification ?? 'n/a'}`,
287
+ `Install scripts: ${installed.source === 'local' ? 'n/a (local link)' : installed.scriptsRun ? 'ran' : 'skipped (use --trust to run)'}`,
288
+ ``,
260
289
  `Hooks registered: ${installed.hooks?.length || 0}`,
261
290
  `Commands added: ${installed.commands?.length || 0}`,
262
291
  ];
263
- if (plugin) {
264
- boxContent.push(`Trust: ${plugin.trustLevel}`);
265
- boxContent.push(`Permissions: ${plugin.permissions.join(', ') || 'none'}`);
266
- }
267
292
  output.printBox(boxContent.join('\n'), 'Installation Complete');
293
+ if (installed.withheld) {
294
+ output.writeln();
295
+ output.printWarning(`Withheld ${installed.withheld.hooks.length} hook(s) and ${installed.withheld.commands.length} command(s) ` +
296
+ `from ${installed.name} because it ${installed.withheld.reasons.join('; ')}.`);
297
+ if (installed.withheld.hooks.length > 0) {
298
+ output.writeln(output.dim(` Hooks not registered: ${installed.withheld.hooks.join(', ')}`));
299
+ }
300
+ if (installed.withheld.commands.length > 0) {
301
+ output.writeln(output.dim(` Commands not added: ${installed.withheld.commands.join(', ')}`));
302
+ }
303
+ output.writeln(output.dim(` To register them, uninstall and reinstall with --trust.`));
304
+ }
268
305
  return { success: true, data: installed };
269
306
  }
270
307
  catch (error) {
@@ -86,8 +86,11 @@ export const policyCommand = {
86
86
  const state = loadPolicyState(root);
87
87
  return print({ receipts: state.receipts });
88
88
  }
89
- if (operation === 'verify')
90
- return print(await verifyPolicyLedger(root));
89
+ if (operation === 'verify') {
90
+ const ledger = await verifyPolicyLedger(root);
91
+ const result = print(ledger);
92
+ return ledger.valid ? result : { ...result, success: false, exitCode: 1 };
93
+ }
91
94
  throw new Error(`unknown policy operation: ${operation}`);
92
95
  }
93
96
  catch (error) {