@claude-flow/cli 3.72.1 → 3.73.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 (39) hide show
  1. package/.claude/helpers/helpers.manifest.json +2 -2
  2. package/README.md +29 -21
  3. package/catalog-manifest.json +2 -2
  4. package/dist/src/commands/agent.js +2 -0
  5. package/dist/src/commands/funnel.d.ts +1 -0
  6. package/dist/src/commands/funnel.js +37 -2
  7. package/dist/src/commands/hooks.js +16 -2
  8. package/dist/src/commands/init.js +70 -16
  9. package/dist/src/commands/mcp.js +15 -0
  10. package/dist/src/commands/neural.js +5 -3
  11. package/dist/src/commands/swarm.js +2 -0
  12. package/dist/src/funnel/activation.d.ts +30 -0
  13. package/dist/src/funnel/activation.js +69 -0
  14. package/dist/src/funnel/events.js +4 -1
  15. package/dist/src/funnel/index.d.ts +2 -1
  16. package/dist/src/funnel/index.js +2 -1
  17. package/dist/src/funnel/message-transport.d.ts +12 -0
  18. package/dist/src/funnel/message-transport.js +23 -1
  19. package/dist/src/funnel/types.d.ts +2 -2
  20. package/dist/src/init/first-win.d.ts +24 -0
  21. package/dist/src/init/first-win.js +97 -0
  22. package/dist/src/mcp-tools/agent-tools.js +2 -0
  23. package/dist/src/mcp-tools/browser-session-tools.d.ts +2 -2
  24. package/dist/src/mcp-tools/browser-session-tools.js +4 -4
  25. package/dist/src/mcp-tools/hooks-tools.js +17 -1
  26. package/dist/src/mcp-tools/neural-tools.js +3 -3
  27. package/dist/src/mcp-tools/swarm-tools.js +2 -0
  28. package/dist/src/missions/schemas.d.ts +117 -117
  29. package/dist/src/mods/command-registry/schema.d.ts +121 -121
  30. package/dist/src/transfer/store/download.d.ts +1 -1
  31. package/dist/src/transfer/store/download.js +22 -21
  32. package/dist/src/transfer/store/publish.d.ts +1 -1
  33. package/dist/src/transfer/store/publish.js +13 -14
  34. package/node_modules/@claude-flow/codex/dist/cli.js +0 -0
  35. package/node_modules/@claude-flow/plugin-agent-federation/dist/bin.js +0 -0
  36. package/node_modules/@claude-flow/plugin-agent-federation/package.json +5 -5
  37. package/node_modules/@claude-flow/security/dist/input-validator.d.ts +6 -6
  38. package/package.json +7 -7
  39. package/plugins/ruflo-console/dist/tui.mjs +153 -153
