@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.
Files changed (72) hide show
  1. package/.claude/helpers/auto-memory-hook.mjs +13 -8
  2. package/.claude/helpers/helpers.manifest.json +5 -5
  3. package/.claude/helpers/hook-handler.cjs +19 -14
  4. package/.claude/helpers/learning-service.mjs +5 -2
  5. package/.claude/helpers/session.cjs +48 -12
  6. package/.claude/helpers/statusline.cjs +14 -4
  7. package/catalog-manifest.json +2 -2
  8. package/dist/src/commands/doctor.js +7 -13
  9. package/dist/src/commands/hooks.js +3 -6
  10. package/dist/src/commands/init.js +3 -10
  11. package/dist/src/commands/metaharness.js +9 -14
  12. package/dist/src/commands/plugins.js +81 -8
  13. package/dist/src/commands/security-code-patterns.d.ts +40 -0
  14. package/dist/src/commands/security-code-patterns.js +192 -0
  15. package/dist/src/commands/security.js +8 -11
  16. package/dist/src/commands/status.js +8 -10
  17. package/dist/src/init/executor.d.ts +1 -1
  18. package/dist/src/init/executor.js +25 -16
  19. package/dist/src/init/helper-commands.d.ts +18 -0
  20. package/dist/src/init/helper-commands.js +142 -0
  21. package/dist/src/init/helpers-generator.js +128 -29
  22. package/dist/src/init/settings-generator.js +36 -103
  23. package/dist/src/mcp-tools/metaharness-tools.js +7 -10
  24. package/dist/src/mcp-tools/task-tools.js +24 -3
  25. package/dist/src/mcp-tools/testgen-tools.js +8 -12
  26. package/dist/src/mcp-tools/transfer-tools.js +9 -8
  27. package/dist/src/mcp-tools/workflow-tools.js +10 -4
  28. package/dist/src/memory/memory-initializer.js +2 -19
  29. package/dist/src/memory/shared-sqlite.d.ts +6 -0
  30. package/dist/src/memory/shared-sqlite.js +35 -7
  31. package/dist/src/missions/schemas.d.ts +125 -125
  32. package/dist/src/mods/command-registry/schema.d.ts +199 -199
  33. package/dist/src/plugins/manager.d.ts +3 -1
  34. package/dist/src/plugins/manager.js +31 -10
  35. package/dist/src/plugins/own-install.d.ts +15 -0
  36. package/dist/src/plugins/own-install.js +51 -0
  37. package/dist/src/plugins/store/discovery.d.ts +26 -2
  38. package/dist/src/plugins/store/discovery.js +108 -81
  39. package/dist/src/plugins/store/index.d.ts +1 -1
  40. package/dist/src/plugins/store/index.js +1 -1
  41. package/dist/src/plugins/store/types.d.ts +5 -0
  42. package/dist/src/plugins/trust-policy.d.ts +13 -0
  43. package/dist/src/plugins/trust-policy.js +29 -0
  44. package/dist/src/ruvector/graph-analyzer.js +18 -6
  45. package/dist/src/transfer/ipfs/client.js +3 -1
  46. package/node_modules/@claude-flow/codex/dist/cli.js +0 -0
  47. package/node_modules/@claude-flow/mcp/dist/resource-registry.d.ts +1 -0
  48. package/node_modules/@claude-flow/mcp/dist/resource-registry.d.ts.map +1 -1
  49. package/node_modules/@claude-flow/mcp/dist/resource-registry.js +10 -4
  50. package/node_modules/@claude-flow/mcp/dist/resource-registry.js.map +1 -1
  51. package/node_modules/@claude-flow/mcp/dist/sampling.d.ts.map +1 -1
  52. package/node_modules/@claude-flow/mcp/dist/sampling.js +13 -6
  53. package/node_modules/@claude-flow/mcp/dist/sampling.js.map +1 -1
  54. package/node_modules/@claude-flow/mcp/dist/task-manager.d.ts.map +1 -1
  55. package/node_modules/@claude-flow/mcp/dist/task-manager.js +10 -1
  56. package/node_modules/@claude-flow/mcp/dist/task-manager.js.map +1 -1
  57. package/node_modules/@claude-flow/mcp/dist/tool-registry.d.ts +1 -0
  58. package/node_modules/@claude-flow/mcp/dist/tool-registry.d.ts.map +1 -1
  59. package/node_modules/@claude-flow/mcp/dist/tool-registry.js +17 -11
  60. package/node_modules/@claude-flow/mcp/dist/tool-registry.js.map +1 -1
  61. package/node_modules/@claude-flow/mcp/package.json +1 -1
  62. package/node_modules/@claude-flow/plugin-agent-federation/dist/bin.js +0 -0
  63. package/node_modules/@claude-flow/security/dist/index.d.ts +1 -1
  64. package/node_modules/@claude-flow/security/dist/index.d.ts.map +1 -1
  65. package/node_modules/@claude-flow/security/dist/index.js +1 -1
  66. package/node_modules/@claude-flow/security/dist/index.js.map +1 -1
  67. package/node_modules/@claude-flow/security/dist/plugins/integrity-verifier.d.ts +9 -1
  68. package/node_modules/@claude-flow/security/dist/plugins/integrity-verifier.d.ts.map +1 -1
  69. package/node_modules/@claude-flow/security/dist/plugins/integrity-verifier.js +15 -2
  70. package/node_modules/@claude-flow/security/dist/plugins/integrity-verifier.js.map +1 -1
  71. package/node_modules/@claude-flow/security/package.json +1 -1
  72. 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
- * Detect if we're on Windows for platform-aware hook commands.
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
- const IS_WINDOWS = process.platform === 'win32';
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(_options) {
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: `sh -c 'D="${projVar}"; [ -f "$D/${script}" ] || D="${homeVar}"; exec node "$D/${script}"'`,
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
- const candidates = [];
70
- let p = resolve(__dirname);
71
- for (let i = 0; i < 8; i++) {
72
- candidates.push(join(p, 'plugins', 'ruflo-metaharness', 'scripts'));
73
- candidates.push(join(p, '..', 'plugins', 'ruflo-metaharness', 'scripts'));
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(task.assignedTo, taskId);
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(task.assignedTo, (agent) => {
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, resolve } from 'node:path';
26
+ import { dirname, join } from 'node:path';
27
27
  import { fileURLToPath } from 'node:url';
28
- import { getProjectCwd } from './types.js';
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
- const candidates = [];
42
- let p = resolve(__dirname);
43
- for (let i = 0; i < 8; i++) {
44
- candidates.push(join(p, 'plugins', 'ruflo-testgen', 'scripts'));
45
- candidates.push(join(p, '..', 'plugins', 'ruflo-testgen', 'scripts'));
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 each step names 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.)',
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
- const a = (step.agent ?? step.agentType ?? step.agent_type);
910
- if (a)
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
- // Legacy fallback: Check for agentic-flow core embeddings
2183
- const agenticFlow = await import('agentic-flow').catch(() => null);
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 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.
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 own = createRequire(import.meta.url);
26
- const agentdbEntry = from ?? own.resolve('agentdb');
27
- const mod = createRequire(agentdbEntry)('better-sqlite3');
28
- found = (mod?.default ?? mod);
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;