@claude-flow/cli 3.56.3 → 3.56.4
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/auto-memory-hook.mjs +13 -8
- package/.claude/helpers/helpers.manifest.json +5 -5
- package/.claude/helpers/hook-handler.cjs +19 -14
- package/.claude/helpers/learning-service.mjs +5 -2
- package/.claude/helpers/session.cjs +48 -12
- package/.claude/helpers/statusline.cjs +14 -4
- package/catalog-manifest.json +2 -2
- package/dist/src/commands/doctor.js +7 -13
- package/dist/src/commands/hooks.js +3 -6
- package/dist/src/commands/init.js +3 -10
- package/dist/src/commands/metaharness.js +9 -14
- package/dist/src/commands/plugins.js +81 -8
- package/dist/src/commands/security-code-patterns.d.ts +40 -0
- package/dist/src/commands/security-code-patterns.js +192 -0
- package/dist/src/commands/security.js +8 -11
- package/dist/src/commands/status.js +8 -10
- package/dist/src/init/executor.d.ts +1 -1
- package/dist/src/init/executor.js +25 -16
- package/dist/src/init/helper-commands.d.ts +18 -0
- package/dist/src/init/helper-commands.js +142 -0
- package/dist/src/init/helpers-generator.js +128 -29
- package/dist/src/init/settings-generator.js +36 -103
- package/dist/src/mcp-tools/metaharness-tools.js +7 -10
- package/dist/src/mcp-tools/task-tools.js +24 -3
- package/dist/src/mcp-tools/testgen-tools.js +8 -12
- package/dist/src/mcp-tools/transfer-tools.js +9 -8
- package/dist/src/mcp-tools/workflow-tools.js +10 -4
- package/dist/src/memory/memory-initializer.js +2 -19
- package/dist/src/memory/shared-sqlite.d.ts +6 -0
- package/dist/src/memory/shared-sqlite.js +35 -7
- package/dist/src/missions/schemas.d.ts +125 -125
- package/dist/src/mods/command-registry/schema.d.ts +199 -199
- package/dist/src/plugins/manager.d.ts +3 -1
- package/dist/src/plugins/manager.js +31 -10
- package/dist/src/plugins/own-install.d.ts +15 -0
- package/dist/src/plugins/own-install.js +51 -0
- package/dist/src/plugins/store/discovery.d.ts +26 -2
- package/dist/src/plugins/store/discovery.js +108 -81
- package/dist/src/plugins/store/index.d.ts +1 -1
- package/dist/src/plugins/store/index.js +1 -1
- package/dist/src/plugins/store/types.d.ts +5 -0
- package/dist/src/plugins/trust-policy.d.ts +13 -0
- package/dist/src/plugins/trust-policy.js +29 -0
- package/dist/src/ruvector/graph-analyzer.js +18 -6
- package/dist/src/transfer/ipfs/client.js +3 -1
- package/node_modules/@claude-flow/codex/dist/cli.js +0 -0
- package/node_modules/@claude-flow/mcp/dist/resource-registry.d.ts +1 -0
- package/node_modules/@claude-flow/mcp/dist/resource-registry.d.ts.map +1 -1
- package/node_modules/@claude-flow/mcp/dist/resource-registry.js +10 -4
- package/node_modules/@claude-flow/mcp/dist/resource-registry.js.map +1 -1
- package/node_modules/@claude-flow/mcp/dist/sampling.d.ts.map +1 -1
- package/node_modules/@claude-flow/mcp/dist/sampling.js +13 -6
- package/node_modules/@claude-flow/mcp/dist/sampling.js.map +1 -1
- package/node_modules/@claude-flow/mcp/dist/task-manager.d.ts.map +1 -1
- package/node_modules/@claude-flow/mcp/dist/task-manager.js +10 -1
- package/node_modules/@claude-flow/mcp/dist/task-manager.js.map +1 -1
- package/node_modules/@claude-flow/mcp/dist/tool-registry.d.ts +1 -0
- package/node_modules/@claude-flow/mcp/dist/tool-registry.d.ts.map +1 -1
- package/node_modules/@claude-flow/mcp/dist/tool-registry.js +17 -11
- package/node_modules/@claude-flow/mcp/dist/tool-registry.js.map +1 -1
- package/node_modules/@claude-flow/mcp/package.json +1 -1
- package/node_modules/@claude-flow/plugin-agent-federation/dist/bin.js +0 -0
- package/node_modules/@claude-flow/security/dist/index.d.ts +1 -1
- package/node_modules/@claude-flow/security/dist/index.d.ts.map +1 -1
- package/node_modules/@claude-flow/security/dist/index.js +1 -1
- package/node_modules/@claude-flow/security/dist/index.js.map +1 -1
- package/node_modules/@claude-flow/security/dist/plugins/integrity-verifier.d.ts +9 -1
- package/node_modules/@claude-flow/security/dist/plugins/integrity-verifier.d.ts.map +1 -1
- package/node_modules/@claude-flow/security/dist/plugins/integrity-verifier.js +15 -2
- package/node_modules/@claude-flow/security/dist/plugins/integrity-verifier.js.map +1 -1
- package/node_modules/@claude-flow/security/package.json +1 -1
- package/package.json +5 -5
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* Creates .claude/settings.json with V3-optimized hook configurations
|
|
4
4
|
*/
|
|
5
5
|
import { detectPlatform } from './types.js';
|
|
6
|
+
import { helperHookCommand, helperScopeFor, helperStatusLineCommand } from './helper-commands.js';
|
|
6
7
|
/**
|
|
7
8
|
* Generate the complete settings.json content
|
|
8
9
|
*/
|
|
@@ -15,7 +16,7 @@ export function generateSettings(options) {
|
|
|
15
16
|
// fails to find its handler. Either bundle the helpers OR drop the hooks —
|
|
16
17
|
// the option this fix takes is the latter (minimal stays minimal).
|
|
17
18
|
if (options.components.settings && options.components.helpers) {
|
|
18
|
-
settings.hooks = generateHooksConfig(options.hooks);
|
|
19
|
+
settings.hooks = generateHooksConfig(options.hooks, helperScopeFor(options.targetDir));
|
|
19
20
|
}
|
|
20
21
|
// Add statusLine configuration if enabled
|
|
21
22
|
if (options.statusline.enabled) {
|
|
@@ -160,102 +161,34 @@ export function generateSettings(options) {
|
|
|
160
161
|
return settings;
|
|
161
162
|
}
|
|
162
163
|
/**
|
|
163
|
-
*
|
|
164
|
+
* Build a hook command that resolves to the right helpers dir for where the
|
|
165
|
+
* settings live (#1943). Project-level settings probe the project's helpers
|
|
166
|
+
* first and fall back to `$HOME/.claude/helpers`; user-level settings
|
|
167
|
+
* (`~/.claude/settings.json`) apply to every opened project, so they are
|
|
168
|
+
* pinned to `$HOME/.claude/helpers`. See helper-commands.ts.
|
|
164
169
|
*/
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
* Build a hook command that resolves to the right helpers dir on every
|
|
168
|
-
* install layout. `ruflo init` can land helpers either project-locally
|
|
169
|
-
* (`<project>/.claude/helpers/…`, when run from a project root) or globally
|
|
170
|
-
* (`$HOME/.claude/helpers/…`, when settings.json gets merged into the
|
|
171
|
-
* user-level Claude Code config). The earlier `${CLAUDE_PROJECT_DIR:-.}`
|
|
172
|
-
* form assumed project-local — so any global-install user hit
|
|
173
|
-
* `MODULE_NOT_FOUND` on every Bash/Edit/Session hook (#1943).
|
|
174
|
-
*
|
|
175
|
-
* The fix is a tiny POSIX `sh` probe: try `$CLAUDE_PROJECT_DIR/.claude/...`
|
|
176
|
-
* first, fall back to `$HOME/.claude/...` if it's missing. Both modes work,
|
|
177
|
-
* the global install never crashes, and project-local overrides still take
|
|
178
|
-
* precedence when present. On Windows, the same probe via `cmd /c` (the %~%
|
|
179
|
-
* fallback uses `IF EXIST`).
|
|
180
|
-
*/
|
|
181
|
-
function hookCmd(script, subcommand) {
|
|
182
|
-
if (IS_WINDOWS) {
|
|
183
|
-
// cmd.exe equivalent of the sh probe below. `IF EXIST` checks the
|
|
184
|
-
// project-local path; falls back to %USERPROFILE% if missing.
|
|
185
|
-
return `cmd /c "IF EXIST \"%CLAUDE_PROJECT_DIR%\\${script.replace(/\//g, '\\')}\" (node \"%CLAUDE_PROJECT_DIR%\\${script.replace(/\//g, '\\')}\" ${subcommand}) ELSE (node \"%USERPROFILE%\\${script.replace(/\//g, '\\')}\" ${subcommand})"`;
|
|
186
|
-
}
|
|
187
|
-
// POSIX sh: prefer project-local helpers, fall back to $HOME/.claude/.
|
|
188
|
-
// The fallback handles `ruflo init`'s global-install path where helpers
|
|
189
|
-
// live at `$HOME/.claude/helpers/` but Claude Code still sets
|
|
190
|
-
// `CLAUDE_PROJECT_DIR` to the *project* root (which has no helpers).
|
|
191
|
-
// eslint-disable-next-line no-template-curly-in-string
|
|
192
|
-
const projVar = '${CLAUDE_PROJECT_DIR:-.}';
|
|
193
|
-
// eslint-disable-next-line no-template-curly-in-string
|
|
194
|
-
const homeVar = '${HOME}';
|
|
195
|
-
return `sh -c 'D="${projVar}"; [ -f "$D/${script}" ] || D="${homeVar}"; exec node "$D/${script}" ${subcommand}'`;
|
|
170
|
+
function hookCmd(script, subcommand, scope) {
|
|
171
|
+
return helperHookCommand(script, subcommand, scope);
|
|
196
172
|
}
|
|
197
173
|
/** Shorthand for CJS hook-handler commands */
|
|
198
|
-
function hookHandlerCmd(subcommand) {
|
|
199
|
-
return hookCmd('.claude/helpers/hook-handler.cjs', subcommand);
|
|
174
|
+
function hookHandlerCmd(subcommand, scope) {
|
|
175
|
+
return hookCmd('.claude/helpers/hook-handler.cjs', subcommand, scope);
|
|
200
176
|
}
|
|
201
177
|
/** Shorthand for ESM auto-memory-hook commands */
|
|
202
|
-
function autoMemoryCmd(subcommand) {
|
|
203
|
-
return hookCmd('.claude/helpers/auto-memory-hook.mjs', subcommand);
|
|
178
|
+
function autoMemoryCmd(subcommand, scope) {
|
|
179
|
+
return hookCmd('.claude/helpers/auto-memory-hook.mjs', subcommand, scope);
|
|
204
180
|
}
|
|
205
181
|
/**
|
|
206
182
|
* Generate statusLine configuration for Claude Code
|
|
207
|
-
* Uses local helper script for cross-platform compatibility (no npx cold-start)
|
|
183
|
+
* Uses local helper script for cross-platform compatibility (no npx cold-start).
|
|
184
|
+
* Claude Code pipes JSON session data to the script via stdin; valid fields
|
|
185
|
+
* are type, command and padding. Platform handling (#1948, #1973) lives in
|
|
186
|
+
* helperStatusLineCommand().
|
|
208
187
|
*/
|
|
209
|
-
function generateStatusLineConfig(
|
|
210
|
-
// Claude Code pipes JSON session data to the script via stdin.
|
|
211
|
-
// Valid fields: type, command, padding (optional).
|
|
212
|
-
// The script runs after each assistant message (debounced 300ms).
|
|
213
|
-
//
|
|
214
|
-
// ruflo#1948 + #1973: the previous `sh -c 'D="${CLAUDE_PROJECT_DIR:-.}"; …'`
|
|
215
|
-
// form requires a POSIX shell on PATH. On native Windows (no
|
|
216
|
-
// Git-Bash / WSL), `sh` either isn't found or its quoting gets
|
|
217
|
-
// mangled, producing weird artifacts like files named `0)` or
|
|
218
|
-
// `toastr.error('ESD...` from misparsed tokens leaking back into
|
|
219
|
-
// the file system. NEVER use `cmd /c` for statusline — Claude Code
|
|
220
|
-
// manages stdin directly for statusline commands and `cmd /c`
|
|
221
|
-
// blocks the stdin forwarding.
|
|
222
|
-
//
|
|
223
|
-
// Solution: emit a platform-appropriate command at init time.
|
|
224
|
-
// POSIX: `sh -c 'D="…"; … exec node "$D/<script>"'` (existing)
|
|
225
|
-
// Windows: a Node.js one-liner that resolves the path internally
|
|
226
|
-
// using `process.env.CLAUDE_PROJECT_DIR` with a HOME
|
|
227
|
-
// fallback — no shell-quoting hazards because the
|
|
228
|
-
// resolution happens inside node, not in the shell.
|
|
229
|
-
const script = '.claude/helpers/statusline.cjs';
|
|
230
|
-
if (process.platform === 'win32') {
|
|
231
|
-
// The Node CLI's `-e` flag avoids all shell-quoting pitfalls.
|
|
232
|
-
// We write the path resolution in JS:
|
|
233
|
-
// const fs = require('fs'); const p = require('path');
|
|
234
|
-
// const d = process.env.CLAUDE_PROJECT_DIR || '.';
|
|
235
|
-
// const f = p.join(d, '.claude/helpers/statusline.cjs');
|
|
236
|
-
// const home = process.env.USERPROFILE || process.env.HOME || '.';
|
|
237
|
-
// const h = p.join(home, '.claude/helpers/statusline.cjs');
|
|
238
|
-
// require(fs.existsSync(f) ? f : h);
|
|
239
|
-
// …compressed onto one line. Double-quotes around the -e arg are
|
|
240
|
-
// safe on cmd.exe; the inner JS uses single-quotes for strings.
|
|
241
|
-
const js = "const fs=require('fs'),p=require('path');" +
|
|
242
|
-
`const d=process.env.CLAUDE_PROJECT_DIR||'.';` +
|
|
243
|
-
`const f=p.join(d,'${script}');` +
|
|
244
|
-
`const h=p.join(process.env.USERPROFILE||process.env.HOME||'.', '${script}');` +
|
|
245
|
-
'require(fs.existsSync(f)?f:h);';
|
|
246
|
-
return {
|
|
247
|
-
type: 'command',
|
|
248
|
-
command: `node -e "${js}"`,
|
|
249
|
-
};
|
|
250
|
-
}
|
|
251
|
-
// Same project-local / $HOME fallback as `hookCmd()` (see #1943).
|
|
252
|
-
// eslint-disable-next-line no-template-curly-in-string
|
|
253
|
-
const projVar = '${CLAUDE_PROJECT_DIR:-.}';
|
|
254
|
-
// eslint-disable-next-line no-template-curly-in-string
|
|
255
|
-
const homeVar = '${HOME}';
|
|
188
|
+
function generateStatusLineConfig(options) {
|
|
256
189
|
return {
|
|
257
190
|
type: 'command',
|
|
258
|
-
command:
|
|
191
|
+
command: helperStatusLineCommand(helperScopeFor(options.targetDir)),
|
|
259
192
|
};
|
|
260
193
|
}
|
|
261
194
|
/**
|
|
@@ -264,7 +197,7 @@ function generateStatusLineConfig(_options) {
|
|
|
264
197
|
* All hooks invoke scripts directly via `node <script> <subcommand>`,
|
|
265
198
|
* working identically on Windows, macOS, and Linux.
|
|
266
199
|
*/
|
|
267
|
-
function generateHooksConfig(config) {
|
|
200
|
+
function generateHooksConfig(config, scope) {
|
|
268
201
|
const hooks = {};
|
|
269
202
|
// Node.js scripts handle errors internally via try/catch.
|
|
270
203
|
// No shell-level error suppression needed (2>/dev/null || true breaks Windows).
|
|
@@ -276,7 +209,7 @@ function generateHooksConfig(config) {
|
|
|
276
209
|
hooks: [
|
|
277
210
|
{
|
|
278
211
|
type: 'command',
|
|
279
|
-
command: hookHandlerCmd('pre-bash'),
|
|
212
|
+
command: hookHandlerCmd('pre-bash', scope),
|
|
280
213
|
timeout: config.timeout,
|
|
281
214
|
},
|
|
282
215
|
],
|
|
@@ -286,7 +219,7 @@ function generateHooksConfig(config) {
|
|
|
286
219
|
hooks: [
|
|
287
220
|
{
|
|
288
221
|
type: 'command',
|
|
289
|
-
command: hookHandlerCmd('pre-edit'),
|
|
222
|
+
command: hookHandlerCmd('pre-edit', scope),
|
|
290
223
|
timeout: config.timeout,
|
|
291
224
|
},
|
|
292
225
|
],
|
|
@@ -301,7 +234,7 @@ function generateHooksConfig(config) {
|
|
|
301
234
|
hooks: [
|
|
302
235
|
{
|
|
303
236
|
type: 'command',
|
|
304
|
-
command: hookHandlerCmd('post-edit'),
|
|
237
|
+
command: hookHandlerCmd('post-edit', scope),
|
|
305
238
|
timeout: 10000,
|
|
306
239
|
},
|
|
307
240
|
],
|
|
@@ -311,7 +244,7 @@ function generateHooksConfig(config) {
|
|
|
311
244
|
hooks: [
|
|
312
245
|
{
|
|
313
246
|
type: 'command',
|
|
314
|
-
command: hookHandlerCmd('post-bash'),
|
|
247
|
+
command: hookHandlerCmd('post-bash', scope),
|
|
315
248
|
timeout: config.timeout,
|
|
316
249
|
},
|
|
317
250
|
],
|
|
@@ -325,7 +258,7 @@ function generateHooksConfig(config) {
|
|
|
325
258
|
hooks: [
|
|
326
259
|
{
|
|
327
260
|
type: 'command',
|
|
328
|
-
command: hookHandlerCmd('route'),
|
|
261
|
+
command: hookHandlerCmd('route', scope),
|
|
329
262
|
timeout: 10000,
|
|
330
263
|
},
|
|
331
264
|
],
|
|
@@ -339,12 +272,12 @@ function generateHooksConfig(config) {
|
|
|
339
272
|
hooks: [
|
|
340
273
|
{
|
|
341
274
|
type: 'command',
|
|
342
|
-
command: hookHandlerCmd('session-restore'),
|
|
275
|
+
command: hookHandlerCmd('session-restore', scope),
|
|
343
276
|
timeout: 15000,
|
|
344
277
|
},
|
|
345
278
|
{
|
|
346
279
|
type: 'command',
|
|
347
|
-
command: autoMemoryCmd('import'),
|
|
280
|
+
command: autoMemoryCmd('import', scope),
|
|
348
281
|
timeout: 8000,
|
|
349
282
|
},
|
|
350
283
|
],
|
|
@@ -358,7 +291,7 @@ function generateHooksConfig(config) {
|
|
|
358
291
|
hooks: [
|
|
359
292
|
{
|
|
360
293
|
type: 'command',
|
|
361
|
-
command: hookHandlerCmd('session-end'),
|
|
294
|
+
command: hookHandlerCmd('session-end', scope),
|
|
362
295
|
timeout: 10000,
|
|
363
296
|
},
|
|
364
297
|
],
|
|
@@ -372,7 +305,7 @@ function generateHooksConfig(config) {
|
|
|
372
305
|
hooks: [
|
|
373
306
|
{
|
|
374
307
|
type: 'command',
|
|
375
|
-
command: autoMemoryCmd('sync'),
|
|
308
|
+
command: autoMemoryCmd('sync', scope),
|
|
376
309
|
timeout: 10000,
|
|
377
310
|
},
|
|
378
311
|
],
|
|
@@ -387,11 +320,11 @@ function generateHooksConfig(config) {
|
|
|
387
320
|
hooks: [
|
|
388
321
|
{
|
|
389
322
|
type: 'command',
|
|
390
|
-
command: hookHandlerCmd('compact-manual'),
|
|
323
|
+
command: hookHandlerCmd('compact-manual', scope),
|
|
391
324
|
},
|
|
392
325
|
{
|
|
393
326
|
type: 'command',
|
|
394
|
-
command: hookHandlerCmd('session-end'),
|
|
327
|
+
command: hookHandlerCmd('session-end', scope),
|
|
395
328
|
timeout: 5000,
|
|
396
329
|
},
|
|
397
330
|
],
|
|
@@ -401,11 +334,11 @@ function generateHooksConfig(config) {
|
|
|
401
334
|
hooks: [
|
|
402
335
|
{
|
|
403
336
|
type: 'command',
|
|
404
|
-
command: hookHandlerCmd('compact-auto'),
|
|
337
|
+
command: hookHandlerCmd('compact-auto', scope),
|
|
405
338
|
},
|
|
406
339
|
{
|
|
407
340
|
type: 'command',
|
|
408
|
-
command: hookHandlerCmd('session-end'),
|
|
341
|
+
command: hookHandlerCmd('session-end', scope),
|
|
409
342
|
timeout: 6000,
|
|
410
343
|
},
|
|
411
344
|
],
|
|
@@ -418,7 +351,7 @@ function generateHooksConfig(config) {
|
|
|
418
351
|
hooks: [
|
|
419
352
|
{
|
|
420
353
|
type: 'command',
|
|
421
|
-
command: hookHandlerCmd('status'),
|
|
354
|
+
command: hookHandlerCmd('status', scope),
|
|
422
355
|
timeout: 3000,
|
|
423
356
|
},
|
|
424
357
|
],
|
|
@@ -431,7 +364,7 @@ function generateHooksConfig(config) {
|
|
|
431
364
|
hooks: [
|
|
432
365
|
{
|
|
433
366
|
type: 'command',
|
|
434
|
-
command: hookHandlerCmd('post-task'),
|
|
367
|
+
command: hookHandlerCmd('post-task', scope),
|
|
435
368
|
timeout: 5000,
|
|
436
369
|
},
|
|
437
370
|
],
|
|
@@ -444,7 +377,7 @@ function generateHooksConfig(config) {
|
|
|
444
377
|
hooks: [
|
|
445
378
|
{
|
|
446
379
|
type: 'command',
|
|
447
|
-
command: hookHandlerCmd('notify'),
|
|
380
|
+
command: hookHandlerCmd('notify', scope),
|
|
448
381
|
timeout: 3000,
|
|
449
382
|
},
|
|
450
383
|
],
|
|
@@ -51,6 +51,8 @@ import { spawn } from 'node:child_process';
|
|
|
51
51
|
import { existsSync, readFileSync } from 'node:fs';
|
|
52
52
|
import { join, dirname, resolve } from 'node:path';
|
|
53
53
|
import { fileURLToPath } from 'node:url';
|
|
54
|
+
import { homedir } from 'node:os';
|
|
55
|
+
import { ownPluginDirs } from '../plugins/own-install.js';
|
|
54
56
|
import { runFlywheelWorker } from '../services/harness-flywheel-runtime.js';
|
|
55
57
|
import { evaluatePolicyRequest } from '../services/policy-runtime.js';
|
|
56
58
|
import { listFlywheelReceipts, promoteFlywheelCandidate, readFlywheelTransactionState, resetSequentialEvidence, verifyFlywheelLedger, } from '../services/flywheel-transaction.js';
|
|
@@ -66,16 +68,11 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
|
66
68
|
* script.
|
|
67
69
|
*/
|
|
68
70
|
function locatePluginScripts(requiredScript) {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
p = dirname(p);
|
|
75
|
-
}
|
|
76
|
-
const cwd = getProjectCwd();
|
|
77
|
-
candidates.push(join(cwd, 'plugins', 'ruflo-metaharness', 'scripts'));
|
|
78
|
-
candidates.push(join(cwd, 'node_modules', '@claude-flow', 'cli', 'plugins', 'ruflo-metaharness', 'scripts'));
|
|
71
|
+
// Bounded at node_modules: a project-local install must not reach the project.
|
|
72
|
+
const candidates = ownPluginDirs(__dirname, 'ruflo-metaharness', 'scripts');
|
|
73
|
+
// Scripts found here are executed, so they come from this CLI's own install
|
|
74
|
+
// (or the user's marketplace checkout), never from the project's cwd.
|
|
75
|
+
candidates.push(join(homedir(), '.claude', 'plugins', 'marketplaces', 'ruflo', 'plugins', 'ruflo-metaharness', 'scripts'));
|
|
79
76
|
for (const c of candidates) {
|
|
80
77
|
if (!existsSync(join(c, '_harness.mjs')))
|
|
81
78
|
continue;
|
|
@@ -71,7 +71,7 @@ function updateAgents(agentIds, mutate) {
|
|
|
71
71
|
continue;
|
|
72
72
|
let touched = false;
|
|
73
73
|
for (const id of agentIds) {
|
|
74
|
-
if (store.agents[id]) {
|
|
74
|
+
if (Object.prototype.hasOwnProperty.call(store.agents, id) && store.agents[id]) {
|
|
75
75
|
mutate(store.agents[id]);
|
|
76
76
|
touched = true;
|
|
77
77
|
}
|
|
@@ -325,6 +325,10 @@ export const taskTools = [
|
|
|
325
325
|
if (status === null) {
|
|
326
326
|
return { success: false, error: `status must be one of ${TASK_STATUSES.join(', ')}` };
|
|
327
327
|
}
|
|
328
|
+
if (input.assignTo !== undefined && (!Array.isArray(input.assignTo)
|
|
329
|
+
|| !input.assignTo.every(id => validateIdentifier(id, 'assignTo').valid))) {
|
|
330
|
+
return { success: false, error: 'assignTo must be an array of agent ids' };
|
|
331
|
+
}
|
|
328
332
|
const store = loadTaskStore();
|
|
329
333
|
const taskId = input.taskId;
|
|
330
334
|
const task = store.tasks[taskId];
|
|
@@ -332,6 +336,7 @@ export const taskTools = [
|
|
|
332
336
|
// Worker effects run AFTER the task store is saved, as task_complete does: a failed save must not leave
|
|
333
337
|
// workers freed and counted against a task that still reads unfinished.
|
|
334
338
|
let afterSave;
|
|
339
|
+
const previouslyAssigned = Array.isArray(task.assignedTo) ? [...task.assignedTo] : [];
|
|
335
340
|
if (status !== undefined) {
|
|
336
341
|
const newStatus = status;
|
|
337
342
|
const wasCompleted = task.status === 'completed';
|
|
@@ -344,13 +349,13 @@ export const taskTools = [
|
|
|
344
349
|
// returns early because the status already reads completed.
|
|
345
350
|
if (newStatus === 'failed' || newStatus === 'cancelled') {
|
|
346
351
|
task.completedAt = new Date().toISOString();
|
|
347
|
-
afterSave = () => releaseAgents(
|
|
352
|
+
afterSave = () => releaseAgents(previouslyAssigned, taskId);
|
|
348
353
|
}
|
|
349
354
|
if (newStatus === 'completed' && !wasCompleted) {
|
|
350
355
|
task.completedAt = new Date().toISOString();
|
|
351
356
|
// Credited only to a worker still holding this task, so complete -> pending -> complete
|
|
352
357
|
// (no reassignment in between) never counts the same work twice.
|
|
353
|
-
afterSave = () => updateAgents(
|
|
358
|
+
afterSave = () => updateAgents(previouslyAssigned, (agent) => {
|
|
354
359
|
if (agent.currentTask === taskId) {
|
|
355
360
|
agent.status = 'idle';
|
|
356
361
|
agent.currentTask = null;
|
|
@@ -367,6 +372,22 @@ export const taskTools = [
|
|
|
367
372
|
task.progress = 100;
|
|
368
373
|
if (input.assignTo) {
|
|
369
374
|
task.assignedTo = input.assignTo;
|
|
375
|
+
// Reassignment must update the same worker stores as task_assign.
|
|
376
|
+
// Terminal transitions release/credit the workers that held the task
|
|
377
|
+
// before this update; replacement assignees did not perform that work.
|
|
378
|
+
if (!['completed', 'failed', 'cancelled'].includes(task.status)) {
|
|
379
|
+
afterSave = () => {
|
|
380
|
+
releaseAgents(previouslyAssigned.filter(id => !task.assignedTo.includes(id)), taskId);
|
|
381
|
+
// Echoing retained assignees must not take them from another task or re-credit completed work.
|
|
382
|
+
updateAgents(task.assignedTo.filter(id => !previouslyAssigned.includes(id)), agent => {
|
|
383
|
+
agent.status = 'busy';
|
|
384
|
+
agent.currentTask = taskId;
|
|
385
|
+
});
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
else if (!afterSave) {
|
|
389
|
+
afterSave = () => releaseAgents(previouslyAssigned, taskId);
|
|
390
|
+
}
|
|
370
391
|
}
|
|
371
392
|
if (input.result && typeof input.result === 'object') {
|
|
372
393
|
task.result = input.result;
|
|
@@ -23,9 +23,10 @@
|
|
|
23
23
|
*/
|
|
24
24
|
import { spawn } from 'node:child_process';
|
|
25
25
|
import { existsSync } from 'node:fs';
|
|
26
|
-
import { dirname, join
|
|
26
|
+
import { dirname, join } from 'node:path';
|
|
27
27
|
import { fileURLToPath } from 'node:url';
|
|
28
|
-
import {
|
|
28
|
+
import { homedir } from 'node:os';
|
|
29
|
+
import { ownPluginDirs } from '../plugins/own-install.js';
|
|
29
30
|
const __filename = fileURLToPath(import.meta.url);
|
|
30
31
|
const __dirname = dirname(__filename);
|
|
31
32
|
/**
|
|
@@ -38,16 +39,11 @@ const __dirname = dirname(__filename);
|
|
|
38
39
|
* today) — when that lands at a candidate path, we've found the dir.
|
|
39
40
|
*/
|
|
40
41
|
function locateTestgenScripts() {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
p = dirname(p);
|
|
47
|
-
}
|
|
48
|
-
const cwd = getProjectCwd();
|
|
49
|
-
candidates.push(join(cwd, 'plugins', 'ruflo-testgen', 'scripts'));
|
|
50
|
-
candidates.push(join(cwd, 'node_modules', '@claude-flow', 'cli', 'plugins', 'ruflo-testgen', 'scripts'));
|
|
42
|
+
// Bounded at node_modules: a project-local install must not reach the project.
|
|
43
|
+
const candidates = ownPluginDirs(__dirname, 'ruflo-testgen', 'scripts');
|
|
44
|
+
// Scripts found here are executed, so they come from this CLI's own install
|
|
45
|
+
// (or the user's marketplace checkout), never from the project's cwd.
|
|
46
|
+
candidates.push(join(homedir(), '.claude', 'plugins', 'marketplaces', 'ruflo', 'plugins', 'ruflo-testgen', 'scripts'));
|
|
51
47
|
for (const c of candidates) {
|
|
52
48
|
if (existsSync(join(c, 'tdd-repair', 'tdd-repair.mjs')))
|
|
53
49
|
return c;
|
|
@@ -331,7 +331,7 @@ export const transferTools = [
|
|
|
331
331
|
return createResult({ error: v.error }, true);
|
|
332
332
|
}
|
|
333
333
|
try {
|
|
334
|
-
const { createPluginDiscoveryService, searchPlugins } = await import('../plugins/store/index.js');
|
|
334
|
+
const { createPluginDiscoveryService, searchPlugins, registryStatus } = await import('../plugins/store/index.js');
|
|
335
335
|
const discovery = createPluginDiscoveryService();
|
|
336
336
|
const result = await discovery.discoverRegistry();
|
|
337
337
|
if (!result.success || !result.registry) {
|
|
@@ -339,7 +339,7 @@ export const transferTools = [
|
|
|
339
339
|
}
|
|
340
340
|
const opts = input;
|
|
341
341
|
const searchResult = searchPlugins(result.registry, opts);
|
|
342
|
-
return createResult(searchResult);
|
|
342
|
+
return createResult({ ...searchResult, registry: registryStatus(result) });
|
|
343
343
|
}
|
|
344
344
|
catch (error) {
|
|
345
345
|
return createResult({ error: error.message }, true);
|
|
@@ -368,7 +368,7 @@ export const transferTools = [
|
|
|
368
368
|
return createResult({ error: v.error }, true);
|
|
369
369
|
}
|
|
370
370
|
try {
|
|
371
|
-
const { createPluginDiscoveryService } = await import('../plugins/store/index.js');
|
|
371
|
+
const { createPluginDiscoveryService, registryStatus } = await import('../plugins/store/index.js');
|
|
372
372
|
const discovery = createPluginDiscoveryService();
|
|
373
373
|
const result = await discovery.discoverRegistry();
|
|
374
374
|
if (!result.success || !result.registry) {
|
|
@@ -379,7 +379,7 @@ export const transferTools = [
|
|
|
379
379
|
if (!plugin) {
|
|
380
380
|
return createResult({ error: 'Plugin not found' }, true);
|
|
381
381
|
}
|
|
382
|
-
return createResult(plugin);
|
|
382
|
+
return createResult({ ...plugin, registry: registryStatus(result) });
|
|
383
383
|
}
|
|
384
384
|
catch (error) {
|
|
385
385
|
return createResult({ error: error.message }, true);
|
|
@@ -402,7 +402,7 @@ export const transferTools = [
|
|
|
402
402
|
},
|
|
403
403
|
handler: async (input) => {
|
|
404
404
|
try {
|
|
405
|
-
const { createPluginDiscoveryService, getFeaturedPlugins } = await import('../plugins/store/index.js');
|
|
405
|
+
const { createPluginDiscoveryService, getFeaturedPlugins, registryStatus } = await import('../plugins/store/index.js');
|
|
406
406
|
const discovery = createPluginDiscoveryService();
|
|
407
407
|
const result = await discovery.discoverRegistry();
|
|
408
408
|
if (!result.success || !result.registry) {
|
|
@@ -410,7 +410,7 @@ export const transferTools = [
|
|
|
410
410
|
}
|
|
411
411
|
const featured = getFeaturedPlugins(result.registry);
|
|
412
412
|
const limit = input.limit || 10;
|
|
413
|
-
return createResult(featured.slice(0, limit));
|
|
413
|
+
return createResult({ plugins: featured.slice(0, limit), registry: registryStatus(result) });
|
|
414
414
|
}
|
|
415
415
|
catch (error) {
|
|
416
416
|
return createResult({ error: error.message }, true);
|
|
@@ -428,14 +428,15 @@ export const transferTools = [
|
|
|
428
428
|
},
|
|
429
429
|
handler: async () => {
|
|
430
430
|
try {
|
|
431
|
-
const { createPluginDiscoveryService, getOfficialPlugins } = await import('../plugins/store/index.js');
|
|
431
|
+
const { createPluginDiscoveryService, getOfficialPlugins, registryStatus } = await import('../plugins/store/index.js');
|
|
432
432
|
const discovery = createPluginDiscoveryService();
|
|
433
433
|
const result = await discovery.discoverRegistry();
|
|
434
434
|
if (!result.success || !result.registry) {
|
|
435
435
|
return createResult({ error: result.error || 'Failed to discover registry' }, true);
|
|
436
436
|
}
|
|
437
|
+
// Empty when the registry is unverified: an unsigned "official" list proves nothing.
|
|
437
438
|
const official = getOfficialPlugins(result.registry);
|
|
438
|
-
return createResult(official);
|
|
439
|
+
return createResult({ plugins: official, registry: registryStatus(result) });
|
|
439
440
|
}
|
|
440
441
|
catch (error) {
|
|
441
442
|
return createResult({ error: error.message }, true);
|
|
@@ -863,7 +863,7 @@ export const workflowTools = [
|
|
|
863
863
|
// `workflow_validate` tool. Structural sanity check (JSON workflow files);
|
|
864
864
|
// a full schema validator is a follow-up.
|
|
865
865
|
name: 'workflow_validate',
|
|
866
|
-
description: 'Structurally validate a workflow definition file (JSON) — checks it has a steps/stages/tasks array and that
|
|
866
|
+
description: 'Structurally validate a workflow definition file (JSON) — checks it has a steps/stages/tasks array and that task steps resolve an agent. Use when native Read is wrong because you want a parsed, structured pass/fail with error/warning lists and step/agent counts rather than eyeballing the file. For just reading the file, native Read is fine. (Basic checks today — a full workflow-schema validator is a tracked follow-up.)',
|
|
867
867
|
category: 'workflow',
|
|
868
868
|
inputSchema: {
|
|
869
869
|
type: 'object',
|
|
@@ -906,10 +906,16 @@ export const workflowTools = [
|
|
|
906
906
|
const agentSet = new Set();
|
|
907
907
|
steps.forEach((s, i) => {
|
|
908
908
|
const step = (s ?? {});
|
|
909
|
-
|
|
910
|
-
|
|
909
|
+
// Match workflow_execute: control steps do not dispatch agents,
|
|
910
|
+
// and task steps use config.agentId or the workflow default.
|
|
911
|
+
const config = step.config;
|
|
912
|
+
const variables = d.variables;
|
|
913
|
+
const a = config?.agentId || variables?.defaultAgentId
|
|
914
|
+
|| step.agent || step.agentType || step.agent_type;
|
|
915
|
+
const needsAgent = !['wait', 'condition', 'parallel', 'loop'].includes(String(step.type));
|
|
916
|
+
if (needsAgent && a)
|
|
911
917
|
agentSet.add(String(a));
|
|
912
|
-
else
|
|
918
|
+
else if (needsAgent)
|
|
913
919
|
warnings.push({ line: i + 1, message: `step ${i + 1} ("${step.name ?? step.id ?? i + 1}") names no agent` });
|
|
914
920
|
});
|
|
915
921
|
agents = agentSet.size;
|
|
@@ -2179,25 +2179,8 @@ async function loadLocalEmbeddingChain(verbose = false, startTime = Date.now())
|
|
|
2179
2179
|
// ruvector ONNX init failed, continue to next fallback
|
|
2180
2180
|
}
|
|
2181
2181
|
}
|
|
2182
|
-
//
|
|
2183
|
-
|
|
2184
|
-
if (agenticFlow && agenticFlow.embeddings) {
|
|
2185
|
-
if (verbose) {
|
|
2186
|
-
console.log('Loading agentic-flow embedding model...');
|
|
2187
|
-
}
|
|
2188
|
-
embeddingModelState = {
|
|
2189
|
-
loaded: true,
|
|
2190
|
-
model: agenticFlow.embeddings,
|
|
2191
|
-
tokenizer: null,
|
|
2192
|
-
dimensions: 768
|
|
2193
|
-
};
|
|
2194
|
-
return {
|
|
2195
|
-
success: true,
|
|
2196
|
-
dimensions: 768,
|
|
2197
|
-
modelName: 'agentic-flow',
|
|
2198
|
-
loadTime: Date.now() - startTime
|
|
2199
|
-
};
|
|
2200
|
-
}
|
|
2182
|
+
// No agentic-flow root-entry fallback: no published root exports
|
|
2183
|
+
// `embeddings`, and importing it runs its CLI main() in 3.0.0-alpha.x.
|
|
2201
2184
|
// No ONNX model available - use fallback
|
|
2202
2185
|
embeddingModelState = {
|
|
2203
2186
|
loaded: true,
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
type DbCtor = new (path: string, opts?: Record<string, unknown>) => any;
|
|
2
|
+
/**
|
|
3
|
+
* Entry file whose better-sqlite3 AgentDB's handle uses: @claude-flow/memory
|
|
4
|
+
* when it routes AgentDB through its own driver, else agentdb.
|
|
5
|
+
* Test seam: `memoryEntry` stands in for the resolved @claude-flow/memory entry.
|
|
6
|
+
*/
|
|
7
|
+
export declare function resolveSqliteOwnerEntry(memoryEntry?: string): string | null;
|
|
2
8
|
/** Test seam: pass `from` to resolve relative to another entry file. */
|
|
3
9
|
export declare function resolveAgentdbBetterSqlite3(from?: string): DbCtor | null;
|
|
4
10
|
/** Constructor shared with AgentDB when possible, else the CLI's own copy. */
|
|
@@ -10,22 +10,50 @@
|
|
|
10
10
|
* like "last connection" to that copy and deletes the -wal/-shm sidecars out
|
|
11
11
|
* from under AgentDB's still-open handle.
|
|
12
12
|
*
|
|
13
|
-
* Resolve the constructor from the same place AgentDB
|
|
14
|
-
* share one SQLite instance (and its connection bookkeeping)
|
|
15
|
-
*
|
|
13
|
+
* Resolve the constructor from the same place AgentDB's handle is opened with,
|
|
14
|
+
* so both share one SQLite instance (and its connection bookkeeping):
|
|
15
|
+
* - @claude-flow/memory >= 3.0.4 opens AgentDB with its own better-sqlite3
|
|
16
|
+
* (agentdb-native-driver.js, getHostSqliteDriver) to keep agentdb's nested
|
|
17
|
+
* 11.x — compiled from source on Node 24, which aborts in Statement GC —
|
|
18
|
+
* out of the process. Resolve from memory's location then.
|
|
19
|
+
* - Older memory lets AgentDB load its own copy: resolve from agentdb.
|
|
20
|
+
* Falls back to the CLI's own copy when neither can be resolved.
|
|
16
21
|
*/
|
|
17
22
|
import { createRequire } from 'node:module';
|
|
23
|
+
import { existsSync } from 'node:fs';
|
|
24
|
+
import { dirname, join } from 'node:path';
|
|
18
25
|
let cached;
|
|
26
|
+
/**
|
|
27
|
+
* Entry file whose better-sqlite3 AgentDB's handle uses: @claude-flow/memory
|
|
28
|
+
* when it routes AgentDB through its own driver, else agentdb.
|
|
29
|
+
* Test seam: `memoryEntry` stands in for the resolved @claude-flow/memory entry.
|
|
30
|
+
*/
|
|
31
|
+
export function resolveSqliteOwnerEntry(memoryEntry) {
|
|
32
|
+
const own = createRequire(import.meta.url);
|
|
33
|
+
try {
|
|
34
|
+
const entry = memoryEntry ?? own.resolve('@claude-flow/memory');
|
|
35
|
+
if (existsSync(join(dirname(entry), 'agentdb-native-driver.js')))
|
|
36
|
+
return entry;
|
|
37
|
+
}
|
|
38
|
+
catch { /* memory not installed */ }
|
|
39
|
+
try {
|
|
40
|
+
return own.resolve('agentdb');
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
19
46
|
/** Test seam: pass `from` to resolve relative to another entry file. */
|
|
20
47
|
export function resolveAgentdbBetterSqlite3(from) {
|
|
21
48
|
if (!from && cached !== undefined)
|
|
22
49
|
return cached;
|
|
23
50
|
let found = null;
|
|
24
51
|
try {
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
52
|
+
const ownerEntry = from ?? resolveSqliteOwnerEntry();
|
|
53
|
+
if (ownerEntry) {
|
|
54
|
+
const mod = createRequire(ownerEntry)('better-sqlite3');
|
|
55
|
+
found = (mod?.default ?? mod);
|
|
56
|
+
}
|
|
29
57
|
}
|
|
30
58
|
catch {
|
|
31
59
|
found = null;
|