@claude-flow/cli 3.56.0 → 3.56.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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "manifest": {
3
- "version": "3.56.0",
3
+ "version": "3.56.1",
4
4
  "files": {
5
5
  "auto-memory-hook.mjs": "c8d218526a3c34034c2a69de23f507f0eb0f70d59c16426a8edc849e6ac22529",
6
6
  "hook-handler.cjs": "34d2aacfb59a73ff94b1467bb57ab9d9c4af89c0e470aadf3bd0d74a503a658d",
@@ -9,6 +9,6 @@
9
9
  "router.cjs": "16648b3c0d73b6681ac2d8d13def6a68286c57f5a11004de5b19a0fa088c833d"
10
10
  }
11
11
  },
12
- "signature": "9T3S9rchP2NnIjrYOL9tV7QvDP3WlsrsR7WlBXpUpoGPCrhe2Mo9bQEU7JOCscuQ+UQsrP211p7vmc93+AlWDg==",
12
+ "signature": "eGhCbWbSEhufQTLyjtRuSNc3JX1kDYCMAbqj/mn4IpYUOX+pi9Xs5lPAo/kVgVolhF1JRspGhzFi3Y8spw0EBw==",
13
13
  "algorithm": "ed25519"
14
14
  }
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "generation": 7,
4
- "generatedAt": "2026-10-08T21:48:51.902Z",
5
- "gitSha": "527796ae",
4
+ "generatedAt": "2026-10-09T04:37:42.484Z",
5
+ "gitSha": "58e0ae7e",
6
6
  "catalog": {
7
7
  "agents": 176,
8
8
  "tools": 423,
@@ -309,7 +309,7 @@ const resetCommand = {
309
309
  ],
310
310
  action: async (ctx) => {
311
311
  try {
312
- const configPath = configManager.reset(ctx.cwd);
312
+ const configPath = configManager.reset(ctx.cwd, ctx.flags.section);
313
313
  output.writeln(`Configuration reset to defaults: ${configPath}`);
314
314
  return { success: true };
315
315
  }
@@ -35,12 +35,110 @@ function ensureConfigDir() {
35
35
  mkdirSync(dir, { recursive: true, mode: 0o700 });
36
36
  }
37
37
  }
38
+ // Plain CLI files and legacy MCP envelopes share the same path. Keep the
39
+ // original plain document so MCP writes remain readable by the CLI.
40
+ const plainDocuments = new WeakMap();
41
+ const STORE_METADATA = new Set(['scopes', 'version', 'updatedAt']);
42
+ function isRecord(value) {
43
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
44
+ }
45
+ function flattenValues(document) {
46
+ const values = {};
47
+ function visit(value, key) {
48
+ if (isRecord(value) && Object.keys(value).length > 0) {
49
+ for (const [child, entry] of Object.entries(value))
50
+ visit(entry, `${key}.${child}`);
51
+ }
52
+ else {
53
+ Object.defineProperty(values, key, { value, enumerable: true, configurable: true, writable: true });
54
+ }
55
+ }
56
+ for (const [key, value] of Object.entries(document)) {
57
+ if (!STORE_METADATA.has(key))
58
+ visit(value, key);
59
+ }
60
+ // CLI reads literal dotted keys before traversing nested objects.
61
+ for (const [key, value] of Object.entries(document)) {
62
+ if (key.includes('.'))
63
+ Object.defineProperty(values, key, { value, enumerable: true, configurable: true, writable: true });
64
+ }
65
+ return values;
66
+ }
67
+ function readDefaultValue(store, key) {
68
+ const document = plainDocuments.get(store);
69
+ if (!document)
70
+ return store.values[key];
71
+ return Object.hasOwn(document, key) ? document[key] : getNestedValue(document, key);
72
+ }
73
+ function writeDefaultValue(store, key, value) {
74
+ const document = plainDocuments.get(store);
75
+ if (!document) {
76
+ store.values[key] = value;
77
+ return;
78
+ }
79
+ // Keep existing literal dotted keys; otherwise use the CLI's nested shape.
80
+ if (Object.hasOwn(document, key)) {
81
+ // Reuse the same segment validation as nested writes.
82
+ setNestedValue({}, key, value);
83
+ document[key] = value;
84
+ }
85
+ else {
86
+ setNestedValue(document, key, value);
87
+ }
88
+ store.values = flattenValues(document);
89
+ }
90
+ function deleteDefaultValue(store, key) {
91
+ const document = plainDocuments.get(store);
92
+ if (!document)
93
+ return Object.hasOwn(store.values, key) && delete store.values[key];
94
+ let target = document;
95
+ let leaf = key;
96
+ if (!Object.hasOwn(document, key)) {
97
+ const parts = key.split('.');
98
+ leaf = parts.pop();
99
+ for (const part of parts) {
100
+ if (!Object.hasOwn(target, part) || !isRecord(target[part]))
101
+ return false;
102
+ target = target[part];
103
+ }
104
+ }
105
+ const deleted = Object.hasOwn(target, leaf) && delete target[leaf];
106
+ store.values = flattenValues(document);
107
+ return deleted;
108
+ }
109
+ function replaceDefaultValues(store, values) {
110
+ const document = plainDocuments.get(store);
111
+ if (!document) {
112
+ store.values = values;
113
+ return;
114
+ }
115
+ for (const key of Object.keys(document)) {
116
+ if (!STORE_METADATA.has(key))
117
+ delete document[key];
118
+ }
119
+ for (const [key, value] of Object.entries(values))
120
+ writeDefaultValue(store, key, value);
121
+ }
38
122
  function loadConfigStore() {
39
123
  try {
40
124
  const path = getConfigPath();
41
125
  if (existsSync(path)) {
42
126
  const data = readFileSync(path, 'utf-8');
43
- return JSON.parse(data);
127
+ const document = JSON.parse(data);
128
+ const isEnvelope = isRecord(document) && isRecord(document.values)
129
+ && isRecord(document.scopes) && typeof document.version === 'string'
130
+ && typeof document.updatedAt === 'string';
131
+ if (isRecord(document) && !isEnvelope) {
132
+ const store = {
133
+ values: flattenValues(document),
134
+ scopes: isRecord(document.scopes) ? document.scopes : {},
135
+ version: typeof document.version === 'string' ? document.version : '3.0.0',
136
+ updatedAt: typeof document.updatedAt === 'string' ? document.updatedAt : '',
137
+ };
138
+ plainDocuments.set(store, document);
139
+ return store;
140
+ }
141
+ return document;
44
142
  }
45
143
  }
46
144
  catch {
@@ -56,13 +154,20 @@ function loadConfigStore() {
56
154
  function saveConfigStore(store) {
57
155
  ensureConfigDir();
58
156
  store.updatedAt = new Date().toISOString();
59
- writeFileSync(getConfigPath(), JSON.stringify(store, null, 2), 'utf-8');
157
+ const document = plainDocuments.get(store);
158
+ if (document) {
159
+ document.updatedAt = store.updatedAt;
160
+ if (Object.keys(store.scopes).length > 0 || Object.hasOwn(document, 'scopes')) {
161
+ document.scopes = store.scopes;
162
+ }
163
+ }
164
+ writeFileSync(getConfigPath(), JSON.stringify(document ?? store, null, 2), 'utf-8');
60
165
  }
61
166
  function getNestedValue(obj, key) {
62
167
  const parts = key.split('.');
63
168
  let current = obj;
64
169
  for (const part of parts) {
65
- if (current && typeof current === 'object' && part in current) {
170
+ if (current && typeof current === 'object' && Object.hasOwn(current, part)) {
66
171
  current = current[part];
67
172
  }
68
173
  else {
@@ -95,7 +200,7 @@ function setNestedValue(obj, key, value) {
95
200
  let current = obj;
96
201
  for (let i = 0; i < parts.length - 1; i++) {
97
202
  const part = parts[i];
98
- if (!(part in current) || typeof current[part] !== 'object') {
203
+ if (!Object.hasOwn(current, part) || current[part] === null || typeof current[part] !== 'object') {
99
204
  current[part] = {};
100
205
  }
101
206
  current = current[part];
@@ -134,7 +239,7 @@ export const configTools = [
134
239
  value = store.scopes[scope][key];
135
240
  }
136
241
  if (value === undefined) {
137
- value = store.values[key];
242
+ value = readDefaultValue(store, key);
138
243
  }
139
244
  if (value === undefined) {
140
245
  value = DEFAULT_CONFIG[key];
@@ -144,7 +249,7 @@ export const configTools = [
144
249
  value,
145
250
  scope,
146
251
  exists: value !== undefined,
147
- source: value !== undefined ? (store.values[key] !== undefined ? 'stored' : 'default') : 'none',
252
+ source: value !== undefined ? (readDefaultValue(store, key) !== undefined ? 'stored' : 'default') : 'none',
148
253
  };
149
254
  },
150
255
  },
@@ -175,9 +280,9 @@ export const configTools = [
175
280
  const key = input.key;
176
281
  const value = input.value;
177
282
  const scope = input.scope || 'default';
178
- const previousValue = store.values[key];
283
+ const previousValue = readDefaultValue(store, key);
179
284
  if (scope === 'default') {
180
- store.values[key] = value;
285
+ writeDefaultValue(store, key, value);
181
286
  }
182
287
  else {
183
288
  if (!store.scopes[scope]) {
@@ -289,8 +394,7 @@ export const configTools = [
289
394
  if (key) {
290
395
  // Reset specific key
291
396
  if (scope === 'default') {
292
- if (key in store.values) {
293
- delete store.values[key];
397
+ if (deleteDefaultValue(store, key)) {
294
398
  resetKeys.push(key);
295
399
  }
296
400
  }
@@ -303,7 +407,7 @@ export const configTools = [
303
407
  // Reset all keys in scope
304
408
  if (scope === 'default') {
305
409
  resetKeys = Object.keys(store.values);
306
- store.values = { ...DEFAULT_CONFIG };
410
+ replaceDefaultValues(store, { ...DEFAULT_CONFIG });
307
411
  }
308
412
  else if (store.scopes[scope]) {
309
413
  resetKeys = Object.keys(store.scopes[scope]);
@@ -385,10 +489,11 @@ export const configTools = [
385
489
  const importedKeys = Object.keys(config);
386
490
  if (scope === 'default') {
387
491
  if (merge) {
388
- Object.assign(store.values, config);
492
+ for (const [key, value] of Object.entries(config))
493
+ writeDefaultValue(store, key, value);
389
494
  }
390
495
  else {
391
- store.values = { ...DEFAULT_CONFIG, ...config };
496
+ replaceDefaultValues(store, { ...DEFAULT_CONFIG, ...config });
392
497
  }
393
498
  }
394
499
  else {
@@ -1235,7 +1235,11 @@ export async function ensureSchemaColumns(dbPath, options = {}) {
1235
1235
  if (columnsAdded.includes('status') || existingColumns.has('status')) {
1236
1236
  try {
1237
1237
  db.run(`UPDATE memory_entries SET status = 'active' WHERE status IS NULL`);
1238
- modified = true;
1238
+ // #3918: only a backfill that changed rows dirties the image. Setting
1239
+ // this unconditionally rewrote every up-to-date store on each open (and,
1240
+ // with encryption at rest, with a fresh nonce, so the bytes changed too).
1241
+ if (db.getRowsModified() > 0)
1242
+ modified = true;
1239
1243
  }
1240
1244
  catch {
1241
1245
  /* table is read-only or doesn't exist — skip */
@@ -18,8 +18,8 @@ export declare class ConfigFileManager {
18
18
  set(cwd: string, key: string, value: unknown): void;
19
19
  /** Create a new config file with defaults */
20
20
  create(cwd: string, overrides?: Record<string, unknown>, force?: boolean): string;
21
- /** Reset config to defaults */
22
- reset(cwd: string): string;
21
+ /** Reset all configuration or only the selected section to defaults. */
22
+ reset(cwd: string, section?: string): string;
23
23
  /** Export config to a specific path */
24
24
  exportTo(cwd: string, exportPath: string): void;
25
25
  /** Import config from a specific path */
@@ -148,11 +148,30 @@ export class ConfigFileManager {
148
148
  this.configPath = targetPath;
149
149
  return targetPath;
150
150
  }
151
- /** Reset config to defaults */
152
- reset(cwd) {
151
+ /** Reset all configuration or only the selected section to defaults. */
152
+ reset(cwd, section = 'all') {
153
+ if (!['agents', 'swarm', 'memory', 'mcp', 'providers', 'all'].includes(section)) {
154
+ throw new Error(`Unknown configuration section: ${section}`);
155
+ }
153
156
  this.selectProject(cwd);
154
157
  const targetPath = this.findConfig(cwd) ?? path.resolve(cwd, CONFIG_FILENAMES[0]);
155
- const config = structuredClone(DEFAULT_CONFIG);
158
+ const config = section === 'all'
159
+ ? structuredClone(DEFAULT_CONFIG)
160
+ : structuredClone(this.load(cwd) ?? {});
161
+ if (section === 'providers') {
162
+ // Provider defaults are supplied by the provider command, so remove
163
+ // only the persisted override, as a whole reset already does.
164
+ delete config.providers;
165
+ }
166
+ else if (section !== 'all') {
167
+ config[section] = structuredClone(DEFAULT_CONFIG[section]);
168
+ }
169
+ if (section !== 'all') {
170
+ for (const key of Object.keys(config)) {
171
+ if (key.startsWith(`${section}.`))
172
+ delete config[key];
173
+ }
174
+ }
156
175
  this.writeAtomic(targetPath, config);
157
176
  this.config = config;
158
177
  this.configPath = targetPath;
@@ -22,6 +22,14 @@ export type AnchorAssessment = {
22
22
  };
23
23
  export type AnchorWriter = (file: string, value: unknown) => Promise<void>;
24
24
  export declare const anchorLogPath: (projectRoot: string) => string;
25
+ /**
26
+ * Directory holding per-project trust material (anchor mirror, HMAC key).
27
+ * Resolved from the environment at call time (#3919): $XDG_CONFIG_HOME when it
28
+ * is absolute, else $HOME/.config (os.homedir() honours $HOME), so a scratch
29
+ * HOME keeps tests and sandboxed runs out of the developer's real home. An XDG
30
+ * user who already has trust state under the legacy ~/.config keeps using it.
31
+ */
32
+ export declare function policyTrustRoot(): string;
25
33
  /**
26
34
  * How the stored anchors relate to `state`; read-only. `rebuild` is the
27
35
  * explicit-establish path: an unreadable log or mirror is treated as absent, but
@@ -13,14 +13,29 @@
13
13
  */
14
14
  import { createHash } from 'node:crypto';
15
15
  import { existsSync, mkdirSync, readFileSync, realpathSync } from 'node:fs';
16
- import { dirname, join, resolve } from 'node:path';
17
- import { userInfo } from 'node:os';
16
+ import { dirname, isAbsolute, join, resolve } from 'node:path';
17
+ import { homedir } from 'node:os';
18
18
  const LOG_FILE = 'ledger-anchors.json';
19
19
  const MIRROR_FILE = 'ledger-anchor-head.json';
20
20
  export const anchorLogPath = (projectRoot) => join(resolve(projectRoot), '.claude-flow', 'policy', LOG_FILE);
21
+ /**
22
+ * Directory holding per-project trust material (anchor mirror, HMAC key).
23
+ * Resolved from the environment at call time (#3919): $XDG_CONFIG_HOME when it
24
+ * is absolute, else $HOME/.config (os.homedir() honours $HOME), so a scratch
25
+ * HOME keeps tests and sandboxed runs out of the developer's real home. An XDG
26
+ * user who already has trust state under the legacy ~/.config keeps using it.
27
+ */
28
+ export function policyTrustRoot() {
29
+ const legacy = join(homedir(), '.config', 'ruflo', 'policy-trust');
30
+ const xdg = process.env.XDG_CONFIG_HOME;
31
+ if (!xdg || !isAbsolute(xdg))
32
+ return legacy;
33
+ const preferred = join(xdg, 'ruflo', 'policy-trust');
34
+ return !existsSync(preferred) && existsSync(legacy) ? legacy : preferred;
35
+ }
21
36
  function mirrorPath(projectRoot) {
22
37
  const id = createHash('sha256').update(realpathSync(projectRoot)).digest('hex');
23
- return join(userInfo().homedir, '.config', 'ruflo', 'policy-trust', id, MIRROR_FILE);
38
+ return join(policyTrustRoot(), id, MIRROR_FILE);
24
39
  }
25
40
  function entryHash(entry) {
26
41
  return createHash('sha256').update(JSON.stringify([
@@ -5,7 +5,7 @@ import { closeSync, constants as fsConstants, existsSync, fstatSync, lstatSync,
5
5
  import { dirname, join, resolve } from 'node:path';
6
6
  import { hostname, userInfo } from 'node:os';
7
7
  import { syncPolicyProjection } from '../mods/policy-projection.js';
8
- import { assessAnchors, recordAnchor } from './policy-ledger-anchor.js';
8
+ import { assessAnchors, policyTrustRoot, recordAnchor } from './policy-ledger-anchor.js';
9
9
  const POLICY_DIR = join('.claude-flow', 'policy');
10
10
  const POLICY_FILE = 'state.json';
11
11
  const LOCK_FILE = 'state.lock';
@@ -194,7 +194,7 @@ async function writeJsonAtomic(file, value) {
194
194
  }
195
195
  }
196
196
  function trustPaths(projectRoot) {
197
- const trustRoot = join(userInfo().homedir, '.config', 'ruflo', 'policy-trust');
197
+ const trustRoot = policyTrustRoot();
198
198
  const projectId = createHash('sha256').update(realpathSync(projectRoot)).digest('hex');
199
199
  const dir = join(trustRoot, projectId);
200
200
  return { key: join(dir, 'anchor.key'), anchor: join(dir, 'state.anchor.json') };
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@claude-flow/cli",
3
- "version": "3.56.0",
3
+ "version": "3.56.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",