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.
- package/.claude/.proven-config-version +1 -0
- package/.claude/helpers/hook-handler.cjs +20 -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 +26 -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 +88 -9
- package/v3/@claude-flow/cli/dist/src/commands/hooks.js +7 -4
- package/v3/@claude-flow/cli/dist/src/commands/index.js +2 -0
- package/v3/@claude-flow/cli/dist/src/commands/init.js +20 -0
- package/v3/@claude-flow/cli/dist/src/commands/memory.js +30 -7
- package/v3/@claude-flow/cli/dist/src/commands/mods.d.ts +13 -0
- package/v3/@claude-flow/cli/dist/src/commands/mods.js +126 -0
- 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 +22 -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/mods/claude-installs.d.ts +29 -0
- package/v3/@claude-flow/cli/dist/src/mods/claude-installs.js +83 -0
- package/v3/@claude-flow/cli/dist/src/mods/install.d.ts +64 -0
- package/v3/@claude-flow/cli/dist/src/mods/install.js +135 -0
- package/v3/@claude-flow/cli/dist/src/mods/policy-projection.d.ts +39 -0
- package/v3/@claude-flow/cli/dist/src/mods/policy-projection.js +65 -0
- package/v3/@claude-flow/cli/dist/src/mods/probe.d.ts +28 -0
- package/v3/@claude-flow/cli/dist/src/mods/probe.js +118 -0
- 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 +42 -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-02T00:19:27.102Z",
|
|
5
|
+
"gitSha": "27982983",
|
|
6
6
|
"catalog": {
|
|
7
|
-
"agents":
|
|
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
|
-
|
|
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;
|
|
@@ -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: ${
|
|
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
|
-
|
|
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) {
|
|
@@ -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
|