@@ -0,0 +1,24 @@
1
+ /**
2
+ * The concrete result `ruflo init` ends with (adoption plan item 1): a quick,
3
+ * local snapshot of the user's own project, plus one prompt to paste into
4
+ * Claude Code. Replaces a list of commands with something about *their* repo.
5
+ *
6
+ * Local only: `git` with argument arrays (no shell), a hard timeout per call,
7
+ * no network. Any failure returns null and init prints nothing extra.
8
+ */
9
+ export interface ProjectSnapshot {
10
+ files: number;
11
+ languages: string[];
12
+ hotFile?: {
13
+ path: string;
14
+ changes: number;
15
+ };
16
+ testCommand?: string;
17
+ }
18
+ /** Snapshot of the project at `cwd`, or null when it is not a git repo with code. */
19
+ export declare function snapshotProject(cwd: string, timeoutMs?: number): ProjectSnapshot | null;
20
+ /** The one prompt to paste into Claude Code, specific to this project. */
21
+ export declare function firstPrompt(s: ProjectSnapshot | null): string;
22
+ /** Lines for init's closing summary. Empty when there is nothing useful to say. */
23
+ export declare function snapshotLines(s: ProjectSnapshot | null): string[];
24
+ //# sourceMappingURL=first-win.d.ts.map
@@ -0,0 +1,97 @@
1
+ /**
2
+ * The concrete result `ruflo init` ends with (adoption plan item 1): a quick,
3
+ * local snapshot of the user's own project, plus one prompt to paste into
4
+ * Claude Code. Replaces a list of commands with something about *their* repo.
5
+ *
6
+ * Local only: `git` with argument arrays (no shell), a hard timeout per call,
7
+ * no network. Any failure returns null and init prints nothing extra.
8
+ */
9
+ import { spawnSync } from 'node:child_process';
10
+ import { existsSync, readFileSync } from 'node:fs';
11
+ import { extname, join } from 'node:path';
12
+ const LANGUAGE = {
13
+ '.ts': 'TypeScript', '.tsx': 'TypeScript', '.js': 'JavaScript', '.jsx': 'JavaScript', '.mjs': 'JavaScript', '.cjs': 'JavaScript',
14
+ '.py': 'Python', '.rs': 'Rust', '.go': 'Go', '.java': 'Java', '.kt': 'Kotlin', '.rb': 'Ruby', '.php': 'PHP',
15
+ '.cs': 'C#', '.cpp': 'C++', '.cc': 'C++', '.c': 'C', '.h': 'C', '.swift': 'Swift', '.scala': 'Scala',
16
+ '.vue': 'Vue', '.svelte': 'Svelte', '.dart': 'Dart', '.ex': 'Elixir', '.exs': 'Elixir', '.lua': 'Lua',
17
+ };
18
+ // Files a "riskiest module" suggestion should never point at.
19
+ const NOT_CODE = /(^|\/)(package-lock\.json|pnpm-lock\.yaml|yarn\.lock|Cargo\.lock|go\.sum|CHANGELOG[^/]*|README[^/]*|\.github\/|\.claude\/|\.claude-flow\/|dist\/|build\/|node_modules\/)/i;
20
+ function git(cwd, args, timeoutMs) {
21
+ const r = spawnSync('git', args, { cwd, encoding: 'utf8', timeout: timeoutMs, maxBuffer: 16 * 1024 * 1024, stdio: ['ignore', 'pipe', 'ignore'] });
22
+ return r.status === 0 && typeof r.stdout === 'string' ? r.stdout : null;
23
+ }
24
+ function testCommandFor(cwd) {
25
+ try {
26
+ const pkg = JSON.parse(readFileSync(join(cwd, 'package.json'), 'utf8'));
27
+ const t = pkg.scripts?.test;
28
+ if (t && !/no test specified/i.test(t))
29
+ return 'npm test';
30
+ }
31
+ catch { /* not a node project */ }
32
+ if (existsSync(join(cwd, 'Cargo.toml')))
33
+ return 'cargo test';
34
+ if (existsSync(join(cwd, 'go.mod')))
35
+ return 'go test ./...';
36
+ if (existsSync(join(cwd, 'pyproject.toml')) || existsSync(join(cwd, 'pytest.ini')))
37
+ return 'pytest';
38
+ return undefined;
39
+ }
40
+ /** Snapshot of the project at `cwd`, or null when it is not a git repo with code. */
41
+ export function snapshotProject(cwd, timeoutMs = 2000) {
42
+ try {
43
+ const listed = git(cwd, ['ls-files', '-z'], timeoutMs);
44
+ if (!listed)
45
+ return null;
46
+ const files = listed.split('\0').filter(Boolean);
47
+ if (files.length === 0)
48
+ return null;
49
+ const counts = new Map();
50
+ for (const f of files) {
51
+ const lang = LANGUAGE[extname(f).toLowerCase()];
52
+ if (lang)
53
+ counts.set(lang, (counts.get(lang) ?? 0) + 1);
54
+ }
55
+ const languages = [...counts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 3).map(([l]) => l);
56
+ // Most-changed code file in the last 90 days (bounded to 500 commits).
57
+ let hotFile;
58
+ const log = git(cwd, ['log', '--since=90.days', '--max-count=500', '--name-only', '--pretty=format:'], timeoutMs);
59
+ if (log) {
60
+ const tracked = new Set(files);
61
+ const churn = new Map();
62
+ for (const line of log.split('\n')) {
63
+ const p = line.trim();
64
+ if (!p || NOT_CODE.test(p) || !tracked.has(p) || !LANGUAGE[extname(p).toLowerCase()])
65
+ continue;
66
+ churn.set(p, (churn.get(p) ?? 0) + 1);
67
+ }
68
+ const top = [...churn.entries()].sort((a, b) => b[1] - a[1])[0];
69
+ if (top && top[1] >= 2)
70
+ hotFile = { path: top[0], changes: top[1] };
71
+ }
72
+ return { files: files.length, languages, hotFile, testCommand: testCommandFor(cwd) };
73
+ }
74
+ catch {
75
+ return null;
76
+ }
77
+ }
78
+ /** The one prompt to paste into Claude Code, specific to this project. */
79
+ export function firstPrompt(s) {
80
+ if (s?.hotFile) {
81
+ const verify = s.testCommand ? ` and run \`${s.testCommand}\` to prove it` : '';
82
+ return `Use a ruflo swarm to review ${s.hotFile.path} (the most-changed file here): find the riskiest logic, add tests for it${verify}, and summarise what you changed.`;
83
+ }
84
+ return 'Use a ruflo swarm to review this repo: find the riskiest module, write tests for it, and summarise what you changed.';
85
+ }
86
+ /** Lines for init's closing summary. Empty when there is nothing useful to say. */
87
+ export function snapshotLines(s) {
88
+ if (!s)
89
+ return [];
90
+ const lines = [`${s.files.toLocaleString('en-US')} tracked file${s.files === 1 ? '' : 's'}${s.languages.length ? ` · ${s.languages.join(', ')}` : ''}`];
91
+ if (s.hotFile)
92
+ lines.push(`Most-changed file (90 days): ${s.hotFile.path} (${s.hotFile.changes} commits)`);
93
+ if (s.testCommand)
94
+ lines.push(`Tests: ${s.testCommand}`);
95
+ return lines;
96
+ }
97
+ //# sourceMappingURL=first-win.js.map
@@ -12,6 +12,7 @@ import { validateIdentifier, validateText, validateAgentSpawn } from './validate
12
12
  import { executeAgentTask } from './agent-execute-core.js';
