claude-flow 3.27.0 → 3.27.1

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.
@@ -15,9 +15,99 @@
15
15
 
16
16
  const path = require('path');
17
17
  const fs = require('fs');
18
+ const os = require('os');
18
19
 
19
20
  const helpersDir = __dirname;
20
21
 
22
+ // Resolve an installed @claude-flow/cli (or ruflo) bin — mirrors
23
+ // statusline-generator.ts's resolveCliBin() candidate list. Used only to
24
+ // spawn the detached funnel-refresh helper below; failures are silent (no
25
+ // candidate found just means the refresh never fires this session).
26
+ //
27
+ // Verifies dist/src/index.js exists alongside bin/cli.js, not just the bin
28
+ // itself — Claude Code's own plugin marketplace mechanism installs by
29
+ // `git clone`/`git pull` with no build step, so `~/.claude/plugins/
30
+ // marketplaces/ruflo` is a SOURCE-ONLY checkout by construction: bin/cli.js
31
+ // is present on disk but importing dist/src/index.js throws
32
+ // ERR_MODULE_NOT_FOUND on every real command (confirmed live — only
33
+ // `--version` happens to survive it, since it reads package.json directly).
34
+ // Without this check, resolveCliBinForHook() picked that doomed candidate
35
+ // first every time and spawnDetachedFunnelRefresh() below had no fallback,
36
+ // so the promo/disclosure row could never populate for any marketplace
37
+ // install, on any OS.
38
+ function resolveCliBinForHook() {
39
+ try {
40
+ const home = os.homedir();
41
+ const cwd = process.cwd();
42
+ const candidates = [
43
+ path.join(home, '.claude', 'plugins', 'marketplaces', 'ruflo', 'bin', 'cli.js'),
44
+ path.join(cwd, 'node_modules', '@claude-flow', 'cli', 'bin', 'cli.js'),
45
+ path.join(cwd, 'node_modules', 'ruflo', 'bin', 'cli.js'),
46
+ path.join(cwd, 'v3', '@claude-flow', 'cli', 'bin', 'cli.js'),
47
+ // helpersDir is .claude/helpers/ inside the package itself when this
48
+ // file is running from a real @claude-flow/cli install (not a project
49
+ // that merely copied the helper) — its bin/ is two levels up.
50
+ path.join(helpersDir, '..', '..', 'bin', 'cli.js'),
51
+ ];
52
+ for (const p of candidates) {
53
+ try {
54
+ if (fs.existsSync(p) && fs.existsSync(path.join(path.dirname(p), '..', 'dist', 'src', 'index.js'))) {
55
+ return p;
56
+ }
57
+ } catch (e) { /* try next candidate */ }
58
+ }
59
+ } catch (e) { /* ignore */ }
60
+ return null;
61
+ }
62
+
63
+ // Fire-and-forget doesn't work when the CALLER is itself a short-lived
64
+ // subprocess (confirmed live: two consecutive statusline renders 5s apart
65
+ // both saw an empty funnel-messages-cache, because the async HTTPS fetch
66
+ // inside a `void refreshRemoteMessages()` call gets killed when the spawning
67
+ // process exits before the request completes). Spawning fully DETACHED +
68
+ // unref'd decouples the refresh's lifetime from this hook's — it keeps
69
+ // running (up to message-transport.ts's own 4s fetch timeout) even after
70
+ // session-restore's own process has already exited, so it actually gets a
71
+ // chance to write the cache. Never awaited here — must not add to
72
+ // SessionStart's own timeout budget.
73
+ //
74
+ // No usable local candidate (resolveCliBinForHook() returned null) falls
75
+ // back to npx: this call is detached/unref'd, so a slower npx cold-start
76
+ // costs nothing perceptible — unlike the statusline's own synchronous
77
+ // render path, where local-first exists purely for per-render latency.
78
+ // `--prefer-offline` avoids a registry round trip for the tarball when
79
+ // already cached while still resolving the current `@latest` version.
80
+ function spawnDetachedHookRefresh(subcommand) {
81
+ try {
82
+ const { spawn } = require('child_process');
83
+ const cliBin = resolveCliBinForHook();
84
+ const cmd = process.platform === 'win32' ? 'npx.cmd' : 'npx';
85
+ const spawnArgs = cliBin
86
+ ? [process.execPath, [cliBin, 'hooks', subcommand, '--quiet']]
87
+ : [cmd, ['--prefer-offline', '@claude-flow/cli', 'hooks', subcommand, '--quiet']];
88
+ const child = spawn(spawnArgs[0], spawnArgs[1], {
89
+ detached: true,
90
+ stdio: 'ignore',
91
+ env: process.env,
92
+ });
93
+ child.unref();
94
+ } catch (e) { /* best-effort only */ }
95
+ }
96
+
97
+ function spawnDetachedFunnelRefresh() {
98
+ spawnDetachedHookRefresh('refresh-funnel');
99
+ }
100
+
101
+ // Same fallback-aware pattern as spawnDetachedFunnelRefresh() above, for
102
+ // ADR-316's co-pilot advisor tip. Safe to call on EVERY session-restore:
103
+ // refresh-advisor's own action checks consent + a 24h TTL BEFORE spending
104
+ // anything, so an unconsented or already-fresh install is a fast no-op file
105
+ // read, never a network call. Never awaited here — must not add to
106
+ // SessionStart's own timeout budget.
107
+ function spawnDetachedAdvisorRefresh() {
108
+ spawnDetachedHookRefresh('refresh-advisor');
109
+ }
110
+
21
111
  // Safe require with stdout suppression - the helper modules have CLI
