@claude-flow/cli 3.71.4 → 3.72.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.
Files changed (124) hide show
  1. package/.claude/helpers/helpers.manifest.json +4 -4
  2. package/.claude/helpers/hook-handler.cjs +23 -10
  3. package/.claude/helpers/statusline.cjs +29 -14
  4. package/.claude/skills/fast-code-edits/SKILL.md +45 -0
  5. package/.claude/skills/graph-mincut/SKILL.md +32 -0
  6. package/.claude/skills/injection-defense/SKILL.md +36 -0
  7. package/.claude/skills/lora-learning/SKILL.md +30 -0
  8. package/.claude/skills/sublinear-solver/SKILL.md +34 -0
  9. package/.claude/skills/vector-quantization/SKILL.md +36 -0
  10. package/README.md +18 -3
  11. package/bin/cli.js +13 -18
  12. package/bin/mcp-server.js +13 -18
  13. package/catalog-manifest.json +4 -4
  14. package/dist/src/appliance/ruvllm-bridge.d.ts +6 -2
  15. package/dist/src/appliance/ruvllm-bridge.js +13 -8
  16. package/dist/src/appliance/rvfa-distribution.js +2 -2
  17. package/dist/src/benchmarks/gaia-critic.d.ts +1 -7
  18. package/dist/src/benchmarks/gaia-critic.js +1 -7
  19. package/dist/src/benchmarks/gaia-decomposer.d.ts +1 -8
  20. package/dist/src/benchmarks/gaia-decomposer.js +1 -8
  21. package/dist/src/commands/agent-wasm.js +2 -2
  22. package/dist/src/commands/analyze.js +1 -1
  23. package/dist/src/commands/daemon.js +1 -1
  24. package/dist/src/commands/doctor.js +3 -2
  25. package/dist/src/commands/guidance.js +9 -9
  26. package/dist/src/commands/hooks.js +13 -8
  27. package/dist/src/commands/init.js +16 -16
  28. package/dist/src/commands/neural.js +87 -77
  29. package/dist/src/commands/route.js +1 -1
  30. package/dist/src/commands/ruvector/backup.js +7 -5
  31. package/dist/src/commands/ruvector/benchmark.js +6 -1
  32. package/dist/src/commands/ruvector/import.js +19 -5
  33. package/dist/src/commands/ruvector/init.js +1 -1
  34. package/dist/src/commands/start.js +1 -1
  35. package/dist/src/commands/status.js +1 -1
  36. package/dist/src/commands/task.js +18 -1
  37. package/dist/src/commands/update.js +1 -1
  38. package/dist/src/funnel/local-signals.js +7 -2
  39. package/dist/src/init/executor.js +13 -0
  40. package/dist/src/init/types.d.ts +6 -0
  41. package/dist/src/init/types.js +3 -0
  42. package/dist/src/mcp-client.js +2 -0
  43. package/dist/src/mcp-protocol.d.ts +90 -0
  44. package/dist/src/mcp-protocol.js +179 -0
  45. package/dist/src/mcp-server.d.ts +1 -0
  46. package/dist/src/mcp-server.js +20 -22
  47. package/dist/src/mcp-tools/agent-execute-core.js +9 -1
  48. package/dist/src/mcp-tools/agent-tools.d.ts +13 -0
  49. package/dist/src/mcp-tools/agent-tools.js +59 -8
  50. package/dist/src/mcp-tools/agentdb-tools.js +34 -2
  51. package/dist/src/mcp-tools/browser-argv.d.ts +9 -0
  52. package/dist/src/mcp-tools/browser-argv.js +33 -0
  53. package/dist/src/mcp-tools/browser-session-tools.js +4 -0
  54. package/dist/src/mcp-tools/browser-tools.js +12 -2
  55. package/dist/src/mcp-tools/coordination-tools.js +5 -4
  56. package/dist/src/mcp-tools/embeddings-tools.js +12 -4
  57. package/dist/src/mcp-tools/fast-apply-tools.d.ts +4 -0
  58. package/dist/src/mcp-tools/fast-apply-tools.js +85 -0
  59. package/dist/src/mcp-tools/glob-exclusions.d.ts +6 -0
  60. package/dist/src/mcp-tools/glob-exclusions.js +81 -0
  61. package/dist/src/mcp-tools/hive-mind-tools.js +104 -35
  62. package/dist/src/mcp-tools/hooks-tools.js +14 -38
  63. package/dist/src/mcp-tools/import-limits.d.ts +4 -0
  64. package/dist/src/mcp-tools/import-limits.js +28 -0
  65. package/dist/src/mcp-tools/memory-tools.js +21 -42
  66. package/dist/src/mcp-tools/metaharness-tools.js +1 -1
  67. package/dist/src/mcp-tools/performance-tools.js +7 -2
  68. package/dist/src/mcp-tools/realpath-containment.d.ts +3 -0
  69. package/dist/src/mcp-tools/realpath-containment.js +39 -0
  70. package/dist/src/mcp-tools/record-store.d.ts +8 -0
  71. package/dist/src/mcp-tools/record-store.js +98 -1
  72. package/dist/src/mcp-tools/security-tools.d.ts +9 -0
  73. package/dist/src/mcp-tools/security-tools.js +39 -3
  74. package/dist/src/mcp-tools/session-tools.js +5 -3
  75. package/dist/src/mcp-tools/swarm-tools.js +35 -17
  76. package/dist/src/mcp-tools/task-tools.js +68 -5
  77. package/dist/src/mcp-tools/terminal-tools.js +9 -0
  78. package/dist/src/mcp-tools/workflow-tools.js +13 -12
  79. package/dist/src/mcp-tools/x-federation-join.js +55 -14
  80. package/dist/src/memory/intelligence.d.ts +11 -0
  81. package/dist/src/memory/intelligence.js +84 -6
  82. package/dist/src/memory/memory-initializer.d.ts +6 -2
  83. package/dist/src/memory/memory-initializer.js +15 -6
  84. package/dist/src/mods/command-registry/schema.d.ts +12 -12
  85. package/dist/src/ruvector/ast-analyzer.d.ts +1 -2
  86. package/dist/src/ruvector/ast-analyzer.js +4 -12
  87. package/dist/src/ruvector/coverage-router.d.ts +1 -2
  88. package/dist/src/ruvector/coverage-router.js +4 -12
  89. package/dist/src/ruvector/diff-classifier.d.ts +1 -2
  90. package/dist/src/ruvector/diff-classifier.js +3 -12
  91. package/dist/src/ruvector/fast-apply.d.ts +46 -0
  92. package/dist/src/ruvector/fast-apply.js +73 -0
  93. package/dist/src/ruvector/graph-analyzer.d.ts +11 -1
  94. package/dist/src/ruvector/graph-analyzer.js +22 -36
  95. package/dist/src/ruvector/mincut-bridge.d.ts +70 -0
  96. package/dist/src/ruvector/mincut-bridge.js +172 -0
  97. package/dist/src/ruvector/model-router.d.ts +6 -1
  98. package/dist/src/ruvector/model-router.js +16 -4
  99. package/dist/src/services/distill-oracle.d.ts +1 -1
  100. package/dist/src/services/distill-oracle.js +2 -2
  101. package/dist/src/services/worker-daemon.js +30 -12
  102. package/dist/src/transfer/storage/gcs.js +3 -2
  103. package/node_modules/@claude-flow/codex/.agents/skills/fast-code-edits/SKILL.md +42 -0
  104. package/node_modules/@claude-flow/codex/.agents/skills/graph-mincut/SKILL.md +42 -0
  105. package/node_modules/@claude-flow/codex/.agents/skills/injection-defense/SKILL.md +45 -0
  106. package/node_modules/@claude-flow/codex/.agents/skills/lora-learning/SKILL.md +39 -0
  107. package/node_modules/@claude-flow/codex/.agents/skills/sublinear-solver/SKILL.md +38 -0
  108. package/node_modules/@claude-flow/codex/.agents/skills/vector-quantization/SKILL.md +49 -0
  109. package/node_modules/@claude-flow/codex/dist/generators/skill-md.d.ts.map +1 -1
  110. package/node_modules/@claude-flow/codex/dist/generators/skill-md.js +6 -0
  111. package/node_modules/@claude-flow/codex/dist/generators/skill-md.js.map +1 -1
  112. package/node_modules/@claude-flow/codex/dist/index.d.ts +3 -3
  113. package/node_modules/@claude-flow/codex/dist/index.js +1 -1
  114. package/node_modules/@claude-flow/codex/dist/templates/index.d.ts.map +1 -1
  115. package/node_modules/@claude-flow/codex/dist/templates/index.js +37 -0
  116. package/node_modules/@claude-flow/codex/dist/templates/index.js.map +1 -1
  117. package/node_modules/@claude-flow/codex/dist/types.d.ts +1 -1
  118. package/node_modules/@claude-flow/codex/dist/types.d.ts.map +1 -1
  119. package/node_modules/@claude-flow/codex/package.json +1 -1
  120. package/package.json +11 -9
  121. package/plugins/ruflo-metaharness/.claude-plugin/plugin.json +2 -1
  122. package/plugins/ruflo-metaharness/scripts/_darwin.mjs +4 -4
  123. package/plugins/ruflo-metaharness/scripts/_harness.mjs +1 -1
  124. package/plugins/ruflo-metaharness/scripts/smoke.sh +4 -4