13
13
  import { AGENT_HEALTH_WINDOW, summarizeAgentExecutions } from './agent-health-metrics.js';
14
14
  import { pheromoneAgentEligibility } from './swarm-tools.js';
15
+ import { noteFirstAgentRun } from '../funnel/activation.js';
15
16
  // Storage paths
16
17
  const STORAGE_DIR = '.claude-flow';
17
18
  const AGENT_DIR = 'agents';
@@ -425,6 +426,7 @@ export const agentTools = [
425
426
  catch { /* COW branch is best-effort; agent already registered */ }
426
427
  }
427
428
  // Include deterministic codemod routing info if applicable
429
+ noteFirstAgentRun('cli'); // activation funnel (opt-in; no-op without telemetry consent)
428
430
  const response = {
429
431
  success: true,
430
432
  agentId,
@@ -13,9 +13,9 @@
13
13
  * AgentDB index). They do not inline a replay engine; replay
14
14
  * enumerates trajectory steps and returns them for the caller to dispatch.
15
15
  * - ruvector resolution is local-first: if a locally installed `ruvector`
16
- * package is found (the CLI already depends on ruvector@0.2.27), its bin
16
+ * package is found (the CLI depends on ruvector ^0.3.11), its bin
17
17
  * is spawned directly with `node`; otherwise we fall back to
18
- * `npx -y ruvector@0.2.27`. The previous 0.2.25 pin forced cold npx
18
+ * `npx -y ruvector@0.3.12`. The previous 0.2.25 pin forced cold npx
19
19
  * downloads of a *second* ruvector version — 4 per browser session.
20
20
  * - Best-effort: missing dependencies (no `ruvector`, no `agent-browser`,
21
21
  * no AgentDB controller) degrade gracefully with a structured error
@@ -13,9 +13,9 @@
13
13
  * AgentDB index). They do not inline a replay engine; replay
14
14
  * enumerates trajectory steps and returns them for the caller to dispatch.
15
15
  * - ruvector resolution is local-first: if a locally installed `ruvector`
16
- * package is found (the CLI already depends on ruvector@0.2.27), its bin
16
+ * package is found (the CLI depends on ruvector ^0.3.11), its bin
17
17
  * is spawned directly with `node`; otherwise we fall back to
18
- * `npx -y ruvector@0.2.27`. The previous 0.2.25 pin forced cold npx
18
+ * `npx -y ruvector@0.3.12`. The previous 0.2.25 pin forced cold npx
19
19
  * downloads of a *second* ruvector version — 4 per browser session.
20
20
  * - Best-effort: missing dependencies (no `ruvector`, no `agent-browser`,
21
21
  * no AgentDB controller) degrade gracefully with a structured error
@@ -25,9 +25,9 @@ import { validateIdentifier, validateText } from './validate-input.js';
25
25
  import { browserUrlError } from './browser-argv.js';
26
26
  // Pin matches the version in this package's own dependency tree so the npx
27
27
  // fallback never downloads a second ruvector. Subcommand surface verified
28
- // against ruvector@0.2.27 --help: `rvf create/compact/status/derive/segments`
28
+ // against ruvector@0.3.12 --help (and earlier 0.2.27): `rvf create/compact/status/derive/segments`
29
29
  // and `hooks trajectory-begin/step/end` all exist with the flags used below.
30
- const RUVECTOR_PIN = 'ruvector@0.2.27';
30
+ const RUVECTOR_PIN = 'ruvector@0.3.12';
31
31
  const RVF_DIR_DEFAULT = '.ruflo/browser-sessions';
