claude-flow 3.57.0 → 3.57.2

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 (33) hide show
  1. package/.claude/helpers/intelligence.cjs +3 -5
  2. package/.claude/proven-config.json +1 -1
  3. package/README.md +12 -3
  4. package/node_modules/@claude-flow/codex/README.md +9 -9
  5. package/node_modules/@claude-flow/codex/dist/cli.js +0 -0
  6. package/node_modules/@claude-flow/codex/dist/dual-mode/cli.js +3 -3
  7. package/node_modules/@claude-flow/codex/dist/dual-mode/cli.js.map +1 -1
  8. package/node_modules/@claude-flow/plugin-agent-federation/dist/bin.js +0 -0
  9. package/package.json +3 -2
  10. package/v3/@claude-flow/cli/README.md +12 -3
  11. package/v3/@claude-flow/cli/catalog-manifest.json +2 -2
  12. package/v3/@claude-flow/cli/dist/src/commands/hooks.js +20 -1
  13. package/v3/@claude-flow/cli/dist/src/commands/memory.js +25 -1
  14. package/v3/@claude-flow/cli/dist/src/commands/workflow.js +55 -15
  15. package/v3/@claude-flow/cli/dist/src/init/executor.js +9 -5
  16. package/v3/@claude-flow/cli/dist/src/init/helper-companions.d.ts +1 -1
  17. package/v3/@claude-flow/cli/dist/src/init/helper-companions.js +4 -3
  18. package/v3/@claude-flow/cli/dist/src/mcp-tools/hooks-tools.js +9 -3
  19. package/v3/@claude-flow/cli/dist/src/mcp-tools/memory-tools.js +58 -9
  20. package/v3/@claude-flow/cli/dist/src/mcp-tools/workflow-tools.js +2 -1
  21. package/v3/@claude-flow/cli/dist/src/memory/graph-edge-writer.d.ts +16 -4
  22. package/v3/@claude-flow/cli/dist/src/memory/graph-edge-writer.js +19 -7
  23. package/v3/@claude-flow/cli/dist/src/memory/intelligence.js +4 -0
  24. package/v3/@claude-flow/cli/dist/src/memory/memory-initializer.js +12 -5
  25. package/v3/@claude-flow/cli/dist/src/missions/schemas.d.ts +113 -113
  26. package/v3/@claude-flow/cli/dist/src/mods/command-registry/schema.d.ts +193 -193
  27. package/v3/@claude-flow/cli/dist/src/services/memory-backup.d.ts +6 -0
  28. package/v3/@claude-flow/cli/dist/src/services/memory-backup.js +109 -81
  29. package/v3/@claude-flow/cli/dist/src/services/policy-ledger-anchor.d.ts +2 -0
  30. package/v3/@claude-flow/cli/dist/src/services/policy-ledger-anchor.js +4 -4
  31. package/v3/@claude-flow/cli/dist/src/services/policy-runtime.js +54 -3
  32. package/v3/@claude-flow/cli/dist/src/services/worker-daemon.js +4 -0
  33. package/v3/@claude-flow/cli/package.json +5 -4
@@ -10,7 +10,12 @@ export interface BackupOptions {
10
10
  /** Injected epoch millis (tests pass a fixed value; avoids Date.now in logic). */
11
11
  timestamp?: number;
12
12
  verbose?: boolean;
13
+ /** Test seam: inject a fake better-sqlite3-shaped constructor instead of importing the real module. */
14
+ Database?: new (path: string, opts?: Record<string, unknown>) => any;
13
15
  }