@@ -0,0 +1,4 @@
1
+ import { type MCPTool } from './types.js';
2
+ export declare const hooksFastApply: MCPTool;
3
+ export declare const fastApplyTools: MCPTool[];
4
+ //# sourceMappingURL=fast-apply-tools.d.ts.map
@@ -0,0 +1,85 @@
1
+ /**
2
+ * hooks_fast-apply — merge a code snippet into a file or string with
3
+ * agent-booster (~ms, no LLM). The caller supplies the target code; for
4
+ * deterministic intents with no snippet use hooks_codemod instead.
5
+ */
6
+ import { existsSync, readFileSync, writeFileSync } from 'fs';
7
+ import { resolve, sep } from 'path';
8
+ import { getProjectCwd } from './types.js';
9
+ import { validatePath } from './validate-input.js';
10
+ import { realpathNearest, isInsideRealRoot } from './realpath-containment.js';
11
+ const MAX_INPUT_BYTES = 1_000_000;
12
+ function langForPath(p) {
13
+ const ext = p.slice(p.lastIndexOf('.') + 1).toLowerCase();
14
+ const map = {
15
+ ts: 'typescript', tsx: 'typescript', mts: 'typescript', cts: 'typescript',
16
+ js: 'javascript', jsx: 'javascript', mjs: 'javascript', cjs: 'javascript',
17
+ py: 'python', rs: 'rust', go: 'go', java: 'java', c: 'c', cpp: 'cpp', h: 'c',
18
+ };
19
+ return map[ext];
20
+ }
21
+ export const hooksFastApply = {
22
+ name: 'hooks_fast-apply',
23
+ description: 'Merge an edit SNIPPET (the rewritten code, e.g. a whole new version of one function) into a file or raw code using agent-booster — a local fast-apply engine (~1-10 ms, no LLM, $0). `edit` must be code, not an instruction: "make add typed" fails, "function add(a: number, b: number): number { return a + b; }" works. Use when you already have the replacement code and the surrounding file is large, instead of re-sending the whole file. Deterministic intents (var-to-const, remove-console, add-logging) belong to hooks_codemod. Returns confidence + strategy; below minConfidence nothing is written.',
24
+ inputSchema: {
25
+ type: 'object',
26
+ properties: {
27
+ edit: { type: 'string', description: 'Target code snippet to merge (code, not an instruction)' },
28
+ file: { type: 'string', description: 'Existing file inside the project to patch in place' },
29
+ code: { type: 'string', description: 'Raw source to patch instead of a file (returns output, writes nothing)' },
30
+ language: { type: 'string', description: 'Language hint (inferred from the file extension when omitted)' },
31
+ minConfidence: { type: 'number', description: 'Minimum merge confidence 0-1 required to apply (default 0.5)' },
32
+ dryRun: { type: 'boolean', description: 'Report the merge result without writing the file' },
33
+ },
34
+ required: ['edit'],
35
+ },
36
+ handler: async (params) => {
37
+ const edit = params.edit;
38
+ if (typeof edit !== 'string' || edit.trim() === '')
39
+ return { success: false, error: 'edit (non-empty code snippet) is required' };
40
+ if (edit.length > MAX_INPUT_BYTES)
41
+ return { success: false, error: 'edit too large' };
42
+ const minConfidence = typeof params.minConfidence === 'number'
43
+ ? Math.min(Math.max(params.minConfidence, 0), 1) : 0.5;
44
+ const dryRun = params.dryRun === true;
45
+ const { fastApply } = await import('../ruvector/fast-apply.js');
46
+ if (typeof params.code === 'string') {
47
+ if (params.code.length > MAX_INPUT_BYTES)
48
+ return { success: false, error: 'code too large' };
49
+ const r = await fastApply({ code: params.code, edit, language: params.language, minConfidence });
50
+ return { ...r, mode: 'code', cost: 0 };
51
+ }
52
+ if (typeof params.file !== 'string')
53
+ return { success: false, error: 'Provide `file` or `code`.' };
54
+ const v = validatePath(params.file, 'file');
55
+ if (!v.valid)
56
+ return { success: false, error: v.error };
57
+ const cwd = getProjectCwd();
58
+ const abs = resolve(cwd, v.sanitized);
59
+ if (abs !== cwd && !abs.startsWith(cwd + sep))
60
+ return { success: false, error: `path escapes project root: ${params.file}` };
61
+ let realRoot;
62
+ try {
63
+ realRoot = realpathNearest(cwd);
64
+ }
65
+ catch (err) {
66
+ return { success: false, error: `cannot resolve project root: ${err.message}` };
67
+ }
68
+ if (!isInsideRealRoot(realRoot, abs))
69
+ return { success: false, error: `path escapes project root via symlink: ${params.file}` };
70
+ if (!existsSync(abs))
71
+ return { success: false, error: `file not found: ${params.file}` };
72
+ const before = readFileSync(abs, 'utf-8');
73
+ if (before.length > MAX_INPUT_BYTES)
74
+ return { success: false, error: 'file too large' };
75
+ const language = params.language ?? langForPath(abs);
76
+ const r = await fastApply({ code: before, edit, language, minConfidence });
77
+ const written = r.success && !dryRun;
78
+ if (written)
79
+ writeFileSync(abs, r.output, 'utf-8');
80
+ const { output, ...meta } = r;
81
+ return { ...meta, mode: dryRun ? 'dry-run' : 'file', file: params.file, written, ...(dryRun && { output }), cost: 0 };
82
+ },
83
+ };
84
+ export const fastApplyTools = [hooksFastApply];
85
+ //# sourceMappingURL=fast-apply-tools.js.map
@@ -0,0 +1,6 @@
1
+ /** Bounded glob matching for caller-provided memory-import exclusions. */
2
+ export declare const MAX_EXCLUDE_PATTERNS = 64;
3
+ export declare const MAX_EXCLUDE_PATTERN_LENGTH = 1024;
4
+ export declare const MAX_EXCLUDE_WILDCARDS = 32;
5
+ export declare function compileExclusionPatterns(input: unknown): Array<(path: string) => boolean>;
6
+ //# sourceMappingURL=glob-exclusions.d.ts.map
@@ -0,0 +1,81 @@
1
+ /** Bounded glob matching for caller-provided memory-import exclusions. */
2
+ export const MAX_EXCLUDE_PATTERNS = 64;
3
+ export const MAX_EXCLUDE_PATTERN_LENGTH = 1024;
4
+ export const MAX_EXCLUDE_WILDCARDS = 32;
5
+ function tokenize(pattern) {
6
+ const tokens = [];
7
+ let wildcards = 0;
8
+ for (let i = 0; i < pattern.length; i++) {
9
+ const char = pattern[i];
10
+ if (char === '*') {
11
+ let end = i + 1;
12
+ while (pattern[end] === '*')
13
+ end++;
14
+ tokens.push({ kind: end - i > 1 ? 'globstar' : 'star' });
15
+ // Preserve the trailing segment-star in odd runs, including its newline behavior.
16
+ if (end - i > 1 && (end - i) % 2 === 1) {
17
+ tokens.push({ kind: 'star' });
18
+ wildcards++;
19
+ }
20
+ i = end - 1;
21
+ wildcards++;
22
+ }
23
+ else if (char === '?') {
24
+ tokens.push({ kind: 'question' });
25
+ wildcards++;
26
+ }
27
+ else {
28
+ tokens.push({ kind: 'literal', value: char });
29
+ }
30
+ }
31
+ if (wildcards > MAX_EXCLUDE_WILDCARDS) {
32
+ throw new Error(`excludeFilePatterns entries may contain at most ${MAX_EXCLUDE_WILDCARDS} wildcard operators`);
33
+ }
34
+ return tokens;
35
+ }
36
+ function matches(tokens, path) {
37
+ // Each path character visits each token once; wildcard placement cannot cause backtracking.
38
+ let previous = new Uint8Array(tokens.length + 1);
39
+ let current = new Uint8Array(tokens.length + 1);
40
+ previous[0] = 1;
41
+ for (let j = 1; j <= tokens.length; j++) {
42
+ const token = tokens[j - 1];
43
+ if (token.kind === 'star' || token.kind === 'globstar')
44
+ previous[j] = previous[j - 1];
45
+ }
46
+ for (let i = 0; i < path.length; i++) {
47
+ current[0] = 0;
48
+ const char = path[i];
49
+ const inSegment = char !== '/' && char !== '\\';
50
+ for (let j = 1; j <= tokens.length; j++) {
51
+ const token = tokens[j - 1];
52
+ if (token.kind === 'star' || token.kind === 'globstar') {
53
+ const canConsume = token.kind === 'globstar'
54
+ ? !'\n\r\u2028\u2029'.includes(char)
55
+ : inSegment;
56
+ current[j] = current[j - 1] || (canConsume ? previous[j] : 0);
57
+ }
58
+ else {
59
+ const canConsume = token.kind === 'literal' ? token.value === char : inSegment;
60
+ current[j] = canConsume ? previous[j - 1] : 0;
61
+ }
62
+ }
63
+ [previous, current] = [current, previous];
64
+ }
65
+ return previous[tokens.length] === 1;
66
+ }
67
+ export function compileExclusionPatterns(input) {
68
+ if (input === undefined)
69
+ return [];
70
+ if (!Array.isArray(input) || input.length > MAX_EXCLUDE_PATTERNS) {
71
+ throw new Error(`excludeFilePatterns must be an array of at most ${MAX_EXCLUDE_PATTERNS} patterns`);
72
+ }
73
+ return Array.from(input, (pattern) => {
74
+ if (typeof pattern !== 'string' || pattern.length > MAX_EXCLUDE_PATTERN_LENGTH) {
75
+ throw new Error(`excludeFilePatterns entries must be strings of at most ${MAX_EXCLUDE_PATTERN_LENGTH} characters`);
76
+ }
77
+ const tokens = tokenize(pattern);
78
+ return (path) => matches(tokens, path);
79
+ });
80
+ }
81
+ //# sourceMappingURL=glob-exclusions.js.map
@@ -8,6 +8,7 @@ import { join } from 'node:path';
8
8
  import { randomBytes, createHash, timingSafeEqual } from 'node:crypto';