32
32
  async function shell(cmd, args, opts = {}) {
33
33
  const { execFile } = await import('node:child_process');
@@ -2224,8 +2224,24 @@ export const hooksBuildAgents = {
2224
2224
  return { success: false, error: v.error };
2225
2225
  }
2226
2226
  const outputDir = resolve(getProjectCwd(), params.outputDir || './agents');
2227
+ // validatePath allows absolute paths, so keep the real output dir inside
2228
+ // the real project root (CWE-22/59): configs are only ever project files.
2229
+ let realProjectRoot;
2230
+ try {
2231
+ realProjectRoot = nodeFs.realpathSync(getProjectCwd());
2232
+ }
2233
+ catch {
2234
+ realProjectRoot = getProjectCwd();
2235
+ }
2236
+ if (!isInsideRealRoot(realProjectRoot, outputDir)) {
2237
+ return { success: false, error: 'outputDir must be inside the project directory' };
2238
+ }
2227
2239
  const focus = params.focus || 'all';
2228
- const format = params.format || 'yaml';
2240
+ // format is spliced into each file name; only the two supported formats.
2241
+ const format = params.format === undefined ? 'yaml' : params.format;
2242
+ if (format !== 'yaml' && format !== 'json') {
2243
+ return { success: false, error: 'format must be "yaml" or "json"' };
2244
+ }
2229
2245
  const persist = params.persist !== false; // Default to true
2230
2246
  const agents = [
2231
2247
  { type: 'coder', configFile: join(outputDir, `coder.${format}`), capabilities: ['code-generation', 'refactoring', 'debugging'], optimizations: ['flash-attention', 'token-reduction'] },
@@ -72,7 +72,7 @@ function activeChampionParams() {
72
72
  // command, because mcp-client statically imports this module. ensureEmbeddings()
73
73
  // keeps the exact same tier chain and degraded/fallback semantics, but only
74
74
  // pays the cost when a handler actually needs an embedding.
75
- // Tier 0 (ADR-089): ruvector@0.2.27 bundled ONNX (no sharp dep, fixes ADR-086's
75
+ // Tier 0 (ADR-089): ruvector bundled ONNX (since 0.2.27) (no sharp dep, fixes ADR-086's
76
76
  // silent-fallback bug at source; closes the chain described in ruvnet/ruvector#523).
77
77
  // Tier 1: agentic-flow v3 ReasoningBank (was Tier 1 — broken on darwin-arm64 without sharp)
78
78
  // Tier 2-3: @claude-flow/embeddings
@@ -110,7 +110,7 @@ function ensureEmbeddings() {
110
110
  }
111
111
  }
112
112
  catch { /* not configured — fall through */ }
113
- // Tier 0: ruvector@0.2.27 — bundled all-MiniLM-L6-v2 + parallel worker pool.
113
+ // Tier 0: ruvector (CLI dependency) — bundled all-MiniLM-L6-v2 + parallel worker pool.
114
114
  // Probe with isOnnxAvailable() and verify an actual embed succeeds (avoids
115
115
  // the type-load-success-but-runtime-fails trap from ADR-086). The probe now
116
116
  // runs on first embed request instead of at import time.
@@ -133,7 +133,7 @@ function ensureEmbeddings() {
133
133
  return Array.isArray(v) ? v : Array.from(v);
134
134
  },
135
135
  };
136
- embeddingServiceName = 'ruvector@0.2.27 (bundled all-MiniLM-L6-v2)';
136
+ embeddingServiceName = 'ruvector (bundled all-MiniLM-L6-v2)';
137
137
  }
138
138
  }
139
139
  catch {
@@ -9,6 +9,7 @@ import { join } from 'node:path';
9
9
  import { nullPrototypeRecords, readPreservedJsonStore } from './record-store.js';
10
10
  import { getProjectCwd } from './types.js';
11
11
  import { validateIdentifier } from './validate-input.js';
12
+ import { noteFirstAgentRun } from '../funnel/activation.js';
12
13
  import { createApscState, isApscAgentEligible, normalizeApscConfig, recordApscSignal, } from '../services/pheromone-adaptive.js';
13
14
  // Swarm state persistence
14
15
  const SWARM_DIR = '.claude-flow/swarm';
@@ -283,6 +284,7 @@ export const swarmTools = [
283
284
  const store = loadSwarmStore();
284
285
  store.swarms[swarmId] = swarmState;
285
286
  saveSwarmStore(store);
287
+ noteFirstAgentRun('cli'); // activation funnel (opt-in; no-op without telemetry consent)
286
288
  return {
287
289
  success: true,
288
290
  swarmId,