22
112
  // sections that run unconditionally on require(), so we mute console
23
113
  // during the require to prevent noisy output.
@@ -168,6 +258,57 @@ const handlers = {
168
258
  } else {
169
259
  console.log('[INFO] Router not available, using default routing');
170
260
  }
261
+
262
+ // Rate-limit -> sponsored-capacity nudge (ADR-312/313). Fires here,
263
+ // client-side, BEFORE the API call this prompt would make — so it still
264
+ // reaches the transcript even if that call then fails from the rate
265
+ // limit. Cheap local file reads only; never a network call or a child
266
+ // process, so it cannot add latency to prompt submission.
267
+ try {
268
+ const rlFunnelEnv = process.env.RUFLO_FUNNEL;
269
+ const rlDisabledByEnv = rlFunnelEnv !== undefined && /^(0|false|off|no)$/i.test(String(rlFunnelEnv).trim());
270
+ const rlCiVars = ['CI', 'GITHUB_ACTIONS', 'GITLAB_CI', 'CIRCLECI', 'TRAVIS', 'BUILDKITE', 'JENKINS_URL', 'TEAMCITY_VERSION', 'TF_BUILD'];
271
+ const rlIsCi = rlCiVars.some((v) => {
272
+ const val = process.env[v];
273
+ return val !== undefined && val !== '' && val !== '0' && String(val).toLowerCase() !== 'false';
274
+ });
275
+ const rlHome = path.join(os.homedir(), '.ruflo');
276
+ let rlUserDisabled = false;
277
+ try {
278
+ const rlUserCfg = JSON.parse(fs.readFileSync(path.join(rlHome, 'funnel.json'), 'utf8'));
279
+ rlUserDisabled = !!(rlUserCfg && rlUserCfg.enabled === false);
280
+ } catch (e) { /* absent/malformed = not disabled */ }
281
+ let rlProjectDisabled = false;
282
+ try {
283
+ const rlProjCfg = JSON.parse(fs.readFileSync(path.join(process.cwd(), 'claude-flow.config.json'), 'utf8'));
284
+ rlProjectDisabled = !!(rlProjCfg && rlProjCfg.funnel && rlProjCfg.funnel.enabled === false);
285
+ } catch (e) { /* absent/malformed = not disabled */ }
286
+
287
+ if (!rlDisabledByEnv && !rlIsCi && !rlUserDisabled && !rlProjectDisabled) {
288
+ let rlStatus = null;
289
+ try { rlStatus = JSON.parse(fs.readFileSync(path.join(rlHome, 'rate-limit-status.json'), 'utf8')); } catch (e) { /* not flagged */ }
290
+ let rlIsLimited = false;
291
+ if (rlStatus && rlStatus.limited) {
292
+ if (rlStatus.since) {
293
+ const rlSinceMs = Date.parse(rlStatus.since);
294
+ rlIsLimited = isNaN(rlSinceMs) ? true : (Date.now() - rlSinceMs) < 6 * 60 * 60 * 1000;
295
+ } else {
296
+ rlIsLimited = true;
297
+ }
298
+ }
299
+ if (rlIsLimited) {
300
+ let rlConsented = false;
301
+ try {
302
+ const rlConsentFile = JSON.parse(fs.readFileSync(path.join(rlHome, 'consent.json'), 'utf8'));
303
+ const rlReceipt = rlConsentFile && rlConsentFile['sponsored-downtime'];
304
+ rlConsented = !!(rlReceipt && rlReceipt.granted === true && rlReceipt.at !== null && rlReceipt.policyVersion === 1);
305
+ } catch (e) { /* not consented */ }
306
+ if (!rlConsented) {
307
+ console.log('[COGNITUM] Hit your Claude usage limit? Free sponsored capacity is available at cognitum.one/meta-llm — run: ruflo proxy sponsor-enable --yes');
308
+ }
309
+ }
310
+ }
311
+ } catch (e) { /* nudge must never break the hook */ }
171
312
  },
172
313
 
173
314
  'pre-bash': () => {
@@ -234,6 +375,14 @@ const handlers = {
234
375
  console.log(`[INTELLIGENCE] Loaded ${initResult.nodes} patterns, ${initResult.edges} edges`);
235
376
  }
236
377
  }