9
9
  import { getProjectCwd } from './types.js';
10
10
  import { validateIdentifier, validateText } from './validate-input.js';
11
+ import { readPreservedJsonStore } from './record-store.js';
11
12
  // Storage paths
12
13
  const STORAGE_DIR = '.claude-flow';
13
14
  const HIVE_DIR = 'hive-mind';
@@ -199,17 +200,10 @@ function ensureHiveDir() {
199
200
  catch { /* not ours / unsupported */ }
200
201
  }
201
202
  function loadHiveState() {
202
- try {
203
- const path = getHivePath();
204
- if (existsSync(path)) {
205
- const data = readFileSync(path, 'utf-8');
206
- return JSON.parse(data);
207
- }
208
- }
209
- catch {
210
- // Return default state on error
211
- }
212
- return {
203
+ // #4737: a parse error used to return a fresh hive, and the next save
204
+ // overwrote state.json (including its capability token). Only a missing
205
+ // file is empty; corrupt bytes are copied aside and the error is reported.
206
+ return readPreservedJsonStore(getHivePath(), 'hive-mind', () => ({
213
207
  initialized: false,
214
208
  topology: 'mesh',
215
209
  workers: [],
@@ -217,7 +211,7 @@ function loadHiveState() {
217
211
  sharedMemory: {},
218
212
  createdAt: new Date().toISOString(),
219
213
  updatedAt: new Date().toISOString(),
220
- };
214
+ }));
221
215
  }
222
216
  function saveHiveState(state) {
223
217
  ensureHiveDir();
@@ -243,24 +237,50 @@ function saveHiveState(state) {
243
237
  throw err;
244
238
  }
245
239
  }
246
- // Import agent store helpers for spawn functionality
247
- import { existsSync as agentStoreExists, readFileSync as readAgentStore, writeFileSync as writeAgentStore, mkdirSync as mkdirAgentStore } from 'node:fs';
248
- function loadAgentStore() {
249
- const storePath = join(getProjectCwd(), '.claude-flow', 'agents.json');
240
+ function getHiveAgentPath() {
241
+ return join(getProjectCwd(), STORAGE_DIR, 'agents.json');
242
+ }
243
+ function loadAgentStore(storePath = getHiveAgentPath()) {
244
+ let text;
250
245
  try {
251
- if (agentStoreExists(storePath)) {
252
- return JSON.parse(readAgentStore(storePath, 'utf-8'));
253
- }
246
+ text = readFileSync(storePath, 'utf-8');
254
247
  }
255
- catch { /* ignore */ }
256
- return { agents: {} };
257
- }
258
- function saveAgentStore(store) {
259
- const storeDir = join(getProjectCwd(), '.claude-flow');
260
- if (!agentStoreExists(storeDir)) {
261
- mkdirAgentStore(storeDir, { recursive: true });
248
+ catch (error) {
249
+ // Only an absent file is empty: never replace corrupt/unreadable data.
250
+ if (error.code === 'ENOENT')
251
+ return { agents: {} };
252
+ throw error;
262
253
  }
263
- writeAgentStore(join(storeDir, 'agents.json'), JSON.stringify(store, null, 2), 'utf-8');
254
+ const store = JSON.parse(text);
255
+ if (!store || typeof store !== 'object' || Array.isArray(store)
256
+ || !store.agents || typeof store.agents !== 'object'
257
+ || Object.values(store.agents).some(agent => !agent || typeof agent !== 'object' || Array.isArray(agent))) {
258
+ throw new Error(`Invalid agent store at ${storePath}; existing data was preserved`);
259
+ }
260
+ return store;
261
+ }
262
+ function saveAgentStore(store, storePath = getHiveAgentPath()) {
263
+ // A new hive store lives directly in STORAGE_DIR; join only writes stores
264
+ // that already contain its agent, so their parent directories already exist.
265
+ mkdirSync(join(getProjectCwd(), STORAGE_DIR), { recursive: true });
266
+ writeFileSync(storePath, JSON.stringify(store, null, 2), 'utf-8');
267
+ }
268
+ function findAgent(store, agentId) {
269
+ if (Array.isArray(store.agents))
270
+ return store.agents.find(agent => agent.agentId === agentId);
271
+ return Object.prototype.hasOwnProperty.call(store.agents, agentId) ? store.agents[agentId] : undefined;
272
+ }
273
+ const HIVE_ROLES = new Set(['worker', 'specialist', 'scout']);
274
+ function capacityError(state, additionalWorkers) {
275
+ const maxAgents = state.maxAgents ?? 15;
276
+ if (state.workers.length + additionalWorkers <= maxAgents)
277
+ return null;
278
+ return {
279
+ success: false,
280
+ error: `Hive-mind capacity exceeded: ${state.workers.length}/${maxAgents} workers; cannot add ${additionalWorkers}. Leave the hive or initialize with a larger maxAgents.`,
281
+ totalWorkers: state.workers.length,
282
+ maxAgents,
283
+ };
264
284
  }
265
285
  export const hiveMindTools = [
266
286
  {
@@ -303,8 +323,19 @@ export const hiveMindTools = [
303
323
  if (!v.valid)
304
324
  return { success: false, error: v.error };
305
325
  }
306
- const count = Math.min(Math.max(1, input.count || 1), 20); // Cap at 20
326
+ const requestedCount = input.count === undefined ? 1 : input.count;
327
+ if (typeof requestedCount !== 'number' || !Number.isInteger(requestedCount) || requestedCount < 1) {
328
+ return { success: false, error: 'count must be a positive integer' };
329
+ }
330
+ const count = Math.min(requestedCount, 20); // Preserve the per-call cap.
331
+ // Reject the entire batch before creating agents or mutating the roster.
332
+ const full = capacityError(state, count);
333
+ if (full)
334
+ return full;
307
335
  const role = input.role || 'worker';
336
+ if (!HIVE_ROLES.has(role)) {
337
+ return { success: false, error: `role must be one of: ${[...HIVE_ROLES].join(', ')}` };
338
+ }
308
339
  const agentType = input.agentType || 'worker';
309
340
  const prefix = input.prefix || 'hive-worker';
310
341
  const agentStore = loadAgentStore();
@@ -312,7 +343,7 @@ export const hiveMindTools = [
312
343
  for (let i = 0; i < count; i++) {
313
344
  const agentId = `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
314
345
  // Create agent record (like agent/spawn)
315
- agentStore.agents[agentId] = {
346
+ const agent = {
316
347
  agentId,
317
348
  agentType,
318
349
  status: 'idle',
@@ -322,6 +353,10 @@ export const hiveMindTools = [
322
353
  createdAt: new Date().toISOString(),
323
354
  domain: 'hive-mind',
324
355
  };
356
+ if (Array.isArray(agentStore.agents))
357
+ agentStore.agents.push(agent);
358
+ else
359
+ agentStore.agents[agentId] = agent;
325
360
  // Join to hive-mind (like hive-mind/join)
326
361
  if (!state.workers.includes(agentId)) {
327
362
  state.workers.push(agentId);
@@ -521,7 +556,7 @@ export const hiveMindTools = [
521
556
  term: state.queen.term,
522
557
  } : { id: 'N/A', status: 'offline', load: 0, tasksQueued: 0 },
523
558
  workers: state.workers.map(w => {
524
- const agent = agentStore.agents[w];
559
+ const agent = findAgent(agentStore, w);
525
560
  return {
526
561
  id: w,
527
562
  type: agent?.agentType || 'worker',
@@ -601,14 +636,43 @@ export const hiveMindTools = [
601
636
  if (tokenError) {
602
637
  return { success: false, agentId, error: tokenError };
603
638
  }
604
- if (!state.workers.includes(agentId)) {
639
+ const alreadyJoined = state.workers.includes(agentId);
640
+ // Retrying a registered member does not consume another slot.
641
+ if (!alreadyJoined) {
642
+ const full = capacityError(state, 1);
643
+ if (full)
644
+ return { ...full, agentId };
645
+ }
646
+ // Both registration paths are legitimate (#1916). Update each existing
647
+ // record in place, preserving metadata and map/array representation.
648
+ const paths = [
649
+ join(getProjectCwd(), STORAGE_DIR, 'agents', 'store.json'),
650
+ getHiveAgentPath(),
651
+ ];
652
+ const records = paths.flatMap(path => {
653
+ const store = loadAgentStore(path);
654
+ const agent = findAgent(store, agentId);
655
+ return agent ? [{ path, store, agent }] : [];
656
+ });
657
+ if (records.length === 0) {
658
+ return { success: false, agentId, error: 'Agent not found. Register it with agent_spawn or hive-mind_spawn first.' };
659
+ }
660
+ const role = input.role || 'worker';
661
+ if (!HIVE_ROLES.has(role)) {
662
+ return { success: false, agentId, error: `role must be one of: ${[...HIVE_ROLES].join(', ')}` };
663
+ }
664
+ for (const { path, store, agent } of records) {
665
+ agent.config = { ...agent.config, role, hiveRole: role };
666
+ saveAgentStore(store, path);
667
+ }
668
+ if (!alreadyJoined) {
605
669
  state.workers.push(agentId);
606
670
  saveHiveState(state);
607
671
  }
608
672
  return {
609
673
  success: true,
610
674
  agentId,
611
- role: input.role || 'worker',
675
+ role,
612
676
  totalWorkers: state.workers.length,
613
677
  joinedAt: new Date().toISOString(),
614
678
  };
@@ -1073,9 +1137,14 @@ export const hiveMindTools = [
1073
1137
  }
1074
1138
  // Clear workers from agent store
1075
1139
  const agentStore = loadAgentStore();
1076
- for (const workerId of state.workers) {
1077
- if (agentStore.agents[workerId]) {
1078
- delete agentStore.agents[workerId];
1140
+ if (Array.isArray(agentStore.agents)) {
1141
+ agentStore.agents = agentStore.agents.filter(agent => !state.workers.includes(agent.agentId));
1142
+ }
1143
+ else {
1144
+ for (const workerId of state.workers) {
1145
+ if (Object.prototype.hasOwnProperty.call(agentStore.agents, workerId)) {
1146
+ delete agentStore.agents[workerId];
1147
+ }
1079
1148
  }
1080
1149
  }
1081
1150
  saveAgentStore(agentStore);
@@ -5,9 +5,10 @@ import { feedbackPatternsSchema, validateFeedbackPatterns } from '../memory/feed
5
5
  */
6
6
  import { mkdirSync, writeFileSync, existsSync, readFileSync, statSync, unlinkSync, readdirSync } from 'fs';
7
7
  import * as nodeFs from 'fs';
8
- import { basename, dirname, isAbsolute, join, relative, resolve, sep } from 'path';
8
+ import { dirname, join, resolve, sep } from 'path';
9
9
  import { getProjectCwd } from './types.js';
10
10
  import { validateIdentifier, validateText, validatePath } from './validate-input.js';
11
+ import { realpathNearest, isInsideRealRoot } from './realpath-containment.js';
11
12
  import { commandRecordingDisabled, redactCommandForStorage } from '../services/command-recording.js';
12
13
  import { checkCommandLoop, recordCommandOutcome } from './tool-loop-guardrail.js';
13
14
  import { buildLearnedRoutingPatterns, } from '../services/learned-routing.js';
@@ -2288,7 +2289,8 @@ export const hooksTransfer = {
2288
2289
  },
2289
2290
  handler: async (params) => {
2290
2291
  const sourcePath = params.sourcePath;
2291
- const minConfidence = params.minConfidence || 0.7;
2292
+ // #4728: an explicit 0 must not fall through to 0.7 (`0 || 0.7` is 0.7).
2293
+ const minConfidence = params.minConfidence ?? 0.7;
2292
2294
  const filter = params.filter;
2293
2295
  {
2294
2296
  const v = validatePath(sourcePath, 'sourcePath');
@@ -3474,7 +3476,8 @@ export const hooksPatternStore = {
3474
3476
  handler: async (params) => {
3475
3477
  const pattern = params.pattern;
3476
3478
  const type = params.type || 'general';
3477
- const confidence = params.confidence || 0.8;
3479
+ // #4728: an explicit 0 must not fall through to 0.8 (`0 || 0.8` is 0.8).
3480
+ const confidence = params.confidence ?? 0.8;
3478
3481
  const metadata = params.metadata;
3479
3482
  const timestamp = new Date().toISOString();
3480
3483
  {
@@ -3559,7 +3562,8 @@ export const hooksPatternSearch = {
3559
3562
  handler: async (params) => {
3560
3563
  const query = params.query;
3561
3564
  const topK = params.topK || 5;
3562
- const minConfidence = params.minConfidence || 0.3;
3565
+ // #4728: an explicit 0 must not fall through to 0.3 (`0 || 0.3` is 0.3).
3566
+ const minConfidence = params.minConfidence ?? 0.3;
3563
3567
  const namespace = params.namespace || 'pattern';
3564
3568
  const strategy = params.strategy || 'semantic';
3565
3569
  {
@@ -4865,7 +4869,8 @@ export const hooksWorkerDetect = {
4865
4869
  handler: async (params) => {
4866
4870
  const prompt = params.prompt;
4867
4871
  const autoDispatch = params.autoDispatch;
4868
- const minConfidence = params.minConfidence || 0.5;
4872
+ // #4728: an explicit 0 must not fall through to 0.5 (`0 || 0.5` is 0.5).
4873
+ const minConfidence = params.minConfidence ?? 0.5;
4869
4874
  {
4870
4875
  const v = validateText(prompt, 'prompt');
4871
4876
  if (!v.valid)
@@ -4968,7 +4973,10 @@ export const hooksModelRoute = {
4968
4973
  implementation: 'fallback',
4969
4974
  };
4970
4975
  }
4971
- const result = await router.route(task);
4976
+ const result = await router.route(task, undefined, {
4977
+ preferCost: typeof params.preferCost === 'boolean' ? params.preferCost : undefined,
4978
+ preferSpeed: typeof params.preferSpeed === 'boolean' ? params.preferSpeed : undefined,
4979
+ });
4972
4980
  return {
4973
4981
  model: result.model,
4974
4982
  confidence: result.confidence,
@@ -5119,38 +5127,6 @@ function codemodLangForExt(abs) {
5119
5127
  return 'javascript';
5120
5128
  return 'typescript';
5121
5129
  }
5122
- // Canonicalize a path that may not exist yet: realpath the nearest existing
5123
- // ancestor and re-attach the missing tail, so a symlinked parent still
5124
- // resolves to its real location (mirrors @claude-flow/security's
5125
- // canonicalizeSync, which is not exported).
5126
- function realpathNearest(p) {
5127
- try {
5128
- return nodeFs.realpathSync(p);
5129
- }
5130
- catch (err) {
5131
- if (err?.code !== 'ENOENT')
5132
- throw err;
5133
- }
5134
- const parent = dirname(p);
5135
- if (parent === p)
5136
- return p;
5137
- return join(realpathNearest(parent), basename(p));
5138
- }
5139
- // CWE-59 (#4346): containment must compare real paths on BOTH sides — a
5140
- // symlink inside the project pointing outside it must not pass, and a
5141
- // symlinked project root (e.g. macOS /var -> /private/var) must not make
5142
- // every path fail. Fails closed if canonicalization throws (ELOOP, EACCES).
5143
- function isInsideRealRoot(realRoot, abs) {
5144
- let real;
5145
- try {
5146
- real = realpathNearest(abs);
5147
- }
5148
- catch {
5149
- return false;
5150
- }
5151
- const rel = relative(realRoot, real);
5152
- return rel === '' || (rel !== '..' && !rel.startsWith('..' + sep) && !isAbsolute(rel));
5153
- }
5154
5130
  // Deterministic codemod execution — the real Tier-1 path (ADR-143)
5155
5131
  export const hooksCodemod = {
5156
5132
  name: 'hooks_codemod',
@@ -0,0 +1,4 @@
1
+ export declare const MAX_IMPORT_BYTES: number;
2
+ /** Bound allocation and reads even if an import file grows after its size check. */
3
+ export declare function readImportFile(file: string): Buffer;
4
+ //# sourceMappingURL=import-limits.d.ts.map
@@ -0,0 +1,28 @@
1
+ import { closeSync, constants, fstatSync, openSync, readSync } from 'node:fs';
2
+ export const MAX_IMPORT_BYTES = 16 * 1024 * 1024;
3
+ /** Bound allocation and reads even if an import file grows after its size check. */
4
+ export function readImportFile(file) {
5
+ const fd = openSync(file, constants.O_RDONLY | (constants.O_NONBLOCK ?? 0));
6
+ try {
7
+ const stat = fstatSync(fd);
8
+ if (!stat.isFile())
9
+ throw new Error('Import input must be a regular file');
10
+ if (stat.size > MAX_IMPORT_BYTES)
11
+ throw new Error('Import file exceeds the 16 MiB limit');
12
+ const buffer = Buffer.alloc(stat.size + 1);
13
+ let total = 0;
14
+ while (total < buffer.length) {
15
+ const count = readSync(fd, buffer, total, buffer.length - total, null);
16
+ if (count === 0)
17
+ break;
18
+ total += count;
19
+ }
20
+ if (total > stat.size)
21
+ throw new Error('Import input changed size while reading');
22
+ return buffer.subarray(0, total);
23
+ }
24
+ finally {
25
+ closeSync(fd);
26
+ }
27
+ }
28
+ //# sourceMappingURL=import-limits.js.map
@@ -18,6 +18,8 @@ import { createHash } from 'crypto';
18
18
  import { getProjectCwd } from './types.js';
19
19
  import { validateIdentifier } from './validate-input.js';
20
20
  import { DEFAULT_SEARCH_THRESHOLD, nearMisses } from '../memory/search-defaults.js';
21
+ import { compileExclusionPatterns, MAX_EXCLUDE_PATTERNS, MAX_EXCLUDE_PATTERN_LENGTH } from './glob-exclusions.js';
22
+ import { readImportFile } from './import-limits.js';
21
23
  // #1604: Align with memory-initializer.ts — single source of truth is .swarm/memory.db
22
24
  const MEMORY_DIR = '.swarm';
23
25
  const LEGACY_MEMORY_FILE = 'store.json';
@@ -124,37 +126,6 @@ function sanitizeMemoryKey(key) {
124
126
  const safe = key.replace(DANGEROUS_KEY_CHARS, '_');
125
127
  return safe.length > MAX_KEY_LENGTH ? safe.slice(0, MAX_KEY_LENGTH) : safe;
126
128
  }
127
- // #1937 — minimal glob → RegExp helper for memory_import_claude exclusion
128
- // patterns. Anchored. Supports the three operators the issue's voice-fidelity
129
- // workflow needs:
130
- // `**` — any chars including path separators
131
- // `*` — any chars except path separators
132
- // `?` — exactly one char except a path separator
133
- // Everything else is regex-escaped. Used to match absolute file paths.
134
- function globToRegex(pattern) {
135
- // Tokenize so we can replace `**` before `*` without overlap.
136
- let out = '';
137
- for (let i = 0; i < pattern.length; i++) {
138
- const c = pattern[i];
139
- if (c === '*' && pattern[i + 1] === '*') {
140
- out += '.*';
141
- i++;
142
- }
143
- else if (c === '*') {
144
- out += '[^/\\\\]*';
145
- }
146
- else if (c === '?') {
147
- out += '[^/\\\\]';
148
- }
149
- else if (/[.+^$|(){}\[\]\\]/.test(c)) {
150
- out += '\\' + c;
151
- }
152
- else {
153
- out += c;
154
- }
155
- }
156
- return new RegExp('^' + out + '$');
157
- }
158
129
  // #1883 — resolve the Claude-Code project memory directory for the *current*
159
130
  // project. Claude Code hashes the project path differently per host OS, and
160
131
  // our previous logic only POSIX-slash-replaced cwd, which breaks for:
@@ -1128,8 +1099,9 @@ export const memoryTools = [
1128
1099
  projectPath: { type: 'string', description: '#1883 — explicit project path to hash, used when cwd does not match Claude Code\'s view (e.g. WSL bridge to Windows host). Pass the canonical project root as Claude Code sees it.' },
1129
1100
  excludeFilePatterns: {
1130
1101
  type: 'array',
1131
- items: { type: 'string' },
1132
- description: '#1937 — glob patterns matched against the absolute file path. Files matching ANY pattern are skipped. Supports `*` (any chars within a path segment), `**` (any chars including separators), and `?` (single char). Examples: `**/voice-*.md`, `**/persona-*.md`. Combine with excludeFiles for explicit paths.',
1102
+ maxItems: MAX_EXCLUDE_PATTERNS,
1103
+ items: { type: 'string', maxLength: MAX_EXCLUDE_PATTERN_LENGTH },
1104
+ description: '#1937 — glob patterns matched against the absolute file path. Files matching ANY pattern are skipped. Supports `*` (any chars within a path segment), `**` (any chars including separators), and `?` (single char). At most 64 patterns, 1024 characters and 32 wildcard operators per pattern. Examples: `**/voice-*.md`, `**/persona-*.md`. Combine with excludeFiles for explicit paths.',
1133
1105
  },
1134
1106
  excludeFiles: {
1135
1107
  type: 'array',
@@ -1139,6 +1111,13 @@ export const memoryTools = [
1139
1111
  },
1140
1112
  },
1141
1113
  handler: async (input) => {
1114
+ let excludeMatchers;
1115
+ try {
1116
+ excludeMatchers = compileExclusionPatterns(input.excludeFilePatterns);
1117
+ }
1118
+ catch (error) {
1119
+ return { success: false, imported: 0, error: error.message };
1120
+ }
1142
1121
  await ensureInitialized();
1143
1122
  const { storeEntry } = await getMemoryFunctions();
1144
1123
  const ns = input.namespace || 'claude-memories';
@@ -1151,13 +1130,11 @@ export const memoryTools = [
1151
1130
  const projectPathOverride = input.projectPath;
1152
1131
  const claudeProjectsDir = join(homedir(), '.claude', 'projects');
1153
1132
  // #1937 — voice-fidelity / persona-restricted exclusion.
1154
- const excludeFilePatterns = Array.isArray(input.excludeFilePatterns) ? input.excludeFilePatterns : [];
1155
1133
  const excludeFilesList = Array.isArray(input.excludeFiles) ? new Set(input.excludeFiles) : new Set();
1156
- const excludeRegexes = excludeFilePatterns.map(globToRegex);
1157
1134
  const isExcluded = (absPath) => {
1158
1135
  if (excludeFilesList.has(absPath))
1159
1136
  return true;
1160
- return excludeRegexes.some(re => re.test(absPath));
1137
+ return excludeMatchers.some(matches => matches(absPath));
1161
1138
  };
1162
1139
  // Find memory files
1163
1140
  const memoryFiles = [];
@@ -1776,7 +1753,7 @@ export const memoryTools = [
1776
1753
  inputSchema: {
1777
1754
  type: 'object',
1778
1755
  properties: {
1779
- inputPath: { type: 'string', description: 'Path to the JSON export file' },
1756
+ inputPath: { type: 'string', description: 'Path to the JSON export file (maximum 16 MiB)' },
1780
1757
  merge: { type: 'boolean', description: 'Merge into existing entries (upsert) vs. fail on conflict (default true)' },
1781
1758
  namespace: { type: 'string', description: 'Override the namespace for all imported entries' },
1782
1759
  dbPath: { type: 'string', description: 'Database file to import into (defaults to the MCP memory store)' },
@@ -1785,8 +1762,6 @@ export const memoryTools = [
1785
1762
  },
1786
1763
  handler: async (input, context) => {
1787
1764
  const dbPath = resolveDbPathOption(input.dbPath, context);
1788
- await ensureInitialized(dbPath);
1789
- const { storeEntry } = await getMemoryFunctions();
1790
1765
  const t0 = Date.now();
1791
1766
  // Values are re-embedded on import; count the vectors actually written
1792
1767
  // rather than reporting a constant 0 next to entries that show a vector.
@@ -1802,11 +1777,15 @@ export const memoryTools = [
1802
1777
  }
1803
1778
  let doc;
1804
1779
  try {
1805
- doc = JSON.parse(readFileSync(inputPath, 'utf-8'));
1780
+ doc = JSON.parse(readImportFile(inputPath).toString('utf-8'));
1806
1781
  }
1807
- catch {
1808
- return { error: 'inputPath is not a valid memory export (JSON parse failed)' };
1782
+ catch (error) {
1783
+ return { error: error instanceof SyntaxError
1784
+ ? 'inputPath is not a valid memory export (JSON parse failed)'
1785
+ : error instanceof Error ? error.message : 'Could not read memory import' };
1809
1786
  }
1787
+ await ensureInitialized(dbPath);
1788
+ const { storeEntry } = await getMemoryFunctions();
1810
1789
  const entries = Array.isArray(doc.entries) ? doc.entries : [];
1811
1790
  const nsOverride = input.namespace ? String(input.namespace) : undefined;
1812
1791
  if (nsOverride) {