claude-flow 3.31.2 → 3.31.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-flow",
3
- "version": "3.31.2",
3
+ "version": "3.31.3",
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-15T16:44:05.609Z",
5
- "gitSha": "f00d78ff",
4
+ "generatedAt": "2026-07-15T18:42:59.914Z",
5
+ "gitSha": "8306ad8f",
6
6
  "catalog": {
7
7
  "agents": 164,
8
8
  "tools": 387,
@@ -122,7 +122,16 @@ export class CLI {
122
122
  if (commandPath[0] !== 'init' && commandPath[0] !== 'update') {
123
123
  try {
124
124
  const { autoRefreshHelpersIfStale } = await import('./init/helper-refresh.js');
125
- const r = await autoRefreshHelpersIfStale(process.cwd());
125
+ // alsoRefreshGlobal:true refresh ~/.claude/helpers too, not just
126
+ // <cwd>/.claude/helpers. Fixes the "promo row missing on remote
127
+ // installs" bug where Claude Code's global settings.json falls back
128
+ // to ~/.claude/helpers/statusline.cjs (executor.ts:460-462) and that
129
+ // file was frozen at whatever version was current when the user
130
+ // last ran `ruflo init` — pre-3.31.3 nothing refreshed it, so any
131
+ // helpers change (e.g. the 2026-07-13 Line-3 funnel row addition)
132
+ // never reached existing installs. Same forward-only semver.gte
133
+ // guard applies to the global pass.
134
+ const r = await autoRefreshHelpersIfStale(process.cwd(), { alsoRefreshGlobal: true });
126
135
  if (r.blocked) {
127
136
  // Integrity failure = potential on-disk tampering of hook code. Warn
128
137
  // loudly (not silent) — the existing project helpers were left intact.
@@ -131,6 +140,12 @@ export class CLI {
131
140
  else if (r.refreshed && this.output.isVerbose()) {
132
141
  this.output.printDebug(`Refreshed .claude/helpers (${r.from} → ${r.to})`);
133
142
  }
143
+ if (r.global?.refreshed && this.output.isVerbose()) {
144
+ this.output.printDebug(`Refreshed ~/.claude/helpers (${r.global.from} → ${r.global.to})`);
145
+ }
146
+ else if (r.global?.blocked && r.global.blocked !== r.blocked) {
147
+ this.output.printWarning(`Skipped ~/.claude/helpers auto-refresh — ${r.global.blocked}.`);
148
+ }
134
149
  }
135
150
  catch { /* silent */ }
136
151
  // ADR-177: adopt a signed proven-configuration champion if the package
@@ -9,42 +9,71 @@ export declare const CRITICAL_HELPERS: string[];
9
9
  /** Installed @claude-flow/cli version — the value the helpers are stamped with. */
10
10
  export declare function getInstalledCliVersion(): string;
11
11
  /**
12
- * On CLI startup: if an initialized project's critical helpers are stamped older
13
- * than the installed CLI version, silently re-copy them. Fast path is a single
14
- * stamp read + string compare (sub-ms); the copy runs at most once per version
15
- * bump. Best-effort, never throws. No-op outside a ruflo project (requires an
16
- * existing hook-handler.cjs never creates files in an unrelated directory).
12
+ * On CLI startup, refresh critical helpers if their stamp is older than the
13
+ * installed CLI version. Two passes:
14
+ *
15
+ * 1. **Project pass** `<cwd>/.claude/helpers/`. Always attempted. The
16
+ * original behavior; project statuslines pin to a stamp per repo.
17
+ *
18
+ * 2. **Global pass** — `~/.claude/helpers/`. Opt-in via `alsoRefreshGlobal`.
19
+ * Fixes the "promo row missing on remote installs" bug: `ruflo init`
20
+ * writes helpers to `~/.claude/helpers/` too so Claude Code's global
21
+ * settings.json statusLine can fall back to them (executor.ts:460-462),
22
+ * but nothing ever REFRESHED that global copy — so any install predating
23
+ * a helpers change (e.g. the 2026-07-13 funnel/promo Line-3 addition)
24
+ * stayed frozen at the pre-feature statusline forever, even after `npm
25
+ * i -g @claude-flow/cli@latest`. The global pass fixes that on the next
26
+ * `ruflo <anything>` invocation. Same forward-only `semver.gte` guard
27
+ * protects against downgrade by a stale cached CLI.
28
+ *
29
+ * `alsoRefreshGlobal` defaults FALSE so tests don't touch the developer's
30
+ * real `~/.claude/helpers/`. The real CLI entry (src/index.ts) passes
31
+ * `true` to activate the global pass in production.
32
+ *
33
+ * Best-effort, never throws. No-op for a helpers dir that doesn't already
34
+ * contain a `hook-handler.cjs` — never creates files in an unrelated dir.
17
35
  *
18
36
  * FORWARD-ONLY (never downgrades): refreshing on any mere INEQUALITY, rather
19
37
  * than only when the installed version is semver-NEWER, is a real corruption
20
38
  * vector — confirmed live: a stray/older installed binary (a stale `npx`
21
39
  * cache, a marketplace install lagging behind an unpublished dev-tree fix)
22
- * running `daemon start` (or any command) against THIS project directory
23
- * would see its own older version != the project's newer stamp and silently
24
- * overwrite hand-fixed `hook-handler.cjs`/`intelligence.cjs` with its own
25
- * older, already-superseded bundled copies. Comparing with `semver.gt`
26
- * instead of `!==` makes that impossible: an older or equal installed
27
- * version is always a no-op, regardless of how it got invoked.
40
+ * running `daemon start` (or any command) would see its own older version !=
41
+ * the project's newer stamp and silently overwrite hand-fixed
42
+ * `hook-handler.cjs`/`intelligence.cjs` with its own older, already-superseded
43
+ * bundled copies. Comparing with `semver.gt` instead of `!==` makes that
44
+ * impossible: an older or equal installed version is always a no-op,
45
+ * regardless of how it got invoked.
28
46
  *
29
47
  * `opts` exists for tests ONLY (mirrors daemon-autostart.ts's injectable
30
- * `SpawnDaemonFn` pattern): the real signed-copy path is otherwise coupled to
31
- * THIS repo's actual current `.claude/helpers` + its real Ed25519 signature —
32
- * fine for production (that coupling to the real source IS the point), but
33
- * it means a test exercising that path for real would only pass when this
34
- * repo's manifest happens to be currently re-signed, which is a separately-
35
- * gated, occasionally-stale publish-time step. `sourceDirOverride` +
36
- * `pubkeyPemOverride` let a test build its own tiny, throwaway-keypair-
37
- * signed fixture and get real, deterministic coverage of the verify → hash
38
- * copy logic without depending on that.
48
+ * `SpawnDaemonFn` pattern): the real signed-copy path is otherwise coupled
49
+ * to THIS repo's actual current `.claude/helpers` + its real Ed25519
50
+ * signature — fine for production (that coupling to the real source IS the
51
+ * point), but it means a test exercising that path for real would only pass
52
+ * when this repo's manifest happens to be currently re-signed, which is a
53
+ * separately-gated, occasionally-stale publish-time step. `sourceDirOverride`
54
+ * + `pubkeyPemOverride` let a test build its own tiny, throwaway-keypair-
55
+ * signed fixture and get real, deterministic coverage of the verify → hash
56
+ * copy logic without depending on that.
57
+ *
58
+ * Return shape: the project-pass result is the top-level object (backwards-
59
+ * compat with pre-3.31.3 callers). If the global pass ran, its own result is
60
+ * carried in the optional `global` field.
39
61
  */
40
62
  export declare function autoRefreshHelpersIfStale(cwd: string, opts?: {
41
63
  sourceDirOverride?: string;
42
64
  pubkeyPemOverride?: string;
43
65
  versionOverride?: string;
66
+ alsoRefreshGlobal?: boolean;
44
67
  }): Promise<{
45
68
  refreshed: boolean;
46
69
  from?: string;
47
70
  to?: string;
48
71
  blocked?: string;
72
+ global?: {
73
+ refreshed: boolean;
74
+ from?: string;
75
+ to?: string;
76
+ blocked?: string;
77
+ };
49
78
  }>;
50
79
  //# sourceMappingURL=helper-refresh.d.ts.map
@@ -14,6 +14,7 @@
14
14
  * heavy generators only on the rare fallback path (source dir unresolvable).
15
15
  */
16
16
  import * as fs from 'fs';
17
+ import * as os from 'os';
17
18
  import * as path from 'path';
18
19
  import { fileURLToPath } from 'url';
19
20
  import { createRequire } from 'module';
@@ -230,50 +231,114 @@ async function writeCriticalHelpers(helpersDir, version, opts = {}) {
230
231
  * signed fixture and get real, deterministic coverage of the verify → hash →
231
232
  * copy logic without depending on that.
232
233
  */
234
+ async function refreshOneHelpersDir(helpersDir, version, opts) {
235
+ if (!fs.existsSync(path.join(helpersDir, 'hook-handler.cjs')))
236
+ return { refreshed: false };
237
+ // .LOCKED marker: users developing ruflo itself (or any project with
238
+ // hand-maintained helpers) can place a `.LOCKED` file at
239
+ // `.claude/helpers/.LOCKED` to opt out of auto-refresh entirely. Fixes the
240
+ // observed-live concurrent-session clobber where a sibling Claude Code
241
+ // session running a stale cached CLI would overwrite hand-edited helpers
242
+ // on this repo (CLAUDE.md "Concurrent-session helper corruption"). The
243
+ // existing semver.gte guard below still fires for normal installs — this
244
+ // is the escape hatch for the small set of users editing helpers directly.
245
+ // Applies to whichever dir this call is refreshing (project or global).
246
+ if (fs.existsSync(path.join(helpersDir, '.LOCKED'))) {
247
+ return { refreshed: false, blocked: '.LOCKED marker present — refresh skipped (delete to re-enable)' };
248
+ }
249
+ let stamped = '';
250
+ try {
251
+ stamped = fs.readFileSync(path.join(helpersDir, HELPERS_STAMP_FILE), 'utf-8').trim();
252
+ }
253
+ catch { /* pre-feature: unstamped */ }
254
+ if (stamped === version)
255
+ return { refreshed: false }; // up to date — fast path
256
+ if (stamped && semver.valid(stamped) && semver.valid(version) && semver.gte(stamped, version)) {
257
+ // Stamped version is already >= what this binary reports — refreshing
258
+ // would silently DOWNGRADE the helpers. Skip, untouched.
259
+ return { refreshed: false };
260
+ }
261
+ const res = await writeCriticalHelpers(helpersDir, version, {
262
+ sourceDirOverride: opts.sourceDirOverride,
263
+ pubkeyPemOverride: opts.pubkeyPemOverride,
264
+ });
265
+ if (res.blocked)
266
+ return { refreshed: false, blocked: res.blocked };
267
+ return res.wrote ? { refreshed: true, from: stamped || '(unstamped)', to: version } : { refreshed: false };
268
+ }
269
+ /**
270
+ * On CLI startup, refresh critical helpers if their stamp is older than the
271
+ * installed CLI version. Two passes:
272
+ *
273
+ * 1. **Project pass** — `<cwd>/.claude/helpers/`. Always attempted. The
274
+ * original behavior; project statuslines pin to a stamp per repo.
275
+ *
276
+ * 2. **Global pass** — `~/.claude/helpers/`. Opt-in via `alsoRefreshGlobal`.
277
+ * Fixes the "promo row missing on remote installs" bug: `ruflo init`
278
+ * writes helpers to `~/.claude/helpers/` too so Claude Code's global
279
+ * settings.json statusLine can fall back to them (executor.ts:460-462),
280
+ * but nothing ever REFRESHED that global copy — so any install predating
281
+ * a helpers change (e.g. the 2026-07-13 funnel/promo Line-3 addition)
282
+ * stayed frozen at the pre-feature statusline forever, even after `npm
283
+ * i -g @claude-flow/cli@latest`. The global pass fixes that on the next
284
+ * `ruflo <anything>` invocation. Same forward-only `semver.gte` guard
285
+ * protects against downgrade by a stale cached CLI.
286
+ *
287
+ * `alsoRefreshGlobal` defaults FALSE so tests don't touch the developer's
288
+ * real `~/.claude/helpers/`. The real CLI entry (src/index.ts) passes
289
+ * `true` to activate the global pass in production.
290
+ *
291
+ * Best-effort, never throws. No-op for a helpers dir that doesn't already
292
+ * contain a `hook-handler.cjs` — never creates files in an unrelated dir.
293
+ *
294
+ * FORWARD-ONLY (never downgrades): refreshing on any mere INEQUALITY, rather
295
+ * than only when the installed version is semver-NEWER, is a real corruption
296
+ * vector — confirmed live: a stray/older installed binary (a stale `npx`
297
+ * cache, a marketplace install lagging behind an unpublished dev-tree fix)
298
+ * running `daemon start` (or any command) would see its own older version !=
299
+ * the project's newer stamp and silently overwrite hand-fixed
300
+ * `hook-handler.cjs`/`intelligence.cjs` with its own older, already-superseded
301
+ * bundled copies. Comparing with `semver.gt` instead of `!==` makes that
302
+ * impossible: an older or equal installed version is always a no-op,
303
+ * regardless of how it got invoked.
304
+ *
305
+ * `opts` exists for tests ONLY (mirrors daemon-autostart.ts's injectable
306
+ * `SpawnDaemonFn` pattern): the real signed-copy path is otherwise coupled
307
+ * to THIS repo's actual current `.claude/helpers` + its real Ed25519
308
+ * signature — fine for production (that coupling to the real source IS the
309
+ * point), but it means a test exercising that path for real would only pass
310
+ * when this repo's manifest happens to be currently re-signed, which is a
311
+ * separately-gated, occasionally-stale publish-time step. `sourceDirOverride`
312
+ * + `pubkeyPemOverride` let a test build its own tiny, throwaway-keypair-
313
+ * signed fixture and get real, deterministic coverage of the verify → hash
314
+ * → copy logic without depending on that.
315
+ *
316
+ * Return shape: the project-pass result is the top-level object (backwards-
317
+ * compat with pre-3.31.3 callers). If the global pass ran, its own result is
318
+ * carried in the optional `global` field.
319
+ */
233
320
  export async function autoRefreshHelpersIfStale(cwd, opts = {}) {
234
321
  try {
235
- const helpersDir = path.join(cwd, '.claude', 'helpers');
236
- if (!fs.existsSync(path.join(helpersDir, 'hook-handler.cjs')))
237
- return { refreshed: false };
238
- // .LOCKED marker: users developing ruflo itself (or any project with
239
- // hand-maintained helpers) can place a `.LOCKED` file at
240
- // `.claude/helpers/.LOCKED` to opt this project out of auto-refresh
241
- // entirely. Fixes the observed-live concurrent-session clobber where a
242
- // sibling Claude Code session running a stale cached CLI would overwrite
243
- // hand-edited helpers on this repo (CLAUDE.md "Concurrent-session helper
244
- // corruption"). Existing semver.gte guard below still fires for normal
245
- // installs — this is the escape hatch for the small set of users editing
246
- // helpers directly. Delete the file to re-enable refresh.
247
- if (fs.existsSync(path.join(helpersDir, '.LOCKED'))) {
248
- return { refreshed: false, blocked: '.LOCKED marker present — refresh skipped (delete to re-enable)' };
249
- }
250
- // Also honor an env-level opt-out for CI / release-time scripting that
251
- // knows it doesn't want any writes to helpers this run.
322
+ // Env-level opt-out applies to BOTH project and global passes.
252
323
  if (/^(1|true|on|yes)$/i.test(String(process.env.RUFLO_HELPERS_LOCKED || ''))) {
253
324
  return { refreshed: false, blocked: 'RUFLO_HELPERS_LOCKED env — refresh skipped' };
254
325
  }
255
326
  const version = opts.versionOverride ?? getInstalledCliVersion();
256
- let stamped = '';
257
- try {
258
- stamped = fs.readFileSync(path.join(helpersDir, HELPERS_STAMP_FILE), 'utf-8').trim();
259
- }
260
- catch { /* pre-feature: unstamped */ }
261
- if (stamped === version)
262
- return { refreshed: false }; // up to date fast path
263
- if (stamped && semver.valid(stamped) && semver.valid(version) && semver.gte(stamped, version)) {
264
- // Stamped version is already >= what this binary reports — refreshing
265
- // would silently DOWNGRADE the project's helpers. Skip, untouched.
266
- return { refreshed: false };
327
+ const projectDir = path.join(cwd, '.claude', 'helpers');
328
+ const projectResult = await refreshOneHelpersDir(projectDir, version, opts);
329
+ // Global pass opt-in only; test callers omit alsoRefreshGlobal to avoid
330
+ // touching the developer's real ~/.claude/helpers.
331
+ if (opts.alsoRefreshGlobal) {
332
+ const globalDir = path.join(os.homedir(), '.claude', 'helpers');
333
+ // Skip if project === global (e.g. someone invoked ruflo from $HOME
334
+ // and $HOME happens to be a ruflo project — refreshing twice is
335
+ // redundant AND could second-guess the first pass's result).
336
+ if (path.resolve(globalDir) !== path.resolve(projectDir)) {
337
+ const globalResult = await refreshOneHelpersDir(globalDir, version, opts);
338
+ return { ...projectResult, global: globalResult };
339
+ }
267
340
  }
268
- const res = await writeCriticalHelpers(helpersDir, version, {
269
- sourceDirOverride: opts.sourceDirOverride,
270
- pubkeyPemOverride: opts.pubkeyPemOverride,
271
- });
272
- // A blocked refresh is a SECURITY signal (tampered source/manifest) — surface
273
- // it, don't advance the stamp, and leave the project's existing helpers intact.
274
- if (res.blocked)
275
- return { refreshed: false, blocked: res.blocked };
276
- return res.wrote ? { refreshed: true, from: stamped || '(unstamped)', to: version } : { refreshed: false };
341
+ return projectResult;
277
342
  }
278
343
  catch {
279
344
  return { refreshed: false };
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@claude-flow/cli",
3
- "version": "3.31.2",
3
+ "version": "3.31.3",
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",