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.
- package/.claude/.proven-config-version +1 -0
- package/.claude/helpers/auto-memory-hook.mjs +6 -2
- package/.claude/helpers/hook-handler.cjs +7 -4
- package/.claude/helpers/learning-service.mjs +4 -2
- package/.claude/helpers/memory.cjs +1 -1
- package/.claude/helpers/metrics-db.mjs +4 -2
- package/.claude/helpers/router.cjs +1 -1
- package/.claude/helpers/session.cjs +1 -1
- package/.claude/proven-config.json +42 -0
- package/.claude-plugin/marketplace.json +16 -1
- package/README.md +1 -53
- package/README.zh-CN.md +1 -53
- package/node_modules/@claude-flow/codex/package.json +1 -1
- package/node_modules/@claude-flow/mcp/dist/tool-registry.d.ts.map +1 -1
- package/node_modules/@claude-flow/mcp/dist/tool-registry.js +11 -3
- package/node_modules/@claude-flow/mcp/dist/tool-registry.js.map +1 -1
- package/node_modules/@claude-flow/mcp/package.json +6 -3
- package/node_modules/@claude-flow/plugin-agent-federation/package.json +3 -3
- package/node_modules/@claude-flow/security/dist/policy/engine.d.ts +2 -6
- package/node_modules/@claude-flow/security/dist/policy/engine.d.ts.map +1 -1
- package/node_modules/@claude-flow/security/dist/policy/engine.js +35 -1
- package/node_modules/@claude-flow/security/dist/policy/engine.js.map +1 -1
- package/node_modules/@claude-flow/security/dist/policy/types.d.ts +18 -0
- package/node_modules/@claude-flow/security/dist/policy/types.d.ts.map +1 -1
- package/node_modules/@claude-flow/security/package.json +2 -3
- package/package.json +7 -7
- package/v3/@claude-flow/cli/README.md +1 -53
- package/v3/@claude-flow/cli/bin/cli.js +4 -1
- package/v3/@claude-flow/cli/catalog-manifest.json +4 -4
- package/v3/@claude-flow/cli/dist/src/commands/analyze.js +2 -2
- package/v3/@claude-flow/cli/dist/src/commands/config.js +5 -7
- package/v3/@claude-flow/cli/dist/src/commands/doctor.d.ts +19 -1
- package/v3/@claude-flow/cli/dist/src/commands/doctor.js +69 -8
- package/v3/@claude-flow/cli/dist/src/commands/hooks.js +22 -9
- package/v3/@claude-flow/cli/dist/src/commands/memory.js +119 -34
- package/v3/@claude-flow/cli/dist/src/commands/metaharness.js +1 -0
- package/v3/@claude-flow/cli/dist/src/commands/plugins.js +44 -7
- package/v3/@claude-flow/cli/dist/src/commands/policy.js +5 -2
- package/v3/@claude-flow/cli/dist/src/commands/security.js +113 -55
- package/v3/@claude-flow/cli/dist/src/commands/session.js +132 -25
- package/v3/@claude-flow/cli/dist/src/commands/swarm.js +11 -11
- package/v3/@claude-flow/cli/dist/src/commands/task.js +5 -4
- package/v3/@claude-flow/cli/dist/src/index.js +10 -1
- package/v3/@claude-flow/cli/dist/src/init/executor.js +11 -5
- package/v3/@claude-flow/cli/dist/src/init/helper-companions.d.ts +3 -0
- package/v3/@claude-flow/cli/dist/src/init/helper-companions.js +34 -0
- package/v3/@claude-flow/cli/dist/src/init/helper-integrity.d.ts +21 -0
- package/v3/@claude-flow/cli/dist/src/init/helper-integrity.js +62 -0
- package/v3/@claude-flow/cli/dist/src/init/helper-refresh.d.ts +18 -11
- package/v3/@claude-flow/cli/dist/src/init/helper-refresh.js +56 -13
- package/v3/@claude-flow/cli/dist/src/init/helpers-generator.js +14 -12
- package/v3/@claude-flow/cli/dist/src/mcp-server.js +10 -3
- package/v3/@claude-flow/cli/dist/src/mcp-tools/agentdb-tools.js +29 -62
- package/v3/@claude-flow/cli/dist/src/mcp-tools/hooks-tools.d.ts +16 -7
- package/v3/@claude-flow/cli/dist/src/mcp-tools/hooks-tools.js +226 -51
- package/v3/@claude-flow/cli/dist/src/mcp-tools/memory-tools.d.ts +7 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/memory-tools.js +131 -40
- package/v3/@claude-flow/cli/dist/src/mcp-tools/policy-enforcer.d.ts +8 -1
- package/v3/@claude-flow/cli/dist/src/mcp-tools/policy-enforcer.js +80 -8
- package/v3/@claude-flow/cli/dist/src/mcp-tools/session-tools.d.ts +15 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/session-tools.js +270 -58
- package/v3/@claude-flow/cli/dist/src/memory/feedback-patterns.d.ts +13 -0
- package/v3/@claude-flow/cli/dist/src/memory/feedback-patterns.js +14 -0
- package/v3/@claude-flow/cli/dist/src/memory/live-memory-row.d.ts +3 -0
- package/v3/@claude-flow/cli/dist/src/memory/live-memory-row.js +5 -0
- package/v3/@claude-flow/cli/dist/src/memory/memory-bridge.d.ts +11 -20
- package/v3/@claude-flow/cli/dist/src/memory/memory-bridge.js +348 -134
- package/v3/@claude-flow/cli/dist/src/memory/memory-initializer.d.ts +9 -1
- package/v3/@claude-flow/cli/dist/src/memory/memory-initializer.js +47 -34
- package/v3/@claude-flow/cli/dist/src/plugins/manager.d.ts +37 -10
- package/v3/@claude-flow/cli/dist/src/plugins/manager.js +106 -20
- package/v3/@claude-flow/cli/dist/src/plugins/trust-policy.d.ts +61 -0
- package/v3/@claude-flow/cli/dist/src/plugins/trust-policy.js +84 -0
- package/v3/@claude-flow/cli/dist/src/ruvector/graph-analyzer.js +4 -1
- package/v3/@claude-flow/cli/dist/src/services/config-file-manager.js +30 -8
- package/v3/@claude-flow/cli/dist/src/services/memory-backup.d.ts +1 -1
- package/v3/@claude-flow/cli/dist/src/services/memory-backup.js +67 -27
- package/v3/@claude-flow/cli/dist/src/services/policy-runtime.d.ts +6 -0
- package/v3/@claude-flow/cli/dist/src/services/policy-runtime.js +27 -2
- package/v3/@claude-flow/cli/dist/src/services/worker-daemon.d.ts +2 -2
- package/v3/@claude-flow/cli/dist/src/services/worker-daemon.js +23 -14
- package/v3/@claude-flow/cli/package.json +9 -9
- package/v3/@claude-flow/guidance/package.json +5 -6
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
106
|
-
|
|
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
|
|
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
|
|
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,
|
|
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 =>
|
|
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 =>
|
|
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 =>
|
|
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
|
-
//
|
|
172
|
-
//
|
|
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
|
-
/*
|
|
235
|
+
/* use the sql.js verifier */
|
|
180
236
|
}
|
|
181
237
|
let chosen = null;
|
|
182
238
|
for (const file of snaps) {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.{
|
|
128
|
-
* Supports
|
|
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.{
|
|
343
|
-
* Supports
|
|
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
|
-
|
|
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 (
|
|
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
|
-
//
|
|
394
|
-
|
|
395
|
-
const
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
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 =
|
|
403
|
-
const rawIdle =
|
|
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 =
|
|
414
|
+
const rawAiEnabled = get('daemon.aiWorkers.enabled');
|
|
406
415
|
return {
|
|
407
|
-
autoStart: typeof
|
|
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.
|
|
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
|
|
101
|
+
"@claude-flow/cli-core": "3.7.0",
|
|
102
102
|
"@claude-flow/codex": "3.0.3",
|
|
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.
|
|
107
|
-
"@claude-flow/shared": "3.0.0
|
|
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
|
|
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
|
|
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
|
|
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
|
|
144
|
-
"@claude-flow/memory": "^3.0.0
|
|
145
|
-
"@claude-flow/shared": "^3.0.0
|
|
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
|
|
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
|
}
|