16
+ /** Which method actually produced the snapshot — downstream migration tooling
17
+ * should require `native-online-backup` rather than trusting a blanket `backedUp: true`. */
18
+ export type BackupMethod = 'native-online-backup' | 'raw-copy-fallback';
14
19
  export interface BackupResult {
15
20
  backedUp: boolean;
16
21
  path?: string;
@@ -18,6 +23,7 @@ export interface BackupResult {
18
23
  rotatedAway?: string[];
19
24
  gcsUri?: string;
20
25
  skipped?: string;
26
+ method?: BackupMethod;
21
27
  }
22
28
  export declare function defaultMemoryDbPath(cwd?: string): string;
23
29
  export interface ProjectBackupResult extends BackupResult {
@@ -14,7 +14,7 @@
14
14
  */
15
15
  import * as fs from 'fs';
16
16
  import * as path from 'path';
17
- import { readFileMaybeEncrypted } from '../fs-secure.js';
17
+ import { existingFileIsEncrypted, readFileMaybeEncrypted } from '../fs-secure.js';
18
18
  import { loadBetterSqlite3 } from '../memory/shared-sqlite.js';
19
19
  import { resolveMemoryRoot } from '../memory/memory-root.js';
20
20
  export function defaultMemoryDbPath(cwd = process.cwd()) {
@@ -37,12 +37,18 @@ export async function backupProjectMemoryDbs(cwd, opts = {}) {
37
37
  }
38
38
  const successful = backups.filter(result => result.backedUp);
39
39
  const failed = backups.find(result => !result.backedUp && result.skipped !== 'no-db');
40
+ // Only report an aggregate `method` when every successful store agrees — a
41
+ // mixed native/raw-copy result must not collapse to whichever store
42
+ // happened to be first, the exact false-confidence #4287 is about.
43
+ const methods = new Set(successful.map(result => result.method));
44
+ const aggregateMethod = methods.size === 1 ? successful[0]?.method : undefined;
40
45
  return {
41
46
  ...successful[0],
42
47
  backedUp: successful.length > 0 && !failed,
43
48
  sizeBytes: successful.reduce((sum, result) => sum + (result.sizeBytes ?? 0), 0),
44
49
  rotatedAway: successful.flatMap(result => result.rotatedAway ?? []),
45
50
  skipped: failed?.skipped ?? (successful.length === 0 ? 'no-db' : undefined),
51
+ method: aggregateMethod,
46
52
  backups,
47
53
  };
48
54
  }
@@ -63,88 +69,25 @@ function isSnapshotFor(file, dbPath) {
63
69
  return file.startsWith(prefix)
64
70
  && /^\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}-\d{3}Z\.db$/.test(file.slice(prefix.length));
65
71
  }
66
- export async function backupMemoryDb(opts = {}) {
67
- const dbPath = opts.dbPath ?? defaultMemoryDbPath();
68
- if (!dbPath || !fs.existsSync(dbPath))
69
- return { backedUp: false, skipped: 'no-db' };
70
- let Database;
72
+ /**
73
+ * A plain file copy of a live WAL-mode SQLite database is not a consistent
74
+ * snapshot — committed data can live only in the `-wal` sidecar until it's
75
+ * checkpointed back into the main file. We can't open an RFE1-encrypted
76
+ * source natively to run a real `wal_checkpoint` probe (it isn't a SQLite
77
+ * image), so for the raw-copy-fallback path the only signal available is
78
+ * sidecar presence: a non-empty `-wal` file means there are frames that a
79
+ * copy of the main file alone would silently drop. Fail closed on that.
80
+ */
81
+ function hasActiveWal(dbPath) {
71
82
  try {
72
- const mod = 'better-sqlite3';
73
- Database = (await import(mod)).default;
83
+ return fs.statSync(`${dbPath}-wal`).size > 0;
74
84
  }
75
85
  catch {
76
- return { backedUp: false, skipped: 'better-sqlite3 unavailable' };
86
+ return false;
77
87
  }
78
- const destDir = opts.destDir ?? path.join(path.dirname(dbPath), 'backups');
79
- try {
80
- fs.mkdirSync(destDir, { recursive: true });
81
- }
82
- catch { /* */ }
83
- const destPath = path.join(destDir, `${snapshotPrefix(dbPath)}${fileStamp(opts.timestamp ?? Date.now())}.db`);
84
- // WAL-safe online backup: read-only source, consistent snapshot to destPath.
85
- let db;
86
- try {
87
- db = new Database(dbPath, { readonly: true });
88
- await db.backup(destPath);
89
- db.close();
90
- }
91
- catch (e) {
92
- try {
93
- db?.close();
94
- }
95
- catch { /* */ }
96
- // #2798 — under CLAUDE_FLOW_ENCRYPT_AT_REST the source is an RFE1
97
- // blob, not native SQLite, so better-sqlite3's online-backup API
98
- // throws "file is not a database" and the run was silently recorded
99
- // as skipped (users with encryption had ZERO backups behind a green
100
- // status). The online-backup API only exists to avoid tearing a live
101
- // WAL-mode DB; that concern doesn't apply to the encrypted file (sql.js
102
- // rewrites it wholesale per flush), so a plain byte copy is a valid
103
- // snapshot. Fall back to it before giving up.
104
- try {
105
- fs.copyFileSync(dbPath, destPath);
106
- const copiedBytes = fs.statSync(destPath).size;
107
- if (copiedBytes > 0) {
108
- // Fall through to rotation/offsite/return below via a flag path.
109
- // (Mirror the success path's tail by re-using the same code.)
110
- const keep = typeof opts.keep === 'number' && opts.keep > 0 ? opts.keep : 7;
111
- const rotatedAway = [];
112
- try {
113
- const snaps = fs.readdirSync(destDir).filter(f => isSnapshotFor(f, dbPath)).sort();
114
- while (snaps.length > keep) {
115
- const old = snaps.shift();
116
- try {
117
- fs.rmSync(path.join(destDir, old), { force: true });
118
- rotatedAway.push(old);
119
- }
120
- catch { /* */ }
121
- }
122
- }
123
- catch { /* */ }
124
- let gcsUri;
125
- if (opts.gcs) {
126
- try {
127
- const { execFileSync } = await import('child_process');
128
- const dest = opts.gcs.replace(/\/+$/, '') + '/' + path.basename(destPath);
129
- execFileSync('gcloud', ['storage', 'cp', destPath, dest], { stdio: ['ignore', 'ignore', 'inherit'] });
130
- gcsUri = dest;
131
- }
132
- catch { /* offsite failed — local snapshot stands */ }
133
- }
134
- if (opts.verbose) {
135
- console.log(`memory DB backed up (byte-copy, encrypted-at-rest) → ${destPath} (${Math.round(copiedBytes / 1024)} KB)`);
136
- }
137
- return { backedUp: true, path: destPath, sizeBytes: copiedBytes, rotatedAway, gcsUri };
138
- }
139
- }
140
- catch { /* byte-copy also failed — report the original error */ }
141
- return { backedUp: false, skipped: `backup failed: ${e?.message ?? e}` };
142
- }
143
- let sizeBytes = 0;
144
- try {
145
- sizeBytes = fs.statSync(destPath).size;
146
- }
147
- catch { /* */ }
88
+ }
89
+ /** Shared tail: rotation + optional GCS offsite + verbose logging + the success receipt. */
90
+ async function finishBackup(destPath, dbPath, destDir, opts, method, sizeBytes) {
148
91
  // Rotation — ISO-stamped names sort chronologically, so keep the tail.
149
92
  const keep = typeof opts.keep === 'number' && opts.keep > 0 ? opts.keep : 7;
150
93
  const rotatedAway = [];
@@ -172,11 +115,96 @@ export async function backupMemoryDb(opts = {}) {
172
115
  catch { /* offsite failed — local snapshot stands */ }
173
116
  }
174
117
  if (opts.verbose) {
175
- console.log(`memory DB backed up → ${destPath} (${Math.round(sizeBytes / 1024)} KB)` +
118
+ const label = method === 'raw-copy-fallback' ? ' (byte-copy, encrypted-at-rest)' : '';
119
+ console.log(`memory DB backed up${label} → ${destPath} (${Math.round(sizeBytes / 1024)} KB)` +
176
120
  (rotatedAway.length ? `, rotated ${rotatedAway.length} old` : '') +
177
121
  (gcsUri ? `, offsite ${gcsUri}` : ''));
178
122
  }
179
- return { backedUp: true, path: destPath, sizeBytes, rotatedAway, gcsUri };
123
+ return { backedUp: true, path: destPath, sizeBytes, rotatedAway, gcsUri, method };
124
+ }
125
+ export async function backupMemoryDb(opts = {}) {
126
+ const dbPath = opts.dbPath ?? defaultMemoryDbPath();
127
+ if (!dbPath || !fs.existsSync(dbPath))
128
+ return { backedUp: false, skipped: 'no-db' };
129
+ const destDir = opts.destDir ?? path.join(path.dirname(dbPath), 'backups');
130
+ try {
131
+ fs.mkdirSync(destDir, { recursive: true });
132
+ }
133
+ catch { /* */ }
134
+ const destPath = path.join(destDir, `${snapshotPrefix(dbPath)}${fileStamp(opts.timestamp ?? Date.now())}.db`);
135
+ let Database = opts.Database;
136
+ let nativeError = null;
137
+ if (!Database) {
138
+ try {
139
+ const mod = 'better-sqlite3';
140
+ Database = (await import(mod)).default;
141
+ }
142
+ catch {
143
+ nativeError = 'better-sqlite3 unavailable';
144
+ }
145
+ }
146
+ if (!nativeError) {
147
+ // WAL-safe online backup: read-only source, consistent snapshot to destPath.
148
+ let db;
149
+ try {
150
+ db = new Database(dbPath, { readonly: true });
151
+ await db.backup(destPath);
152
+ db.close();
153
+ let sizeBytes = 0;
154
+ try {
155
+ sizeBytes = fs.statSync(destPath).size;
156
+ }
157
+ catch { /* */ }
158
+ return await finishBackup(destPath, dbPath, destDir, opts, 'native-online-backup', sizeBytes);
159
+ }
160
+ catch (e) {
161
+ try {
162
+ db?.close();
163
+ }
164
+ catch { /* */ }
165
+ try {
166
+ fs.rmSync(destPath, { force: true });
167
+ }
168
+ catch { /* a native backup() that throws mid-write can leave a torn destPath */ }
169
+ nativeError = e?.message ?? String(e);
170
+ }
171
+ }
172
+ // #2798 — under CLAUDE_FLOW_ENCRYPT_AT_REST the source is an RFE1 blob, not
173
+ // native SQLite, so better-sqlite3's online-backup API throws "file is not
174
+ // a database" (or isn't even importable on a WASM-only host). The
175
+ // online-backup API exists to avoid tearing a live WAL-mode DB; that
176
+ // concern doesn't apply to the encrypted file (sql.js rewrites it wholesale
177
+ // per flush), so a plain byte copy IS a valid snapshot there.
178
+ //
179
+ // But this fallback must never fire for an ORDINARY (non-encrypted) source
180
+ // just because the native backup threw — SQLITE_BUSY, a disk error, or any
181
+ // other native-backup failure on a perfectly normal database must fail
182
+ // closed, not silently report `backedUp: true` off a raw copy. Positively
183
+ // verify encryption (4-byte RFE1 magic sniff) before ever taking this path;
184
+ // "the native backup threw" is never by itself evidence the source is
185
+ // encrypted.
186
+ if (existingFileIsEncrypted(dbPath) !== true) {
187
+ return { backedUp: false, skipped: `native backup failed: ${nativeError}` };
188
+ }
189
+ // Even for a confirmed-encrypted source, refuse the raw-copy fallback if
190
+ // there's an active WAL — a plain copy of the main file alone would miss
191
+ // uncommitted WAL frames and silently produce an inconsistent snapshot.
192
+ if (hasActiveWal(dbPath)) {
193
+ return { backedUp: false, skipped: 'active WAL present — refusing raw-copy fallback (would be inconsistent)' };
194
+ }
195
+ try {
196
+ fs.copyFileSync(dbPath, destPath);
197
+ const copiedBytes = fs.statSync(destPath).size;
198
+ if (copiedBytes > 0) {
199
+ return await finishBackup(destPath, dbPath, destDir, opts, 'raw-copy-fallback', copiedBytes);
200
+ }
201
+ }
202
+ catch { /* byte-copy also failed — report the original native error below */ }
203
+ try {
204
+ fs.rmSync(destPath, { force: true });
205
+ }
206
+ catch { /* */ }
207
+ return { backedUp: false, skipped: `native backup failed: ${nativeError}` };
180
208
  }
181
209
  /** Verify a snapshot without modifying it, including RFE1 images and WASM-only hosts. */
182
210
  async function verifiedBackupRows(file, Database) {
@@ -32,6 +32,8 @@ export type AnchorAssessment = {
32
32
  };
33
33
  export type AnchorWriter = (file: string, value: unknown) => Promise<void>;
34
34
  export type StreamingAnchorWriter = (file: string, value: AtomicJsonSource) => Promise<void>;
35
+ export declare const ANCHOR_LOG_FILE = "ledger-anchors.json";
36
+ export declare const ANCHOR_MIRROR_FILE = "ledger-anchor-head.json";
35
37
  export declare const anchorLogPath: (projectRoot: string) => string;
36
38
  /**
37
39
  * Directory holding per-project trust material (anchor mirror, HMAC key).
@@ -33,9 +33,9 @@ import { closeSync, constants as fsConstants, fstatSync, existsSync, mkdtempSync
33
33
  import { isAbsolute, join, resolve } from 'node:path';
34
34
  import { homedir, tmpdir, userInfo } from 'node:os';
35
35
  import { AgenticPolicyEngine, canonicalizePolicy } from '@claude-flow/security/policy/index';
36
- const LOG_FILE = 'ledger-anchors.json';
37
- const MIRROR_FILE = 'ledger-anchor-head.json';
38
- export const anchorLogPath = (projectRoot) => join(resolve(projectRoot), '.claude-flow', 'policy', LOG_FILE);
36
+ export const ANCHOR_LOG_FILE = 'ledger-anchors.json';
37
+ export const ANCHOR_MIRROR_FILE = 'ledger-anchor-head.json';
38
+ export const anchorLogPath = (projectRoot) => join(resolve(projectRoot), '.claude-flow', 'policy', ANCHOR_LOG_FILE);
39
39
  /**
40
40
  * The home directory trust material lives under: $HOME when it is an absolute path, else the account's home. An empty or relative
41
41
  * $HOME (an unset variable in a wrapper script, `HOME=. cmd`) would make join() yield a path relative to the working directory and
@@ -71,7 +71,7 @@ export function policyTrustRoot() {
71
71
  }
72
72
  function mirrorPath(projectRoot) {
73
73
  const id = createHash('sha256').update(realpathSync(projectRoot)).digest('hex');
74
- return join(policyTrustRoot(), id, MIRROR_FILE);
74
+ return join(policyTrustRoot(), id, ANCHOR_MIRROR_FILE);
75
75
  }
76
76
  // Duplicated from policy-runtime.ts's RECEIPT_ARCHIVE_FILE rather than
77
77
  // imported, to avoid a circular dependency (policy-runtime.ts imports this
@@ -7,17 +7,18 @@ import { AgenticPolicyEngine, POLICY_STATE_VERSION, createLegacyCompatibleState,
7
7
  import { isMcpCallerAuthEnabled, decodeTokenEnvelope, publicKeyFromHex, verifyInvocationToken, } from '@claude-flow/security/mcp-caller-identity';
8
8
  import { createHash, createHmac, randomBytes, randomUUID, timingSafeEqual } from 'node:crypto';
9
9
  import { execFileSync } from 'node:child_process';
10
- import { closeSync, constants as fsConstants, existsSync, fstatSync, fsyncSync, ftruncateSync, writeSync, lstatSync, mkdirSync, openSync, readFileSync, readSync, readlinkSync, realpathSync, renameSync, unlinkSync, writeFileSync, } from 'node:fs';
10
+ import { closeSync, constants as fsConstants, existsSync, fstatSync, fsyncSync, ftruncateSync, writeSync, lstatSync, mkdirSync, openSync, readFileSync, readSync, readdirSync, readlinkSync, realpathSync, renameSync, unlinkSync, writeFileSync, } from 'node:fs';
11
11
  import { dirname, join, resolve } from 'node:path';
12
12
  import { hostname, userInfo } from 'node:os';
13
13
  import { syncPolicyProjection } from '../mods/policy-projection.js';
14
14
  import { ATOMIC_JSON_WRITER } from './policy-anchor-log.js';
15
15
  import { captureArchiveSnapshot, verifyFullPolicyLedger } from './policy-ledger-audit.js';
16
- import { assessAnchors, policyTrustRoot, recordAnchor } from './policy-ledger-anchor.js';
16
+ import { ANCHOR_LOG_FILE, ANCHOR_MIRROR_FILE, assessAnchors, policyTrustRoot, recordAnchor } from './policy-ledger-anchor.js';
17
17
  const POLICY_DIR = join('.claude-flow', 'policy');
18
18
  const POLICY_FILE = 'state.json';
19
19
  const LOCK_FILE = 'state.lock';
20
20
  const RECEIPT_ARCHIVE_FILE = 'receipts.ledger.jsonl';
21
+ const STATE_ANCHOR_FILE = 'state.anchor.json';
21
22
  const LOCK_WAIT_MS = 5_000;
22
23
  // #3164: without a bound, `state.receipts` grows forever and every
23
24
  // transaction's verify/clone/serialize cost is O(all receipts ever issued) —
@@ -362,11 +363,59 @@ async function writeJsonAtomic(file, value) {
362
363
  }
363
364
  }
364
365
  }
366
+ // #3164: a writer killed between creating its temp file and renaming it
367
+ // (SIGKILL from a caller's timeout, OOM) never reaches writeJsonAtomic's
368
+ // cleanup, so a full-size copy of the file stays behind forever (~250 MB each
369
+ // on a mature ledger). Every writer of these files holds the project's
370
+ // state.lock, so while the lock is held no temp file for them can belong to a
371
+ // write in progress. That invariant has known gaps: released versions before
372
+ // the dead-owner fix take over any lock older than 30 s, even a live owner's
373
+ // (a writer suspended mid-write, e.g. a sleeping laptop). So a temp is also
374
+ // kept while its writer pid is alive on this host, and until it is
375
+ // ORPHAN_TEMP_MIN_AGE_MS old; a skipped orphan is removed by a later call.
376
+ const ORPHAN_TEMP_MIN_AGE_MS = 10 * 60_000;
377
+ const ATOMIC_TEMP = /^(.+)\.(\d+)\.[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.tmp$/;
378
+ const PROJECT_ATOMIC_FILES = new Set([POLICY_FILE, ANCHOR_LOG_FILE]);
379
+ const TRUST_ATOMIC_FILES = new Set([STATE_ANCHOR_FILE, ANCHOR_MIRROR_FILE]);
380
+ function reapOrphanedTemps(dir, files) {
381
+ let names;
382
+ try {
383
+ names = readdirSync(dir);
384
+ }
385
+ catch {
386
+ return;
387
+ }
388
+ const now = Date.now();
389
+ for (const name of names) {
390
+ const match = ATOMIC_TEMP.exec(name);
391
+ if (!match || !files.has(match[1]))
392
+ continue;
393
+ const pid = Number(match[2]);
394
+ if (Number.isSafeInteger(pid) && pid > 0 && isPidAlive(pid))
395
+ continue;
396
+ const file = join(dir, name);
397
+ try {
398
+ const stat = lstatSync(file);
399
+ if (!stat.isFile() || now - stat.mtimeMs < ORPHAN_TEMP_MIN_AGE_MS)
400
+ continue;
401
+ unlinkSync(file);
402
+ }
403
+ catch { /* already gone, or not removable: never fail the caller */ }
404
+ }
405
+ }
406
+ /** Call only while holding the project's state.lock. */
407
+ function reapOrphanedPolicyTemps(projectRoot) {
408
+ reapOrphanedTemps(paths(projectRoot).dir, PROJECT_ATOMIC_FILES);
409
+ try {
410
+ reapOrphanedTemps(dirname(trustPaths(projectRoot).anchor), TRUST_ATOMIC_FILES);
411
+ }
412
+ catch { /* no trust dir yet */ }
413
+ }
365
414
  function trustPaths(projectRoot) {
366
415
  const trustRoot = policyTrustRoot();
367
416
  const projectId = createHash('sha256').update(realpathSync(projectRoot)).digest('hex');
368
417
  const dir = join(trustRoot, projectId);
369
- return { key: join(dir, 'anchor.key'), anchor: join(dir, 'state.anchor.json') };
418
+ return { key: join(dir, 'anchor.key'), anchor: join(dir, STATE_ANCHOR_FILE) };
370
419
  }
371
420
  function trustKey(projectRoot, create) {
372
421
  const { key } = trustPaths(projectRoot);
@@ -531,6 +580,7 @@ export async function withPolicyTransaction(projectRoot, operation, options = {}
531
580
  mkdirDurably(target.dir);
532
581
  const release = await acquireLock(target.lock);
533
582
  try {
583
+ reapOrphanedPolicyTemps(projectRoot);
534
584
  const loaded = loadPolicyState(projectRoot);
535
585
  const engine = AgenticPolicyEngine.fromState(loaded, {
536
586
  signingKey: process.env.CLAUDE_FLOW_POLICY_SIGNING_KEY,
@@ -691,6 +741,7 @@ export async function verifyPolicyLedger(projectRoot = process.cwd(), options =
691
741
  // strings, receipt arrays or maps. The descriptor pins path replacements.
692
742
  const release = await acquireLock(target.lock);
693
743
  try {
744
+ reapOrphanedPolicyTemps(projectRoot);
694
745
  const engine = AgenticPolicyEngine.fromState(loadPolicyState(projectRoot), engineOptions);
695
746
  const outcome = reconcileLedger(projectRoot, engine, options);
696
747
  if (outcome.persist) {
@@ -1699,6 +1699,10 @@ export class WorkerDaemon extends EventEmitter {
1699
1699
  rotatedAway: r.rotatedAway?.length ?? 0,
1700
1700
  gcsUri: r.gcsUri,
1701
1701
  skipped: r.skipped,
1702
+ // Which method actually completed (per-store detail is in `backups`);
1703
+ // migration tooling should require 'native-online-backup', not a
1704
+ // blanket `backedUp: true` — see #4287.
1705
+ method: r.method,
1702
1706
  };
1703
1707
  }
1704
1708
  catch (error) {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@claude-flow/cli",
3
- "version": "3.57.0",
3
+ "version": "3.57.2",
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",
@@ -48,6 +48,7 @@
48
48
  "import": "./dist/src/index.js"
49
49
  },
50
50
  "./dist/*": "./dist/*",
51
+ "./package.json": "./package.json",
51
52
  "./ruvector": {
52
53
  "types": "./dist/src/ruvector/index.d.ts",
53
54
  "import": "./dist/src/ruvector/index.js"
@@ -98,7 +99,7 @@
98
99
  "vitest": "^4.1.0"
99
100
  },
100
101
  "dependencies": {
101
- "@claude-flow/cli-core": "3.7.3",
102
+ "@claude-flow/cli-core": "3.7.5",
102
103
  "@claude-flow/codex": "3.0.5",
103
104
  "@claude-flow/mcp": "3.1.1",
104
105
  "@claude-flow/neural": "3.0.0",
@@ -125,11 +126,11 @@
125
126
  "ws": "^8.21.0",
126
127
  "yaml": "^2.8.0",
127
128
  "zod": "^3.22.0",
128
- "@claude-flow/memory": "3.0.5"
129
+ "@claude-flow/memory": "3.0.6"
129
130
  },
130
131
  "optionalDependencies": {
131
132
  "@agntcy/slim-bindings": "2.0.0-alpha.5",
132
- "@claude-flow/memory": "3.0.5",
133
+ "@claude-flow/memory": "3.0.6",
133
134
  "@metaharness/darwin": "~0.12.0",
134
135
  "@metaharness/flywheel": "~0.1.10",
135
136
  "@metaharness/radio": "~0.1.0",