claude-flow 3.47.1 → 3.49.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 (84) hide show
  1. package/.claude/.proven-config-version +1 -0
  2. package/.claude/helpers/auto-memory-hook.mjs +6 -2
  3. package/.claude/helpers/hook-handler.cjs +7 -4
  4. package/.claude/helpers/learning-service.mjs +4 -2
  5. package/.claude/helpers/memory.cjs +1 -1
  6. package/.claude/helpers/metrics-db.mjs +4 -2
  7. package/.claude/helpers/router.cjs +1 -1
  8. package/.claude/helpers/session.cjs +1 -1
  9. package/.claude/proven-config.json +42 -0
  10. package/.claude-plugin/marketplace.json +16 -1
  11. package/README.md +1 -53
  12. package/README.zh-CN.md +1 -53
  13. package/node_modules/@claude-flow/codex/package.json +1 -1
  14. package/node_modules/@claude-flow/mcp/dist/tool-registry.d.ts.map +1 -1
  15. package/node_modules/@claude-flow/mcp/dist/tool-registry.js +11 -3
  16. package/node_modules/@claude-flow/mcp/dist/tool-registry.js.map +1 -1
  17. package/node_modules/@claude-flow/mcp/package.json +6 -3
  18. package/node_modules/@claude-flow/plugin-agent-federation/package.json +3 -3
  19. package/node_modules/@claude-flow/security/dist/policy/engine.d.ts +2 -6
  20. package/node_modules/@claude-flow/security/dist/policy/engine.d.ts.map +1 -1
  21. package/node_modules/@claude-flow/security/dist/policy/engine.js +35 -1
  22. package/node_modules/@claude-flow/security/dist/policy/engine.js.map +1 -1
  23. package/node_modules/@claude-flow/security/dist/policy/types.d.ts +18 -0
  24. package/node_modules/@claude-flow/security/dist/policy/types.d.ts.map +1 -1
  25. package/node_modules/@claude-flow/security/package.json +2 -3
  26. package/package.json +7 -7
  27. package/v3/@claude-flow/cli/README.md +1 -53
  28. package/v3/@claude-flow/cli/bin/cli.js +4 -1
  29. package/v3/@claude-flow/cli/catalog-manifest.json +4 -4
  30. package/v3/@claude-flow/cli/dist/src/commands/analyze.js +2 -2
  31. package/v3/@claude-flow/cli/dist/src/commands/config.js +5 -7
  32. package/v3/@claude-flow/cli/dist/src/commands/doctor.d.ts +19 -1
  33. package/v3/@claude-flow/cli/dist/src/commands/doctor.js +69 -8
  34. package/v3/@claude-flow/cli/dist/src/commands/hooks.js +22 -9
  35. package/v3/@claude-flow/cli/dist/src/commands/memory.js +119 -34
  36. package/v3/@claude-flow/cli/dist/src/commands/metaharness.js +1 -0
  37. package/v3/@claude-flow/cli/dist/src/commands/plugins.js +44 -7
  38. package/v3/@claude-flow/cli/dist/src/commands/policy.js +5 -2
  39. package/v3/@claude-flow/cli/dist/src/commands/security.js +113 -55
  40. package/v3/@claude-flow/cli/dist/src/commands/session.js +132 -25
  41. package/v3/@claude-flow/cli/dist/src/commands/swarm.js +11 -11
  42. package/v3/@claude-flow/cli/dist/src/commands/task.js +5 -4
  43. package/v3/@claude-flow/cli/dist/src/index.js +10 -1
  44. package/v3/@claude-flow/cli/dist/src/init/executor.js +11 -5
  45. package/v3/@claude-flow/cli/dist/src/init/helper-companions.d.ts +3 -0
  46. package/v3/@claude-flow/cli/dist/src/init/helper-companions.js +34 -0
  47. package/v3/@claude-flow/cli/dist/src/init/helper-integrity.d.ts +21 -0
  48. package/v3/@claude-flow/cli/dist/src/init/helper-integrity.js +62 -0
  49. package/v3/@claude-flow/cli/dist/src/init/helper-refresh.d.ts +18 -11
  50. package/v3/@claude-flow/cli/dist/src/init/helper-refresh.js +56 -13
  51. package/v3/@claude-flow/cli/dist/src/init/helpers-generator.js +14 -12
  52. package/v3/@claude-flow/cli/dist/src/mcp-server.js +10 -3
  53. package/v3/@claude-flow/cli/dist/src/mcp-tools/agentdb-tools.js +29 -62
  54. package/v3/@claude-flow/cli/dist/src/mcp-tools/hooks-tools.d.ts +16 -7
  55. package/v3/@claude-flow/cli/dist/src/mcp-tools/hooks-tools.js +226 -51
  56. package/v3/@claude-flow/cli/dist/src/mcp-tools/memory-tools.d.ts +7 -0
  57. package/v3/@claude-flow/cli/dist/src/mcp-tools/memory-tools.js +131 -40
  58. package/v3/@claude-flow/cli/dist/src/mcp-tools/policy-enforcer.d.ts +8 -1
  59. package/v3/@claude-flow/cli/dist/src/mcp-tools/policy-enforcer.js +80 -8
  60. package/v3/@claude-flow/cli/dist/src/mcp-tools/session-tools.d.ts +15 -0
  61. package/v3/@claude-flow/cli/dist/src/mcp-tools/session-tools.js +270 -58
  62. package/v3/@claude-flow/cli/dist/src/memory/feedback-patterns.d.ts +13 -0
  63. package/v3/@claude-flow/cli/dist/src/memory/feedback-patterns.js +14 -0
  64. package/v3/@claude-flow/cli/dist/src/memory/live-memory-row.d.ts +3 -0
  65. package/v3/@claude-flow/cli/dist/src/memory/live-memory-row.js +5 -0
  66. package/v3/@claude-flow/cli/dist/src/memory/memory-bridge.d.ts +11 -20
  67. package/v3/@claude-flow/cli/dist/src/memory/memory-bridge.js +348 -134
  68. package/v3/@claude-flow/cli/dist/src/memory/memory-initializer.d.ts +9 -1
  69. package/v3/@claude-flow/cli/dist/src/memory/memory-initializer.js +47 -34
  70. package/v3/@claude-flow/cli/dist/src/plugins/manager.d.ts +37 -10
  71. package/v3/@claude-flow/cli/dist/src/plugins/manager.js +106 -20
  72. package/v3/@claude-flow/cli/dist/src/plugins/trust-policy.d.ts +61 -0
  73. package/v3/@claude-flow/cli/dist/src/plugins/trust-policy.js +84 -0
  74. package/v3/@claude-flow/cli/dist/src/ruvector/graph-analyzer.js +4 -1
  75. package/v3/@claude-flow/cli/dist/src/services/config-file-manager.js +30 -8
  76. package/v3/@claude-flow/cli/dist/src/services/memory-backup.d.ts +1 -1
  77. package/v3/@claude-flow/cli/dist/src/services/memory-backup.js +67 -27
  78. package/v3/@claude-flow/cli/dist/src/services/policy-runtime.d.ts +6 -0
  79. package/v3/@claude-flow/cli/dist/src/services/policy-runtime.js +27 -2
  80. package/v3/@claude-flow/cli/dist/src/services/worker-daemon.d.ts +2 -2
  81. package/v3/@claude-flow/cli/dist/src/services/worker-daemon.js +23 -14
  82. package/v3/@claude-flow/cli/package.json +9 -9
  83. package/v3/@claude-flow/guidance/package.json +5 -6
  84. package/v3/@claude-flow/shared/package.json +6 -3
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Plugin install trust policy (#3557).
3
+ *
4
+ * `plugins install --verify` used to be parsed and never read, and a plugin's
5
+ * declared `trustLevel`/`permissions` were neither recorded nor enforced.
6
+ * These pure helpers decide what an install may register.
7
+ */
8
+ import { createHash } from 'crypto';
9
+ /**
10
+ * Permissions a plugin may declare and still have its hooks and commands
11
+ * registered without `--trust`. Anything else (filesystem, network, shell,
12
+ * secrets, config, privileged, …) needs an explicit `--trust`.
13
+ */
14
+ export const DEFAULT_PLUGIN_PERMISSIONS = Object.freeze(['memory:read']);
15
+ /** Trust levels that are withheld unless the user passes `--trust`. */
16
+ export const UNTRUSTED_TRUST_LEVELS = Object.freeze(['untrusted', 'unverified']);
17
+ /** Registry trust levels that the registry itself vouches for. */
18
+ export const REGISTRY_VOUCHED_TRUST_LEVELS = Object.freeze(['official', 'verified']);
19
+ function asStringList(value) {
20
+ if (!Array.isArray(value))
21
+ return [];
22
+ return value.filter((v) => typeof v === 'string' && v.trim() !== '').map((v) => v.trim());
23
+ }
24
+ /**
25
+ * Read the trust declaration from a plugin's package.json. The `claude-flow`
26
+ * block wins; top-level fields are accepted as a fallback.
27
+ */
28
+ export function readDeclaredTrust(pkg) {
29
+ const block = (pkg['claude-flow'] && typeof pkg['claude-flow'] === 'object'
30
+ ? pkg['claude-flow']
31
+ : {});
32
+ const trustLevel = typeof block.trustLevel === 'string'
33
+ ? block.trustLevel
34
+ : typeof pkg.trustLevel === 'string' ? pkg.trustLevel : undefined;
35
+ const permissions = asStringList(block.permissions).length > 0
36
+ ? asStringList(block.permissions)
37
+ : asStringList(pkg.permissions);
38
+ return { trustLevel, permissions };
39
+ }
40
+ /** Decide whether an install may register the plugin's hooks and commands. */
41
+ export function evaluatePluginTrust(declared, opts) {
42
+ const excessPermissions = declared.permissions.filter((p) => !DEFAULT_PLUGIN_PERMISSIONS.includes(p));
43
+ if (!opts.verify) {
44
+ return { allowed: true, verificationSkipped: true, reasons: [], excessPermissions };
45
+ }
46
+ const reasons = [];
47
+ const registryVouched = opts.registryTrustLevel !== undefined
48
+ && REGISTRY_VOUCHED_TRUST_LEVELS.includes(opts.registryTrustLevel);
49
+ if (!registryVouched) {
50
+ if (declared.trustLevel && UNTRUSTED_TRUST_LEVELS.includes(declared.trustLevel)) {
51
+ reasons.push(`declares trustLevel "${declared.trustLevel}"`);
52
+ }
53
+ if (excessPermissions.length > 0) {
54
+ reasons.push(`declares permissions beyond the default set (${DEFAULT_PLUGIN_PERMISSIONS.join(', ')}): ${excessPermissions.join(', ')}`);
55
+ }
56
+ }
57
+ if (reasons.length > 0 && !opts.trust) {
58
+ return { allowed: false, verificationSkipped: false, reasons, excessPermissions };
59
+ }
60
+ return { allowed: true, verificationSkipped: false, reasons: [], excessPermissions };
61
+ }
62
+ /**
63
+ * Whether npm may run the package's lifecycle scripts (preinstall/install/
64
+ * postinstall, and those of its dependencies). Scripts execute before the
65
+ * package's own trust declaration can be read, so only an explicit `--trust`
66
+ * or a registry-vouched entry allows them; everything else installs with
67
+ * `--ignore-scripts`.
68
+ */
69
+ export function shouldRunInstallScripts(opts) {
70
+ if (opts.trust === true)
71
+ return true;
72
+ return opts.registryTrustLevel !== undefined && REGISTRY_VOUCHED_TRUST_LEVELS.includes(opts.registryTrustLevel);
73
+ }
74
+ /** A registry checksum we can actually verify: `sha256:` + 64 hex chars. */
75
+ export function parseSha256Checksum(checksum) {
76
+ if (typeof checksum !== 'string')
77
+ return null;
78
+ const m = /^sha256:([0-9a-f]{64})$/i.exec(checksum.trim());
79
+ return m ? m[1].toLowerCase() : null;
80
+ }
81
+ export function sha256Hex(data) {
82
+ return createHash('sha256').update(data).digest('hex');
83
+ }
84
+ //# sourceMappingURL=trust-policy.js.map
@@ -96,7 +96,10 @@ async function loadRuVector() {
96
96
  function extractImports(content, _filePath) {
97
97
  const imports = [];
98
98
  // ES6 import statements
99
- const esImportRegex = /import\s+(?:(?:\{[^}]*\}|\*\s+as\s+\w+|\w+)\s*,?\s*)*\s*from\s*['"]([^'"]+)['"]/g;
99
+ // Keep the import clause's shape finite. Repeating an optional separator
100
+ // around alternatives lets ordinary prose such as "import one two three ..."
101
+ // backtrack exponentially when no `from` clause follows.
102
+ const esImportRegex = /\bimport\s+(?:type\s+)?(?:[\w$]+\s*,\s*)?(?:\{[^}]*\}|\*\s+as\s+[\w$]+|[\w$]+)\s+from\s*['"]([^'"]+)['"]/g;
100
103
  let match;
101
104
  while ((match = esImportRegex.exec(content)) !== null) {
102
105
  imports.push({ path: match[1], type: 'import' });
@@ -80,12 +80,23 @@ export class ConfigFileManager {
80
80
  }
81
81
  try {
82
82
  const content = fs.readFileSync(this.configPath, 'utf-8');
83
- this.config = JSON.parse(content);
83
+ const parsed = JSON.parse(content);
84
+ if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
85
+ throw new Error('Config file must contain a JSON object');
86
+ }
87
+ this.config = parsed;
84
88
  return this.config;
85
89
  }
86
- catch {
90
+ catch (error) {
87
91
  this.config = null;
88
- return null;
92
+ if (error.code === 'ENOENT') {
93
+ this.configPath = null;
94
+ return null;
95
+ }
96
+ // A parse/read failure is not a missing config. In particular, set()
97
+ // must never replace a recoverable file with fallback defaults.
98
+ const detail = error instanceof Error ? error.message : String(error);
99
+ throw new Error(`Failed to load config ${this.configPath}: ${detail}`);
89
100
  }
90
101
  }
91
102
  /** Get the current config, loading if needed */
@@ -98,12 +109,23 @@ export class ConfigFileManager {
98
109
  /** Get a nested config value by dot-separated key */
99
110
  get(cwd, key) {
100
111
  const config = this.getConfig(cwd);
101
- return getNestedValue(config, key);
112
+ return Object.hasOwn(config, key) ? config[key] : getNestedValue(config, key);
102
113
  }
103
114
  /** Set a nested config value by dot-separated key */
104
115
  set(cwd, key, value) {
105
- const config = this.getConfig(cwd);
106
- setNestedValue(config, key, value);
116
+ // Reject the entire path before touching cached config or disk.
117
+ if (key.split('.').some(part => part === '__proto__' || part === 'constructor' || part === 'prototype')) {
118
+ throw new Error(`Unsafe configuration key: ${key}`);
119
+ }
120
+ // A targeted update must not persist unrelated defaults (notably the
121
+ // default memory path, which would relocate an existing memory store).
122
+ const config = this.config ?? this.load(cwd) ?? {};
123
+ if (Object.hasOwn(config, key)) {
124
+ config[key] = value;
125
+ }
126
+ else {
127
+ setNestedValue(config, key, value);
128
+ }
107
129
  this.config = config;
108
130
  const targetPath = this.configPath ?? path.resolve(cwd, CONFIG_FILENAMES[0]);
109
131
  this.writeAtomic(targetPath, config);
@@ -181,7 +203,7 @@ function getNestedValue(obj, key) {
181
203
  const parts = key.split('.');
182
204
  let current = obj;
183
205
  for (const part of parts) {
184
- if (current === null || current === undefined || typeof current !== 'object') {
206
+ if (current === null || current === undefined || typeof current !== 'object' || !Object.hasOwn(current, part)) {
185
207
  return undefined;
186
208
  }
187
209
  current = current[part];
@@ -194,7 +216,7 @@ function setNestedValue(obj, key, value) {
194
216
  let current = obj;
195
217
  for (let i = 0; i < parts.length - 1; i++) {
196
218
  const part = parts[i];
197
- if (!(part in current) || typeof current[part] !== 'object' || current[part] === null) {
219
+ if (!Object.hasOwn(current, part) || typeof current[part] !== 'object' || current[part] === null) {
198
220
  current[part] = {};
199
221
  }
200
222
  current = current[part];
@@ -25,7 +25,7 @@ export interface RestoreResult {
25
25
  restored: boolean;
26
26
  /** The backup file that was restored. */
27
27
  from?: string;
28
- /** memory_entries count in the restored DB (-1 if it couldn't be verified). */
28
+ /** Verified memory_entries count in the restored DB. */
29
29
  rows?: number;
30
30
  /** Where the corrupt live DB was parked before the swap. */
31
31
  corruptBackupPath?: string;
@@ -14,6 +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
18
  export function defaultMemoryDbPath(cwd = process.cwd()) {
18
19
  return path.join(cwd, '.swarm', 'memory.db');
19
20
  }
@@ -21,6 +22,19 @@ export function defaultMemoryDbPath(cwd = process.cwd()) {
21
22
  function fileStamp(ms) {
22
23
  return new Date(ms).toISOString().replace(/[:.]/g, '-');
23
24
  }
25
+ /** Keep legacy memory.db names; give every other basename its own snapshot set. */
26
+ function snapshotPrefix(dbPath) {
27
+ const name = path.basename(dbPath);
28
+ // A distinct namespace prevents an extensionless `memory` database from
29
+ // colliding with the historical `memory.db` prefix. Encoding is injective
30
+ // within the nondefault namespace, including names that contain `%`.
31
+ return name === 'memory.db' ? 'memory-' : `store-${encodeURIComponent(name)}-`;
32
+ }
33
+ function isSnapshotFor(file, dbPath) {
34
+ const prefix = snapshotPrefix(dbPath);
35
+ return file.startsWith(prefix)
36
+ && /^\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}-\d{3}Z\.db$/.test(file.slice(prefix.length));
37
+ }
24
38
  export async function backupMemoryDb(opts = {}) {
25
39
  const dbPath = opts.dbPath ?? defaultMemoryDbPath();
26
40
  if (!dbPath || !fs.existsSync(dbPath))
@@ -38,7 +52,7 @@ export async function backupMemoryDb(opts = {}) {
38
52
  fs.mkdirSync(destDir, { recursive: true });
39
53
  }
40
54
  catch { /* */ }
41
- const destPath = path.join(destDir, `memory-${fileStamp(opts.timestamp ?? Date.now())}.db`);
55
+ const destPath = path.join(destDir, `${snapshotPrefix(dbPath)}${fileStamp(opts.timestamp ?? Date.now())}.db`);
42
56
  // WAL-safe online backup: read-only source, consistent snapshot to destPath.
43
57
  let db;
44
58
  try {
@@ -68,7 +82,7 @@ export async function backupMemoryDb(opts = {}) {
68
82
  const keep = typeof opts.keep === 'number' && opts.keep > 0 ? opts.keep : 7;
69
83
  const rotatedAway = [];
70
84
  try {
71
- const snaps = fs.readdirSync(destDir).filter(f => /^memory-.*\.db$/.test(f)).sort();
85
+ const snaps = fs.readdirSync(destDir).filter(f => isSnapshotFor(f, dbPath)).sort();
72
86
  while (snaps.length > keep) {
73
87
  const old = snaps.shift();
74
88
  try {
@@ -107,7 +121,7 @@ export async function backupMemoryDb(opts = {}) {
107
121
  const keep = typeof opts.keep === 'number' && opts.keep > 0 ? opts.keep : 7;
108
122
  const rotatedAway = [];
109
123
  try {
110
- const snaps = fs.readdirSync(destDir).filter(f => /^memory-.*\.db$/.test(f)).sort();
124
+ const snaps = fs.readdirSync(destDir).filter(f => isSnapshotFor(f, dbPath)).sort();
111
125
  while (snaps.length > keep) {
112
126
  const old = snaps.shift();
113
127
  try {
@@ -136,6 +150,48 @@ export async function backupMemoryDb(opts = {}) {
136
150
  }
137
151
  return { backedUp: true, path: destPath, sizeBytes, rotatedAway, gcsUri };
138
152
  }
153
+ /** Verify a snapshot without modifying it, including RFE1 images and WASM-only hosts. */
154
+ async function verifiedBackupRows(file, Database) {
155
+ if (Database) {
156
+ let db;
157
+ try {
158
+ db = new Database(file, { readonly: true });
159
+ if (String(db.pragma('integrity_check', { simple: true })).toLowerCase() !== 'ok')
160
+ return null;
161
+ const rows = Number(db.prepare('SELECT COUNT(*) AS c FROM memory_entries').get()?.c ?? 0);
162
+ return rows > 0 ? rows : null;
163
+ }
164
+ catch {
165
+ // Native SQLite cannot open encrypted RFE1 images. Verify the decrypted
166
+ // bytes below; a failed native open is never evidence of a good backup.
167
+ }
168
+ finally {
169
+ try {
170
+ db?.close();
171
+ }
172
+ catch { /* best effort */ }
173
+ }
174
+ }
175
+ let db;
176
+ try {
177
+ const initSqlJs = (await import('sql.js')).default;
178
+ const SQL = await initSqlJs();
179
+ db = new SQL.Database(readFileMaybeEncrypted(file, null));
180
+ const integrity = db.exec('PRAGMA integrity_check')[0]?.values?.[0]?.[0];
181
+ const rows = Number(db.exec('SELECT COUNT(*) FROM memory_entries')[0]?.values?.[0]?.[0] ?? 0);
182
+ return String(integrity).toLowerCase() === 'ok' && rows > 0 ? rows : null;
183
+ }
184
+ catch {
185
+ // No verifier, wrong key, failed authentication, or invalid SQLite image.
186
+ return null;
187
+ }
188
+ finally {
189
+ try {
190
+ db?.close();
191
+ }
192
+ catch { /* best effort */ }
193
+ }
194
+ }
139
195
  /**
140
196
  * Restore the newest integrity-ok backup over a corrupt/malformed memory DB
141
197
  * (issue #2584).
@@ -158,7 +214,7 @@ export async function restoreMemoryDbFromBackup(dbPath, opts = {}) {
158
214
  try {
159
215
  snaps = fs
160
216
  .readdirSync(destDir)
161
- .filter(f => /^memory-.*\.db$/.test(f))
217
+ .filter(f => isSnapshotFor(f, dbPath))
162
218
  .map(f => path.join(destDir, f))
163
219
  .sort() // ISO-stamped names sort chronologically
164
220
  .reverse(); // newest first
@@ -168,39 +224,23 @@ export async function restoreMemoryDbFromBackup(dbPath, opts = {}) {
168
224
  }
169
225
  if (!snaps.length)
170
226
  return { restored: false, skipped: 'no-backups' };
171
- // better-sqlite3 verifies a candidate's integrity. Absent (WASM-only host) →
172
- // accept the newest non-empty snapshot, flagged rows=-1 (unverified).
227
+ // Prefer native verification for plaintext snapshots. sql.js verifies
228
+ // encrypted snapshots and provides the same checks on WASM-only hosts.
173
229
  let Database = null;
174
230
  try {
175
231
  const mod = 'better-sqlite3';
176
232
  Database = (await import(mod)).default;
177
233
  }
178
234
  catch {
179
- /* verifier unavailable — trust newest non-empty */
235
+ /* use the sql.js verifier */
180
236
  }
181
237
  let chosen = null;
182
238
  for (const file of snaps) {
183
- try {
184
- if (Database) {
185
- const db = new Database(file, { readonly: true });
186
- const integ = String(db.pragma('integrity_check', { simple: true }) ?? '');
187
- let rows = 0;
188
- try {
189
- rows = db.prepare('SELECT COUNT(*) AS c FROM memory_entries').get()?.c ?? 0;
190
- }
191
- catch { /* no entries table — not a usable memory DB */ }
192
- db.close();
193
- if (integ.toLowerCase() === 'ok' && rows > 0) {
194
- chosen = { file, rows };
195
- break;
196
- }
197
- }
198
- else if (fs.statSync(file).size > 0) {
199
- chosen = { file, rows: -1 };
200
- break;
201
- }
239
+ const rows = await verifiedBackupRows(file, Database);
240
+ if (rows !== null) {
241
+ chosen = { file, rows };
242
+ break;
202
243
  }
203
- catch { /* unreadable snapshot — try the next-older one */ }
204
244
  }
205
245
  if (!chosen)
206
246
  return { restored: false, skipped: 'no-integrity-ok-backup' };
@@ -17,6 +17,12 @@ export declare function issuePolicyApproval(approval: Omit<PolicyApproval, 'uses
17
17
  issuedAt?: number;
18
18
  }, projectRoot?: string, approvalIssuerVerifier?: (issuer: string) => boolean): Promise<PolicyApproval>;
19
19
  export declare function revokePolicyApproval(id: string, projectRoot?: string): Promise<boolean>;
20
+ /**
21
+ * Read-only verification (#3568). It must not run inside
22
+ * `withPolicyTransaction`, whose own post-operation check throws a generic
23
+ * `policy-ledger-verification-failed` and hides which check failed. The only
24
+ * write is persisting an anchor established for a pre-anchor ledger.
25
+ */
20
26
  export declare function verifyPolicyLedger(projectRoot?: string): Promise<ReturnType<AgenticPolicyEngine['verifyLedger']>>;
21
27
  export declare function authorizeMcpTool(toolName: string, input: Record<string, unknown>, context?: Record<string, unknown>, attributes?: Readonly<{
22
28
  actionType?: string;
@@ -338,9 +338,12 @@ export async function withPolicyTransaction(projectRoot, operation, options = {}
338
338
  approvalIssuerVerifier: options.approvalIssuerVerifier,
339
339
  });
340
340
  const result = await operation(engine);
341
- const nextState = engine.exportState();
341
+ // Verify before exporting: verification establishes the ledger anchor
342
+ // (#3568) on state written before the anchor existed, and that anchor
343
+ // must be part of what is persisted.
342
344
  if (!engine.verifyLedger().valid)
343
345
  throw new Error('policy-ledger-verification-failed');
346
+ const nextState = engine.exportState();
344
347
  await writePolicyState(projectRoot, target.state, nextState);
345
348
  return result;
346
349
  }
@@ -393,8 +396,30 @@ export async function issuePolicyApproval(approval, projectRoot = process.cwd(),
393
396
  export async function revokePolicyApproval(id, projectRoot = process.cwd()) {
394
397
  return withPolicyTransaction(projectRoot, (engine) => engine.revokeApproval(id));
395
398
  }
399
+ /**
400
+ * Read-only verification (#3568). It must not run inside
401
+ * `withPolicyTransaction`, whose own post-operation check throws a generic
402
+ * `policy-ledger-verification-failed` and hides which check failed. The only
403
+ * write is persisting an anchor established for a pre-anchor ledger.
404
+ */
396
405
  export async function verifyPolicyLedger(projectRoot = process.cwd()) {
397
- return withPolicyTransaction(projectRoot, (engine) => engine.verifyLedger());
406
+ const target = paths(projectRoot);
407
+ mkdirSync(target.dir, { recursive: true, mode: 0o700 });
408
+ const release = await acquireLock(target.lock);
409
+ try {
410
+ const engine = AgenticPolicyEngine.fromState(loadPolicyState(projectRoot), {
411
+ signingKey: process.env.CLAUDE_FLOW_POLICY_SIGNING_KEY,
412
+ keyId: process.env.CLAUDE_FLOW_POLICY_KEY_ID,
413
+ });
414
+ const result = engine.verifyLedger();
415
+ if (result.anchor === 'established-now') {
416
+ await writePolicyState(projectRoot, target.state, engine.exportState());
417
+ }
418
+ return result;
419
+ }
420
+ finally {
421
+ release();
422
+ }
398
423
  }
399
424
  /**
400
425
  * Resolve the caller identity for `authorizeMcpTool`.
@@ -124,8 +124,8 @@ export declare class WorkerDaemon extends EventEmitter {
124
124
  static isWslEnvironment(): boolean;
125
125
  static getEffectiveCpuCount(): number;
126
126
  /**
127
- * Read daemon-specific config from .claude-flow/config.{json,yaml,yml}.
128
- * Supports dot-notation keys like 'daemon.resourceThresholds.maxCpuLoad'.
127
+ * Read daemon-specific config from CLI JSON paths or .claude-flow/config.{yaml,yml}.
128
+ * Supports dotted and nested keys like 'daemon.resourceThresholds.maxCpuLoad'.
129
129
  * #1844: prefer JSON when both exist (existing behavior) but fall back
130
130
  * to YAML so operators using the v3 canonical YAML format aren't silently
131
131
  * ignored. The chosen path (or a parse warning) is returned rather than
@@ -10,6 +10,7 @@
10
10
  * causal_edges (30 min interval, ADR-174; RUFLO_DAEMON_NO_DISTILL=1 / --no-distill to skip)
11
11
  * - testgaps: Test coverage analysis (20 min interval)
12
12
  */
13
+ import { ConfigFileManager } from './config-file-manager.js';
13
14
  import { EventEmitter } from 'events';
14
15
  import { existsSync, mkdirSync, writeFileSync, readFileSync, appendFileSync, unlinkSync } from 'fs';
15
16
  import { cpus } from 'os';
@@ -339,8 +340,8 @@ export class WorkerDaemon extends EventEmitter {
339
340
  return cpus().length || 1;
340
341
  }
341
342
  /**
342
- * Read daemon-specific config from .claude-flow/config.{json,yaml,yml}.
343
- * Supports dot-notation keys like 'daemon.resourceThresholds.maxCpuLoad'.
343
+ * Read daemon-specific config from CLI JSON paths or .claude-flow/config.{yaml,yml}.
344
+ * Supports dotted and nested keys like 'daemon.resourceThresholds.maxCpuLoad'.
344
345
  * #1844: prefer JSON when both exist (existing behavior) but fall back
345
346
  * to YAML so operators using the v3 canonical YAML format aren't silently
346
347
  * ignored. The chosen path (or a parse warning) is returned rather than
@@ -352,13 +353,14 @@ export class WorkerDaemon extends EventEmitter {
352
353
  * The caller logs these once `this.config` exists.
353
354
  */
354
355
  readDaemonConfigFromFile(claudeFlowDir) {
355
- const jsonPath = join(claudeFlowDir, 'config.json');
356
+ // Share the CLI's JSON search order, including root and explicit config files.
357
+ const jsonPath = new ConfigFileManager().findConfig(join(claudeFlowDir, '..'));
356
358
  const yamlPath = join(claudeFlowDir, 'config.yaml');
357
359
  const ymlPath = join(claudeFlowDir, 'config.yml');
358
360
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
359
361
  let raw;
360
362
  let chosenPath;
361
- if (existsSync(jsonPath)) {
363
+ if (jsonPath) {
362
364
  try {
363
365
  raw = JSON.parse(readFileSync(jsonPath, 'utf-8'));
364
366
  chosenPath = jsonPath;
@@ -390,21 +392,28 @@ export class WorkerDaemon extends EventEmitter {
390
392
  return {};
391
393
  }
392
394
  try {
393
- // Support both flat keys at root and nested under scopes.project
394
- const cfg = raw?.scopes?.project ?? raw;
395
- const rawCpuLoad = cfg['daemon.resourceThresholds.maxCpuLoad'] ?? raw['daemon.resourceThresholds.maxCpuLoad'];
396
- const rawMinMem = cfg['daemon.resourceThresholds.minFreeMemoryPercent'] ?? raw['daemon.resourceThresholds.minFreeMemoryPercent'];
397
- const rawMaxConcurrent = cfg['daemon.maxConcurrent'] ?? raw['daemon.maxConcurrent'];
398
- const rawTimeout = cfg['daemon.workerTimeoutMs'] ?? raw['daemon.workerTimeoutMs'];
395
+ // The CLI writes nested objects; legacy files use dotted keys. Scoped
396
+ // values retain precedence, including explicit false and zero.
397
+ const read = (obj, key) => {
398
+ if (!obj)
399
+ return undefined;
400
+ return obj[key] ?? key.split('.').reduce((value, part) => value && typeof value === 'object' && Object.hasOwn(value, part) ? value[part] : undefined, obj);
401
+ };
402
+ const get = (key) => read(raw?.scopes?.project, key) ?? read(raw, key);
403
+ const rawAutoStart = get('daemon.autoStart');
404
+ const rawCpuLoad = get('daemon.resourceThresholds.maxCpuLoad');
405
+ const rawMinMem = get('daemon.resourceThresholds.minFreeMemoryPercent');
406
+ const rawMaxConcurrent = get('daemon.maxConcurrent');
407
+ const rawTimeout = get('daemon.workerTimeoutMs');
399
408
  // #2356 — lifecycle limits are configured in SECONDS in config.json
400
409
  // (`daemon.ttlSecs` / `daemon.idleSecs`) for parity with the CLI flag
401
410
  // and env var; stored internally as ms. An explicit 0 disables.
402
- const rawTtl = cfg['daemon.ttlSecs'] ?? raw['daemon.ttlSecs'];
403
- const rawIdle = cfg['daemon.idleSecs'] ?? raw['daemon.idleSecs'];
411
+ const rawTtl = get('daemon.ttlSecs');
412
+ const rawIdle = get('daemon.idleSecs');
404
413
  // #2661 — explicit opt-in for scheduled AI workers.
405
- const rawAiEnabled = cfg['daemon.aiWorkers.enabled'] ?? raw['daemon.aiWorkers.enabled'];
414
+ const rawAiEnabled = get('daemon.aiWorkers.enabled');
406
415
  return {
407
- autoStart: typeof raw['daemon.autoStart'] === 'boolean' ? raw['daemon.autoStart'] : undefined,
416
+ autoStart: typeof rawAutoStart === 'boolean' ? rawAutoStart : undefined,
408
417
  maxConcurrent: (typeof rawMaxConcurrent === 'number' && rawMaxConcurrent > 0) ? rawMaxConcurrent : undefined,
409
418
  workerTimeoutMs: (typeof rawTimeout === 'number' && rawTimeout > 0) ? rawTimeout : undefined,
410
419
  maxCpuLoad: (typeof rawCpuLoad === 'number' && rawCpuLoad > 0 && rawCpuLoad < 1000) ? rawCpuLoad : undefined,
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@claude-flow/cli",
3
- "version": "3.47.1",
3
+ "version": "3.49.0",
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",
@@ -98,13 +98,13 @@
98
98
  "vitest": "^4.1.0"
99
99
  },
100
100
  "dependencies": {
101
- "@claude-flow/cli-core": "3.7.0-alpha.6",
101
+ "@claude-flow/cli-core": "3.7.0",
102
102
  "@claude-flow/codex": "3.0.3",
103
- "@claude-flow/mcp": "3.0.0-alpha.10",
104
- "@claude-flow/neural": "3.0.0-alpha.10",
105
- "@claude-flow/plugin-agent-federation": "1.0.0-alpha.18",
106
- "@claude-flow/security": "3.0.0-alpha.14",
107
- "@claude-flow/shared": "3.0.0-alpha.7",
103
+ "@claude-flow/mcp": "3.0.0",
104
+ "@claude-flow/neural": "3.0.0",
105
+ "@claude-flow/plugin-agent-federation": "1.0.0",
106
+ "@claude-flow/security": "3.0.1",
107
+ "@claude-flow/shared": "3.0.0",
108
108
  "@iarna/toml": "^2.2.5",
109
109
  "@noble/ed25519": "2.3.0",
110
110
  "@ruvector/rabitq-wasm": "0.1.0",
@@ -125,11 +125,11 @@
125
125
  "ws": "^8.21.0",
126
126
  "yaml": "^2.8.0",
127
127
  "zod": "^3.22.0",
128
- "@claude-flow/memory": "3.0.0-alpha.26"
128
+ "@claude-flow/memory": "3.0.0"
129
129
  },
130
130
  "optionalDependencies": {
131
131
  "@agntcy/slim-bindings": "2.0.0-alpha.5",
132
- "@claude-flow/memory": "3.0.0-alpha.26",
132
+ "@claude-flow/memory": "3.0.0",
133
133
  "@metaharness/darwin": "~0.10.2",
134
134
  "@metaharness/flywheel": "~0.1.10",
135
135
  "@metaharness/radio": "~0.1.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@claude-flow/guidance",
3
- "version": "3.0.0-alpha.4",
3
+ "version": "3.0.0",
4
4
  "description": "Guidance Control Plane - Compiles, retrieves, enforces, and evolves guidance rules for Claude Code sessions",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -140,9 +140,9 @@
140
140
  "typecheck": "tsc --noEmit"
141
141
  },
142
142
  "dependencies": {
143
- "@claude-flow/hooks": "^3.0.0-alpha.7",
144
- "@claude-flow/memory": "^3.0.0-alpha.2",
145
- "@claude-flow/shared": "^3.0.0-alpha.1"
143
+ "@claude-flow/hooks": "^3.0.0",
144
+ "@claude-flow/memory": "^3.0.0",
145
+ "@claude-flow/shared": "^3.0.0"
146
146
  },
147
147
  "devDependencies": {
148
148
  "@types/node": "^20.10.0",
@@ -192,7 +192,6 @@
192
192
  "directory": "v3/@claude-flow/guidance"
193
193
  },
194
194
  "publishConfig": {
195
- "access": "public",
196
- "tag": "v3alpha"
195
+ "access": "public"
197
196
  }
198
197
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@claude-flow/shared",
3
- "version": "3.0.0-alpha.8",
3
+ "version": "3.0.0",
4
4
  "type": "module",
5
5
  "description": "Shared module - common types, events, utilities, core interfaces",
6
6
  "main": "dist/index.js",
@@ -16,6 +16,10 @@
16
16
  "./resilience": "./dist/resilience/index.js",
17
17
  "./src/plugin-interface.js": "./dist/plugin-interface.js"
18
18
  },
19
+ "files": [
20
+ "dist",
21
+ "README.md"
22
+ ],
19
23
  "scripts": {
20
24
  "test": "vitest run",
21
25
  "build": "tsc"
@@ -37,7 +41,6 @@
37
41
  "zod": "^3.22.4"
38
42
  },
39
43
  "publishConfig": {
40
- "access": "public",
41
- "tag": "v3alpha"
44
+ "access": "public"
42
45
  }
43
46
  }