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.
- package/.claude/.proven-config-version +1 -0
- package/.claude/helpers/hook-handler.cjs +7 -4
- package/.claude/helpers/memory.cjs +1 -1
- package/.claude/helpers/router.cjs +1 -1
- package/.claude/helpers/session.cjs +1 -1
- package/.claude/proven-config.json +42 -0
- package/.claude-plugin/marketplace.json +16 -1
- package/README.md +1 -53
- package/README.zh-CN.md +1 -53
- package/node_modules/@claude-flow/codex/package.json +1 -1
- package/node_modules/@claude-flow/security/dist/policy/engine.d.ts +2 -6
- package/node_modules/@claude-flow/security/dist/policy/engine.d.ts.map +1 -1
- package/node_modules/@claude-flow/security/dist/policy/engine.js +35 -1
- package/node_modules/@claude-flow/security/dist/policy/engine.js.map +1 -1
- package/node_modules/@claude-flow/security/dist/policy/types.d.ts +18 -0
- package/node_modules/@claude-flow/security/dist/policy/types.d.ts.map +1 -1
- package/node_modules/@claude-flow/security/package.json +1 -1
- package/package.json +2 -2
- package/v3/@claude-flow/cli/README.md +3 -53
- package/v3/@claude-flow/cli/catalog-manifest.json +4 -4
- package/v3/@claude-flow/cli/dist/src/commands/doctor.d.ts +19 -1
- package/v3/@claude-flow/cli/dist/src/commands/doctor.js +69 -8
- package/v3/@claude-flow/cli/dist/src/commands/hooks.js +7 -4
- package/v3/@claude-flow/cli/dist/src/commands/memory.js +30 -7
- package/v3/@claude-flow/cli/dist/src/commands/plugins.js +44 -7
- package/v3/@claude-flow/cli/dist/src/commands/policy.js +5 -2
- package/v3/@claude-flow/cli/dist/src/commands/session.js +128 -21
- package/v3/@claude-flow/cli/dist/src/commands/swarm.js +11 -11
- package/v3/@claude-flow/cli/dist/src/index.js +10 -1
- package/v3/@claude-flow/cli/dist/src/init/executor.js +11 -5
- package/v3/@claude-flow/cli/dist/src/init/helper-companions.d.ts +3 -0
- package/v3/@claude-flow/cli/dist/src/init/helper-companions.js +34 -0
- package/v3/@claude-flow/cli/dist/src/init/helper-integrity.d.ts +21 -0
- package/v3/@claude-flow/cli/dist/src/init/helper-integrity.js +62 -0
- package/v3/@claude-flow/cli/dist/src/init/helper-refresh.d.ts +18 -11
- package/v3/@claude-flow/cli/dist/src/init/helper-refresh.js +56 -13
- package/v3/@claude-flow/cli/dist/src/init/helpers-generator.js +10 -10
- package/v3/@claude-flow/cli/dist/src/mcp-tools/hooks-tools.d.ts +16 -3
- package/v3/@claude-flow/cli/dist/src/mcp-tools/hooks-tools.js +87 -14
- package/v3/@claude-flow/cli/dist/src/mcp-tools/memory-tools.d.ts +7 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/memory-tools.js +43 -6
- package/v3/@claude-flow/cli/dist/src/mcp-tools/session-tools.d.ts +15 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/session-tools.js +236 -50
- package/v3/@claude-flow/cli/dist/src/memory/memory-initializer.js +6 -3
- package/v3/@claude-flow/cli/dist/src/plugins/manager.d.ts +37 -10
- package/v3/@claude-flow/cli/dist/src/plugins/manager.js +106 -20
- package/v3/@claude-flow/cli/dist/src/plugins/trust-policy.d.ts +61 -0
- package/v3/@claude-flow/cli/dist/src/plugins/trust-policy.js +84 -0
- package/v3/@claude-flow/cli/dist/src/services/policy-runtime.d.ts +6 -0
- package/v3/@claude-flow/cli/dist/src/services/policy-runtime.js +27 -2
- package/v3/@claude-flow/cli/package.json +2 -2
|
@@ -2,15 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://cognitum.one/agentic-engineering)
|
|
4
4
|
|
|
5
|
-
<!-- Try Ruflo — the
|
|
6
|
-
[](https://flo.ruv.io/)
|
|
5
|
+
<!-- Try Ruflo — the 3 badges first-time visitors actually act on -->
|
|
7
6
|
[](https://www.npmjs.com/package/ruflo)
|
|
8
7
|
[](https://opensource.org/licenses/MIT)
|
|
9
8
|
[](https://github.com/ruvnet/claude-flow)
|
|
10
9
|
|
|
11
10
|
<!-- Ecosystem strip (collapsed visually with flat-square) -->
|
|
12
|
-
[](https://goal.ruv.io/)
|
|
13
|
-
[](https://goal.ruv.io/agents)
|
|
14
11
|
[](https://github.com/ruvnet/ruvector)
|
|
15
12
|
[](https://github.com/ruvnet/ruflo/blob/main/data/clone-data.proof.json)
|
|
16
13
|
[](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
|
[](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":
|
|
4
|
-
"generatedAt": "2026-
|
|
5
|
-
"gitSha": "
|
|
3
|
+
"generation": 7,
|
|
4
|
+
"generatedAt": "2026-10-01T00:04:08.841Z",
|
|
5
|
+
"gitSha": "0bd8e314",
|
|
6
6
|
"catalog": {
|
|
7
|
-
"agents":
|
|
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
|
-
|
|
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
|
-
|
|
634
|
-
|
|
635
|
-
|
|
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
|
|
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: ${
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
{
|
|
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
|
-
|
|
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) {
|