@claude-flow/cli 3.53.0 → 3.54.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.
- package/.claude/helpers/helpers.manifest.json +4 -4
- package/.claude/helpers/hook-handler.cjs +88 -6
- package/.claude/helpers/intelligence.cjs +104 -2
- package/README.md +141 -44
- package/catalog-manifest.json +2 -2
- package/dist/src/blocking-stdio.d.ts +11 -0
- package/dist/src/blocking-stdio.js +23 -0
- package/dist/src/commands/memory.js +9 -1
- package/dist/src/commands/session.js +11 -37
- package/dist/src/index.js +3 -0
- package/dist/src/init/executor.js +1 -0
- package/dist/src/init/helpers-generator.js +65 -3
- package/dist/src/mcp-tools/agentdb-tools.js +13 -1
- package/dist/src/mcp-tools/hooks-tools.js +1 -1
- package/dist/src/mcp-tools/session-tools.js +4 -1
- package/dist/src/memory/embedding-q8.d.ts +19 -0
- package/dist/src/memory/embedding-q8.js +38 -0
- package/dist/src/memory/graph-edge-writer.js +3 -2
- package/dist/src/memory/lucene-bm25.js +11 -19
- package/dist/src/memory/memory-bridge.d.ts +8 -0
- package/dist/src/memory/memory-bridge.js +21 -2
- package/dist/src/memory/memory-initializer.d.ts +5 -0
- package/dist/src/memory/memory-initializer.js +12 -5
- package/dist/src/memory/shared-sqlite.d.ts +7 -0
- package/dist/src/memory/shared-sqlite.js +45 -0
- package/dist/src/mods/probe.d.ts +7 -0
- package/dist/src/mods/probe.js +29 -1
- package/dist/src/production/monitoring.d.ts +2 -0
- package/dist/src/production/monitoring.js +8 -3
- package/dist/src/production/rate-limiter.js +8 -5
- package/dist/src/production/retry.js +2 -0
- package/dist/src/services/git-workspace-identity.js +20 -3
- package/dist/src/services/worker-queue.d.ts +1 -0
- package/dist/src/services/worker-queue.js +9 -1
- package/package.json +2 -2
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
type DbCtor = new (path: string, opts?: Record<string, unknown>) => any;
|
|
2
|
+
/** Test seam: pass `from` to resolve relative to another entry file. */
|
|
3
|
+
export declare function resolveAgentdbBetterSqlite3(from?: string): DbCtor | null;
|
|
4
|
+
/** Constructor shared with AgentDB when possible, else the CLI's own copy. */
|
|
5
|
+
export declare function loadBetterSqlite3(): Promise<DbCtor>;
|
|
6
|
+
export {};
|
|
7
|
+
//# sourceMappingURL=shared-sqlite.d.ts.map
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* #3693 — one better-sqlite3 identity per process.
|
|
3
|
+
*
|
|
4
|
+
* AgentDB (via @claude-flow/memory) owns a live native handle on memory.db. If
|
|
5
|
+
* the CLI opens its own handle through a DIFFERENT installed copy of
|
|
6
|
+
* better-sqlite3 (a nested `agentdb/node_modules/better-sqlite3` next to a
|
|
7
|
+
* hoisted one is the common layout), the two copies each statically link their
|
|
8
|
+
* own SQLite and keep separate per-process file bookkeeping. Closing the CLI's
|
|
9
|
+
* handle (graph-edge idle release: wal_checkpoint(TRUNCATE) + close) then looks
|
|
10
|
+
* like "last connection" to that copy and deletes the -wal/-shm sidecars out
|
|
11
|
+
* from under AgentDB's still-open handle.
|
|
12
|
+
*
|
|
13
|
+
* Resolve the constructor from the same place AgentDB resolves it, so both
|
|
14
|
+
* share one SQLite instance (and its connection bookkeeping). Falls back to the
|
|
15
|
+
* CLI's own copy when agentdb cannot be resolved.
|
|
16
|
+
*/
|
|
17
|
+
import { createRequire } from 'node:module';
|
|
18
|
+
let cached;
|
|
19
|
+
/** Test seam: pass `from` to resolve relative to another entry file. */
|
|
20
|
+
export function resolveAgentdbBetterSqlite3(from) {
|
|
21
|
+
if (!from && cached !== undefined)
|
|
22
|
+
return cached;
|
|
23
|
+
let found = null;
|
|
24
|
+
try {
|
|
25
|
+
const own = createRequire(import.meta.url);
|
|
26
|
+
const agentdbEntry = from ?? own.resolve('agentdb');
|
|
27
|
+
const mod = createRequire(agentdbEntry)('better-sqlite3');
|
|
28
|
+
found = (mod?.default ?? mod);
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
found = null;
|
|
32
|
+
}
|
|
33
|
+
if (!from)
|
|
34
|
+
cached = found;
|
|
35
|
+
return found;
|
|
36
|
+
}
|
|
37
|
+
/** Constructor shared with AgentDB when possible, else the CLI's own copy. */
|
|
38
|
+
export async function loadBetterSqlite3() {
|
|
39
|
+
const shared = resolveAgentdbBetterSqlite3();
|
|
40
|
+
if (shared)
|
|
41
|
+
return shared;
|
|
42
|
+
const mod = 'better-sqlite3';
|
|
43
|
+
return (await import(mod)).default;
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=shared-sqlite.js.map
|
package/dist/src/mods/probe.d.ts
CHANGED
|
@@ -29,5 +29,12 @@ export interface ProbeInputs {
|
|
|
29
29
|
/** Reads for the plugin-resolution findings; the real filesystem when absent. */
|
|
30
30
|
fs?: ReadFs;
|
|
31
31
|
}
|
|
32
|
+
declare const LEGACY_EVENTS: {
|
|
33
|
+
readonly route: "UserPromptSubmit";
|
|
34
|
+
readonly 'post-edit': "PostToolUse";
|
|
35
|
+
};
|
|
36
|
+
/** Which of route/post-edit the settings run through the CLI's own `hooks` command, in any of the files. */
|
|
37
|
+
export declare function legacyCliHooks(files: readonly string[]): Array<keyof typeof LEGACY_EVENTS>;
|
|
32
38
|
export declare function probeMods(inputs: ProbeInputs): Finding[];
|
|
39
|
+
export {};
|
|
33
40
|
//# sourceMappingURL=probe.d.ts.map
|
package/dist/src/mods/probe.js
CHANGED
|
@@ -33,6 +33,26 @@ export function managedSettingsPath(os = platform()) {
|
|
|
33
33
|
return 'C:\\ProgramData\\ClaudeCode\\managed-settings.json';
|
|
34
34
|
return '/etc/claude-code/managed-settings.json';
|
|
35
35
|
}
|
|
36
|
+
const LEGACY_EVENTS = { route: 'UserPromptSubmit', 'post-edit': 'PostToolUse' };
|
|
37
|
+
/** Which of route/post-edit the settings run through the CLI's own `hooks` command, in any of the files. */
|
|
38
|
+
export function legacyCliHooks(files) {
|
|
39
|
+
const commands = (event) => files.flatMap((f) => {
|
|
40
|
+
let groups;
|
|
41
|
+
try {
|
|
42
|
+
groups = get(readSettingsFile(f), 'hooks', event);
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return [];
|
|
46
|
+
}
|
|
47
|
+
return Array.isArray(groups)
|
|
48
|
+
? groups.flatMap((g) => (Array.isArray(g?.hooks) ? g.hooks.map((h) => h?.command).filter((c) => typeof c === 'string') : []))
|
|
49
|
+
: [];
|
|
50
|
+
});
|
|
51
|
+
return Object.keys(LEGACY_EVENTS).filter((sub) => {
|
|
52
|
+
const word = new RegExp(`(?:^|[\\s/"'])(?:@claude-flow/cli|claude-flow|ruflo)(?:@[\\w.-]+)?["']?\\s+hooks\\s+${sub}(?![\\w-])`);
|
|
53
|
+
return commands(LEGACY_EVENTS[sub]).some((c) => word.test(c));
|
|
54
|
+
});
|
|
55
|
+
}
|
|
36
56
|
export function probeMods(inputs) {
|
|
37
57
|
const root = resolve(inputs.projectRoot);
|
|
38
58
|
const home = inputs.home ?? homedir();
|
|
@@ -104,6 +124,14 @@ export function probeMods(inputs) {
|
|
|
104
124
|
findings.push(managedOnly !== undefined && managedOnly !== false
|
|
105
125
|
? { name: 'managed policy', status: 'warn', message: 'allowManagedModsOnly is set: Claude Code refuses user mods; classic hooks stay in charge' }
|
|
106
126
|
: { name: 'managed policy', status: 'pass', message: managed === undefined ? 'no managed settings' : 'user mods allowed (allowManagedModsOnly not set)' });
|
|
127
|
+
// 4a. Settings that still run the CLI's own `hooks route|post-edit` (what init
|
|
128
|
+
// wrote before hook-handler.cjs). That command never reads the handshake, so
|
|
129
|
+
// the mod stands down for those events (plugins/ruflo-mods/hooks/ownership.ts,
|
|
130
|
+
// legacyCliConfigured: keep the two patterns in step).
|
|
131
|
+
const legacy = legacyCliHooks(files);
|
|
132
|
+
if (legacy.length) {
|
|
133
|
+
findings.push({ name: 'legacy CLI hooks', status: 'warn', message: `settings run the CLI's own \`hooks ${legacy.join('` and `hooks ')}\`: the mod stands down for ${legacy.join(' and ')}, which that command keeps`, fix: 'ruflo init upgrade --settings (regenerate the settings hooks)' });
|
|
134
|
+
}
|
|
107
135
|
// 4. Every classic helper a hook could run honours the handshake (the mod's
|
|
108
136
|
// own rule, plugins/ruflo-mods/hooks/session.ts); else the mod stands down.
|
|
109
137
|
const helpers = [...new Set([join(root, '.claude', 'helpers', 'hook-handler.cjs'), join(home, '.claude', 'helpers', 'hook-handler.cjs')])].filter(existsSync);
|
|
@@ -116,7 +144,7 @@ export function probeMods(inputs) {
|
|
|
116
144
|
}
|
|
117
145
|
});
|
|
118
146
|
findings.push(helpers.length === 0
|
|
119
|
-
? { name: 'classic handshake', status: 'pass', message: 'no hook-handler.cjs: the mod owns route and post-edit outright' }
|
|
147
|
+
? { name: 'classic handshake', status: 'pass', message: legacy.length ? 'no hook-handler.cjs (see legacy CLI hooks)' : 'no hook-handler.cjs: the mod owns route and post-edit outright' }
|
|
120
148
|
: stale.length === 0
|
|
121
149
|
? { name: 'classic handshake', status: 'pass', message: `${helpers.join(', ')} hand route and post-edit to the mod while it runs` }
|
|
122
150
|
: { name: 'classic handshake', status: 'warn', message: `${stale.join(', ')} predate${stale.length === 1 ? 's' : ''} the handshake: the mod stands down and classic hooks keep route/post-edit`, fix: 'ruflo init --upgrade (refresh helpers in the project and in ~/.claude)' });
|
|
@@ -243,6 +243,7 @@ export class MonitoringHooks {
|
|
|
243
243
|
* Get metrics for a specific name
|
|
244
244
|
*/
|
|
245
245
|
getMetrics(name, since) {
|
|
246
|
+
this.expireMetrics();
|
|
246
247
|
let filtered = this.metrics.filter(m => m.name === name);
|
|
247
248
|
if (since) {
|
|
248
249
|
filtered = filtered.filter(m => m.timestamp >= since);
|
|
@@ -253,6 +254,7 @@ export class MonitoringHooks {
|
|
|
253
254
|
* Get all metrics summary
|
|
254
255
|
*/
|
|
255
256
|
getMetricsSummary() {
|
|
257
|
+
this.expireMetrics();
|
|
256
258
|
const summary = {};
|
|
257
259
|
for (const metric of this.metrics) {
|
|
258
260
|
if (!summary[metric.name]) {
|
|
@@ -320,11 +322,14 @@ export class MonitoringHooks {
|
|
|
320
322
|
}
|
|
321
323
|
}
|
|
322
324
|
}
|
|
325
|
+
/** Drop metrics older than retentionMs (also applied on reads, #3855). */
|
|
326
|
+
expireMetrics() {
|
|
327
|
+
const cutoff = Date.now() - this.config.retentionMs;
|
|
328
|
+
this.metrics = this.metrics.filter(m => m.timestamp > cutoff);
|
|
329
|
+
}
|
|
323
330
|
cleanupMetrics() {
|
|
324
|
-
const now = Date.now();
|
|
325
|
-
const cutoff = now - this.config.retentionMs;
|
|
326
331
|
// Remove old metrics
|
|
327
|
-
this.
|
|
332
|
+
this.expireMetrics();
|
|
328
333
|
// Limit total metrics
|
|
329
334
|
if (this.metrics.length > this.config.maxMetrics) {
|
|
330
335
|
this.metrics = this.metrics.slice(-this.config.maxMetrics);
|
|
@@ -41,12 +41,15 @@ export class RateLimiter {
|
|
|
41
41
|
const limits = this.getLimits(operation);
|
|
42
42
|
const now = Date.now();
|
|
43
43
|
// Get or create bucket
|
|
44
|
-
const
|
|
44
|
+
const trackedUser = userId && this.config.perUserLimits ? userId : undefined;
|
|
45
|
+
const bucketKey = trackedUser
|
|
45
46
|
? `${operation}:${userId}`
|
|
46
47
|
: `global:${operation}`;
|
|
47
48
|
let bucket = this.buckets.get(bucketKey);
|
|
48
49
|
if (!bucket) {
|
|
49
50
|
bucket = this.createBucket();
|
|
51
|
+
bucket.operation = operation;
|
|
52
|
+
bucket.userId = trackedUser;
|
|
50
53
|
this.buckets.set(bucketKey, bucket);
|
|
51
54
|
this.cleanupBuckets();
|
|
52
55
|
}
|
|
@@ -133,10 +136,10 @@ export class RateLimiter {
|
|
|
133
136
|
getStats() {
|
|
134
137
|
const operationCounts = new Map();
|
|
135
138
|
const users = new Set();
|
|
136
|
-
for (const
|
|
137
|
-
const
|
|
138
|
-
if (userId)
|
|
139
|
-
users.add(userId);
|
|
139
|
+
for (const bucket of this.buckets.values()) {
|
|
140
|
+
const operation = bucket.operation;
|
|
141
|
+
if (bucket.userId)
|
|
142
|
+
users.add(bucket.userId);
|
|
140
143
|
const current = operationCounts.get(operation) || 0;
|
|
141
144
|
operationCounts.set(operation, current + bucket.requests.length);
|
|
142
145
|
}
|
|
@@ -54,6 +54,8 @@ function calculateDelay(attempt, config, strategy = 'exponential') {
|
|
|
54
54
|
const jitterRange = delay * config.jitter;
|
|
55
55
|
delay += (Math.random() - 0.5) * 2 * jitterRange;
|
|
56
56
|
}
|
|
57
|
+
// Enforce the configured maximum on the final (jittered) delay
|
|
58
|
+
delay = Math.min(delay, config.maxDelayMs);
|
|
57
59
|
return Math.round(Math.max(0, delay));
|
|
58
60
|
}
|
|
59
61
|
/**
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
import { createHash } from 'crypto';
|
|
25
25
|
import { resolve } from 'path';
|
|
26
26
|
import * as fs from 'fs';
|
|
27
|
-
import { safeGitTextSync } from '@claude-flow/security/safe-git';
|
|
27
|
+
import { safeGitBufferSync, safeGitTextSync } from '@claude-flow/security/safe-git';
|
|
28
28
|
const GIT_TIMEOUT_MS = 3000;
|
|
29
29
|
function git(cwd, ...args) {
|
|
30
30
|
try {
|
|
@@ -37,6 +37,23 @@ function git(cwd, ...args) {
|
|
|
37
37
|
return null;
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* Path-valued git output: git terminates the record with a single LF, so strip
|
|
42
|
+
* only that. A full trim() would corrupt valid paths ending in whitespace.
|
|
43
|
+
*/
|
|
44
|
+
function gitPath(cwd, ...args) {
|
|
45
|
+
try {
|
|
46
|
+
const out = safeGitBufferSync(cwd, args, {
|
|
47
|
+
timeoutMs: GIT_TIMEOUT_MS,
|
|
48
|
+
stderr: 'ignore',
|
|
49
|
+
}).toString('utf8');
|
|
50
|
+
const rec = out.endsWith('\n') ? out.slice(0, -1) : out;
|
|
51
|
+
return rec.length > 0 ? rec : null;
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
40
57
|
function sha256(input) {
|
|
41
58
|
return createHash('sha256').update(input).digest('hex');
|
|
42
59
|
}
|
|
@@ -50,7 +67,7 @@ export function resolveGitWorkspaceIdentity(dir) {
|
|
|
50
67
|
const resolved = resolve(dir);
|
|
51
68
|
let stable = identityCache.get(resolved);
|
|
52
69
|
if (!stable) {
|
|
53
|
-
const worktreeRoot =
|
|
70
|
+
const worktreeRoot = gitPath(resolved, 'rev-parse', '--show-toplevel');
|
|
54
71
|
if (!worktreeRoot) {
|
|
55
72
|
stable = {
|
|
56
73
|
worktreeRoot: resolved,
|
|
@@ -63,7 +80,7 @@ export function resolveGitWorkspaceIdentity(dir) {
|
|
|
63
80
|
else {
|
|
64
81
|
// --git-common-dir may be relative to the worktree root (git < 2.31
|
|
65
82
|
// and some invocation contexts) — resolve against it.
|
|
66
|
-
const rawCommon =
|
|
83
|
+
const rawCommon = gitPath(worktreeRoot, 'rev-parse', '--git-common-dir') ?? '.git';
|
|
67
84
|
const commonGitDir = resolve(worktreeRoot, rawCommon);
|
|
68
85
|
stable = {
|
|
69
86
|
worktreeRoot,
|
|
@@ -162,6 +162,7 @@ export class WorkerQueue extends EventEmitter {
|
|
|
162
162
|
workerId;
|
|
163
163
|
heartbeatTimer;
|
|
164
164
|
processingTasks = new Set();
|
|
165
|
+
retryTimers = new Set();
|
|
165
166
|
isShuttingDown = false;
|
|
166
167
|
maxConcurrent = 1;
|
|
167
168
|
initialized = false;
|
|
@@ -286,10 +287,13 @@ export class WorkerQueue extends EventEmitter {
|
|
|
286
287
|
this.store.setTask(taskId, task);
|
|
287
288
|
// Re-queue with delay (exponential backoff)
|
|
288
289
|
const delay = Math.min(30000, 1000 * Math.pow(2, task.retryCount));
|
|
289
|
-
|
|
290
|
+
// Tracked so shutdown() can release it
|
|
291
|
+
const timer = setTimeout(() => {
|
|
292
|
+
this.retryTimers.delete(timer);
|
|
290
293
|
const queueName = this.getQueueName(task.workerType);
|
|
291
294
|
this.store.pushToQueue(queueName, taskId, PRIORITY_SCORES[task.priority]);
|
|
292
295
|
}, delay);
|
|
296
|
+
this.retryTimers.add(timer);
|
|
293
297
|
this.emit('taskRetrying', { taskId, retryCount: task.retryCount, delay });
|
|
294
298
|
}
|
|
295
299
|
else {
|
|
@@ -469,6 +473,10 @@ export class WorkerQueue extends EventEmitter {
|
|
|
469
473
|
for (const taskId of this.processingTasks) {
|
|
470
474
|
await this.fail(taskId, 'Worker shutdown', false);
|
|
471
475
|
}
|
|
476
|
+
// Release scheduled retry timers (they would hold the process open)
|
|
477
|
+
for (const timer of this.retryTimers)
|
|
478
|
+
clearTimeout(timer);
|
|
479
|
+
this.retryTimers.clear();
|
|
472
480
|
// Stop store cleanup
|
|
473
481
|
this.store.stopCleanup();
|
|
474
482
|
await this.unregisterWorker();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@claude-flow/cli",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.54.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",
|
|
@@ -104,7 +104,7 @@
|
|
|
104
104
|
"@claude-flow/neural": "3.0.0",
|
|
105
105
|
"@claude-flow/plugin-agent-federation": "1.0.0",
|
|
106
106
|
"@claude-flow/security": "3.0.1",
|
|
107
|
-
"@claude-flow/shared": "3.0.
|
|
107
|
+
"@claude-flow/shared": "3.0.1",
|
|
108
108
|
"@iarna/toml": "^2.2.5",
|
|
109
109
|
"@noble/ed25519": "2.3.0",
|
|
110
110
|
"@ruvector/rabitq-wasm": "0.1.0",
|