378
+ // Warm the funnel message cache once per session (see
379
+ // spawnDetachedFunnelRefresh's doc comment for why this must happen
380
+ // here, detached, rather than as the statusline's own fire-and-forget).
381
+ spawnDetachedFunnelRefresh();
382
+ // ADR-316 co-pilot advisor tip — same detached pattern; cheap no-op
383
+ // when not consented or still within the 24h TTL (see refresh-advisor's
384
+ // own doc comment).
385
+ spawnDetachedAdvisorRefresh();
237
386
  },
238
387
 
239
388
  'session-end': async () => {
@@ -587,6 +587,16 @@ function recordEdit(file, success) {
587
587
  sessionId: sessionGet('sessionId') || null,
588
588
  });
589
589
  fs.appendFileSync(PENDING_PATH, entry + '\n', 'utf-8');
590
+ // Runaway-storage guard: pending-insights is append-only and only drained by
591
+ // consolidation. If it grows past ~512KB (thousands of un-consolidated edits
592
+ // — e.g. the daemon never ran), keep only the most recent 2000 lines so it
593
+ // can never grow unbounded. Cheap (a statSync per edit; rewrite only when over).
594
+ try {
595
+ if (fs.statSync(PENDING_PATH).size > 512 * 1024) {
596
+ const lines = fs.readFileSync(PENDING_PATH, 'utf-8').split('\n').filter(Boolean);
597
+ if (lines.length > 2000) fs.writeFileSync(PENDING_PATH, lines.slice(-2000).join('\n') + '\n', 'utf-8');
598
+ }
599
+ } catch (e) { /* non-fatal */ }
590
600
  }
591
601
 
592
602
  /**
@@ -116,7 +116,20 @@ function resolveCliBinCandidates() {
116
116
  }
117
117
  } catch { /* ignore */ }
118
118
  } catch { /* ignore */ }
119
- return candidates.filter((p) => { try { return fs.existsSync(p); } catch { return false; } });
119
+ return candidates.filter((p) => {
120
+ try {
121
+ if (!fs.existsSync(p)) return false;
122
+ // A candidate's bin/cli.js can exist on disk while its compiled
123
+ // dist/ never got built (Claude Code's own plugin marketplace just
124
+ // git-clones the repo — no install/build step — so every marketplace
125
+ // install is a source-only checkout by construction). Importing
126
+ // dist/src/index.js from bin/cli.js then throws MODULE_NOT_FOUND on
127
+ // every real command; only --version happens to survive it. Check
128
+ // for the compiled entrypoint too so a doomed candidate is skipped
129
+ // up front instead of wasting a spawn-and-fail on every render.
130
+ return fs.existsSync(path.join(path.dirname(p), '..', 'dist', 'src', 'index.js'));
131
+ } catch { return false; }
132
+ });
120
133
  }
121
134
 
122
135
  // Return { fresh, promoFresh, data }. 'fresh' is true only if within the TTL
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-flow",
3
- "version": "3.27.0",
3
+ "version": "3.27.1",
4
4
  "description": "Ruflo - Enterprise AI agent orchestration for Claude Code. Deploy 60+ specialized agents in coordinated swarms with self-learning, fault-tolerant consensus, vector memory, and MCP integration",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "generation": 1,
4
- "generatedAt": "2026-07-14T03:21:11.654Z",
5
- "gitSha": "87212ddb",
4
+ "generatedAt": "2026-07-14T03:48:36.860Z",
5
+ "gitSha": "5ca9ce1b",
6
6
  "catalog": {
7
7
  "agents": 164,
8
8
  "tools": 387,
@@ -139,7 +139,20 @@ function resolveCliBinCandidates() {
139
139
  }
140
140
  } catch { /* ignore */ }
141
141
  } catch { /* ignore */ }
142
- return candidates.filter((p) => { try { return fs.existsSync(p); } catch { return false; } });
142
+ return candidates.filter((p) => {
143
+ try {
144
+ if (!fs.existsSync(p)) return false;
145
+ // A candidate's bin/cli.js can exist on disk while its compiled
146
+ // dist/ never got built (Claude Code's own plugin marketplace just
147
+ // git-clones the repo — no install/build step — so every marketplace
148
+ // install is a source-only checkout by construction). Importing
149
+ // dist/src/index.js from bin/cli.js then throws MODULE_NOT_FOUND on
150
+ // every real command; only --version happens to survive it. Check
151
+ // for the compiled entrypoint too so a doomed candidate is skipped
152
+ // up front instead of wasting a spawn-and-fail on every render.
153
+ return fs.existsSync(path.join(path.dirname(p), '..', 'dist', 'src', 'index.js'));
154
+ } catch { return false; }
155
+ });
143
156
  }
144
157
 
145
158
  // Return { fresh, promoFresh, data }. 'fresh' is true only if within the TTL
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@claude-flow/cli",
3
- "version": "3.27.0",
3
+ "version": "3.27.1",
4
4
  "type": "module",
5
5
  "description": "Ruflo CLI - Enterprise AI agent orchestration with 60+ specialized agents, swarm coordination, MCP server, self-learning hooks, and vector memory for Claude Code",
6
6
  "main": "dist/src/index.js",