claude-flow 3.47.0 → 3.48.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/.claude/helpers/auto-memory-hook.mjs +6 -2
  2. package/.claude/helpers/learning-service.mjs +4 -2
  3. package/.claude/helpers/metrics-db.mjs +4 -2
  4. package/node_modules/@claude-flow/codex/package.json +1 -1
  5. package/node_modules/@claude-flow/mcp/dist/tool-registry.d.ts.map +1 -1
  6. package/node_modules/@claude-flow/mcp/dist/tool-registry.js +11 -3
  7. package/node_modules/@claude-flow/mcp/dist/tool-registry.js.map +1 -1
  8. package/node_modules/@claude-flow/mcp/package.json +6 -3
  9. package/node_modules/@claude-flow/plugin-agent-federation/package.json +3 -3
  10. package/node_modules/@claude-flow/security/package.json +2 -3
  11. package/package.json +7 -7
  12. package/v3/@claude-flow/cli/README.md +0 -2
  13. package/v3/@claude-flow/cli/bin/cli.js +4 -1
  14. package/v3/@claude-flow/cli/catalog-manifest.json +2 -2
  15. package/v3/@claude-flow/cli/dist/src/commands/analyze.js +2 -2
  16. package/v3/@claude-flow/cli/dist/src/commands/config.js +5 -7
  17. package/v3/@claude-flow/cli/dist/src/commands/hive-mind-worker.d.ts +88 -0
  18. package/v3/@claude-flow/cli/dist/src/commands/hive-mind-worker.js +242 -0
  19. package/v3/@claude-flow/cli/dist/src/commands/hive-mind.js +24 -41
  20. package/v3/@claude-flow/cli/dist/src/commands/hooks.js +15 -5
  21. package/v3/@claude-flow/cli/dist/src/commands/memory.js +89 -27
  22. package/v3/@claude-flow/cli/dist/src/commands/metaharness.js +1 -0
  23. package/v3/@claude-flow/cli/dist/src/commands/security.js +113 -55
  24. package/v3/@claude-flow/cli/dist/src/commands/session.js +6 -6
  25. package/v3/@claude-flow/cli/dist/src/commands/task.js +5 -4
  26. package/v3/@claude-flow/cli/dist/src/init/helpers-generator.js +4 -2
  27. package/v3/@claude-flow/cli/dist/src/mcp-server.js +10 -3
  28. package/v3/@claude-flow/cli/dist/src/mcp-tools/agentdb-tools.js +29 -62
  29. package/v3/@claude-flow/cli/dist/src/mcp-tools/hooks-tools.d.ts +0 -4
  30. package/v3/@claude-flow/cli/dist/src/mcp-tools/hooks-tools.js +139 -37
  31. package/v3/@claude-flow/cli/dist/src/mcp-tools/memory-tools.js +89 -35
  32. package/v3/@claude-flow/cli/dist/src/mcp-tools/policy-enforcer.d.ts +8 -1
  33. package/v3/@claude-flow/cli/dist/src/mcp-tools/policy-enforcer.js +80 -8
  34. package/v3/@claude-flow/cli/dist/src/mcp-tools/session-tools.js +34 -8
  35. package/v3/@claude-flow/cli/dist/src/mcp-tools/task-tools.js +73 -53
  36. package/v3/@claude-flow/cli/dist/src/memory/feedback-patterns.d.ts +13 -0
  37. package/v3/@claude-flow/cli/dist/src/memory/feedback-patterns.js +14 -0
  38. package/v3/@claude-flow/cli/dist/src/memory/live-memory-row.d.ts +3 -0
  39. package/v3/@claude-flow/cli/dist/src/memory/live-memory-row.js +5 -0
  40. package/v3/@claude-flow/cli/dist/src/memory/memory-bridge.d.ts +11 -20
  41. package/v3/@claude-flow/cli/dist/src/memory/memory-bridge.js +348 -134
  42. package/v3/@claude-flow/cli/dist/src/memory/memory-initializer.d.ts +9 -1
  43. package/v3/@claude-flow/cli/dist/src/memory/memory-initializer.js +41 -31
  44. package/v3/@claude-flow/cli/dist/src/ruvector/graph-analyzer.js +4 -1
  45. package/v3/@claude-flow/cli/dist/src/services/config-file-manager.js +30 -8
  46. package/v3/@claude-flow/cli/dist/src/services/memory-backup.d.ts +1 -1
  47. package/v3/@claude-flow/cli/dist/src/services/memory-backup.js +67 -27
  48. package/v3/@claude-flow/cli/dist/src/services/worker-daemon.d.ts +2 -2
  49. package/v3/@claude-flow/cli/dist/src/services/worker-daemon.js +23 -14
  50. package/v3/@claude-flow/cli/package.json +9 -9
  51. package/v3/@claude-flow/guidance/package.json +5 -6
  52. package/v3/@claude-flow/shared/package.json +6 -3
@@ -13,6 +13,7 @@ import { createBuiltinAIDefence } from '../security/builtin-aidefence.js';
13
13
  // for both validation and the traversal-depth maps below.
14
14
  const SCAN_DEPTHS = ['quick', 'standard', 'deep'];
15
15
  const SCAN_TYPES = ['code', 'deps', 'all'];
16
+ const SCAN_OUTPUTS = ['text', 'json', 'sarif'];
16
17
  // Real type predicates, not `as` casts. Array.includes() does not narrow a
17
18
  // string to a literal union on its own, so without these the depth-map lookups
18
19
  // below need an unchecked assertion — and an unchecked assertion would let a
@@ -23,6 +24,37 @@ const SCAN_TYPES = ['code', 'deps', 'all'];
23
24
  // is being fixed for.
24
25
  const isScanDepth = (v) => SCAN_DEPTHS.includes(v);
25
26
  const isScanType = (v) => SCAN_TYPES.includes(v);
27
+ const isScanOutput = (v) => SCAN_OUTPUTS.includes(v);
28
+ function scanSarif(findings) {
29
+ const ruleId = (type) => `RUFLO-${type.toUpperCase().replace(/[^A-Z0-9]+/g, '-')}`;
30
+ const level = (severity) => severity === 'critical' || severity === 'high' ? 'error' :
31
+ severity === 'medium' ? 'warning' : 'note';
32
+ const rules = [...new Map(findings.map(finding => [finding.type, {
33
+ id: ruleId(finding.type),
34
+ name: finding.type,
35
+ shortDescription: { text: finding.type },
36
+ }])).values()];
37
+ return {
38
+ version: '2.1.0',
39
+ $schema: 'https://json.schemastore.org/sarif-2.1.0.json',
40
+ runs: [{
41
+ tool: { driver: { name: 'Ruflo Security Scan', rules } },
42
+ results: findings.map(finding => {
43
+ const lineMatch = /^(.*):(\d+)$/.exec(finding.location);
44
+ const file = lineMatch?.[1] ?? (finding.location.startsWith('package.json:') ? 'package.json' : finding.location);
45
+ return {
46
+ ruleId: ruleId(finding.type),
47
+ level: level(finding.severity),
48
+ message: { text: finding.description },
49
+ locations: [{ physicalLocation: {
50
+ artifactLocation: { uri: file.replace(/\\/g, '/') },
51
+ ...(lineMatch ? { region: { startLine: Number(lineMatch[2]) } } : {}),
52
+ } }],
53
+ };
54
+ }),
55
+ }],
56
+ };
57
+ }
26
58
  // `full` was never a supported depth, but the CLI itself printed it — the
27
59
  // statusline insight, the announcement, the release-notes blurb, the CLAUDE.md
28
60
  // that `init` generates, and two shipped agent definitions all tell people to
@@ -62,6 +94,7 @@ const scanCommand = {
62
94
  const target = ctx.flags.target || '.';
63
95
  const requestedDepth = ctx.flags.depth || 'standard';
64
96
  const scanType = ctx.flags.type || 'all';
97
+ const requestedOutput = ctx.flags.output || 'text';
65
98
  const fix = ctx.flags.fix;
66
99
  // A security scanner must never silently degrade on an unrecognised enum
67
100
  // value. An unknown --depth used to fall through to the shallowest
@@ -91,6 +124,11 @@ const scanCommand = {
91
124
  output.printError(`Invalid --type '${scanType}'. Expected one of: ${SCAN_TYPES.join(', ')}.`);
92
125
  return { success: false, exitCode: 1 };
93
126
  }
127
+ if (!isScanOutput(requestedOutput)) {
128
+ output.printError(`Invalid --output '${requestedOutput}'. Expected one of: ${SCAN_OUTPUTS.join(', ')}.`);
129
+ return { success: false, exitCode: 1 };
130
+ }
131
+ const outputFormat = requestedOutput;
94
132
  // --target names WHAT gets scanned, and was never validated. A path that
95
133
  // does not exist (or is a file, not a directory) made every phase read
96
134
  // nothing, and the swallowed dir-read catches turned that into zero
@@ -106,11 +144,14 @@ const scanCommand = {
106
144
  output.printError(`Target is not a directory: ${resolvedTarget}`);
107
145
  return { success: false, exitCode: 1 };
108
146
  }
109
- output.writeln();
110
- output.writeln(output.bold('Security Scan'));
111
- output.writeln(output.dim('─'.repeat(50)));
147
+ if (outputFormat === 'text') {
148
+ output.writeln();
149
+ output.writeln(output.bold('Security Scan'));
150
+ output.writeln(output.dim('─'.repeat(50)));
151
+ }
112
152
  const spinner = output.createSpinner({ text: `Scanning ${target}...`, spinner: 'dots' });
113
- spinner.start();
153
+ if (outputFormat === 'text')
154
+ spinner.start();
114
155
  const findings = [];
115
156
  let criticalCount = 0, highCount = 0, mediumCount = 0, lowCount = 0;
116
157
  try {
@@ -151,9 +192,9 @@ const scanCommand = {
151
192
  else
152
193
  lowCount++;
153
194
  findings.push({
154
- severity: sev === 'critical' ? output.error('CRITICAL') :
155
- sev === 'high' ? output.warning('HIGH') :
156
- sev === 'moderate' || sev === 'medium' ? output.warning('MEDIUM') : output.info('LOW'),
195
+ severity: sev === 'critical' ? 'critical' :
196
+ sev === 'high' ? 'high' :
197
+ sev === 'moderate' || sev === 'medium' ? 'medium' : 'low',
157
198
  type: 'Dependency CVE',
158
199
  location: `package.json:${pkg}`,
159
200
  description: title.substring(0, 35),
@@ -206,7 +247,7 @@ const scanCommand = {
206
247
  if (pattern.test(lines[i])) {
207
248
  highCount++;
208
249
  findings.push({
209
- severity: output.warning('HIGH'),
250
+ severity: 'high',
210
251
  type: 'Hardcoded Secret',
211
252
  location: `${path.relative(target, fullPath)}:${i + 1}`,
212
253
  description: type,
@@ -261,7 +302,7 @@ const scanCommand = {
261
302
  else
262
303
  mediumCount++;
263
304
  findings.push({
264
- severity: severity === 'high' ? output.warning('HIGH') : output.warning('MEDIUM'),
305
+ severity: severity === 'high' ? 'high' : 'medium',
265
306
  type,
266
307
  location: `${path.relative(target, fullPath)}:${i + 1}`,
267
308
  description: desc,
@@ -280,35 +321,57 @@ const scanCommand = {
280
321
  const scanDepth = CODE_SCAN_DEPTH[depth];
281
322
  scanCodeDir(path.resolve(target), scanDepth);
282
323
  }
283
- spinner.succeed('Scan complete');
324
+ if (outputFormat === 'text')
325
+ spinner.succeed('Scan complete');
284
326
  // Display results
285
- output.writeln();
286
- if (findings.length > 0) {
287
- output.printTable({
288
- columns: [
289
- { key: 'severity', header: 'Severity', width: 12 },
290
- { key: 'type', header: 'Type', width: 18 },
291
- { key: 'location', header: 'Location', width: 25 },
292
- { key: 'description', header: 'Description', width: 35 },
293
- ],
294
- data: findings.slice(0, 20), // Show first 20
295
- });
296
- if (findings.length > 20) {
297
- output.writeln(output.dim(`... and ${findings.length - 20} more issues`));
327
+ if (outputFormat === 'text') {
328
+ output.writeln();
329
+ if (findings.length > 0) {
330
+ output.printTable({
331
+ columns: [
332
+ { key: 'severity', header: 'Severity', width: 12 },
333
+ { key: 'type', header: 'Type', width: 18 },
334
+ { key: 'location', header: 'Location', width: 25 },
335
+ { key: 'description', header: 'Description', width: 35 },
336
+ ],
337
+ data: findings.slice(0, 20).map(finding => ({
338
+ ...finding,
339
+ severity: finding.severity === 'critical' ? output.error('CRITICAL') :
340
+ finding.severity === 'high' ? output.warning('HIGH') :
341
+ finding.severity === 'medium' ? output.warning('MEDIUM') : output.info('LOW'),
342
+ })), // Show first 20
343
+ });
344
+ if (findings.length > 20) {
345
+ output.writeln(output.dim(`... and ${findings.length - 20} more issues`));
346
+ }
298
347
  }
348
+ else {
349
+ output.writeln(output.success('No security issues found!'));
350
+ }
351
+ output.writeln();
352
+ output.printBox([
353
+ `Target: ${target}`,
354
+ `Depth: ${depth}`,
355
+ `Type: ${scanType}`,
356
+ ``,
357
+ `Critical: ${criticalCount} High: ${highCount} Medium: ${mediumCount} Low: ${lowCount}`,
358
+ `Total Issues: ${findings.length}`,
359
+ ].join('\n'), 'Scan Summary');
299
360
  }
300
- else {
301
- output.writeln(output.success('No security issues found!'));
302
- }
303
- output.writeln();
304
- output.printBox([
305
- `Target: ${target}`,
306
- `Depth: ${depth}`,
307
- `Type: ${scanType}`,
308
- ``,
309
- `Critical: ${criticalCount} High: ${highCount} Medium: ${mediumCount} Low: ${lowCount}`,
310
- `Total Issues: ${findings.length}`,
311
- ].join('\n'), 'Scan Summary');
361
+ const record = {
362
+ timestamp: new Date().toISOString(),
363
+ target,
364
+ depth,
365
+ type: scanType,
366
+ summary: {
367
+ critical: criticalCount,
368
+ high: highCount,
369
+ medium: mediumCount,
370
+ low: lowCount,
371
+ total: findings.length,
372
+ },
373
+ findings,
374
+ };
312
375
  // Persist the scan result so downstream consumers (the statusline's
313
376
  // getSecurityStatus in funnel/local-signals.ts, which reads
314
377
  // `.claude/security-scans/*.json`) reflect real scan state. Best-effort:
@@ -316,20 +379,6 @@ const scanCommand = {
316
379
  try {
317
380
  const scanDirOut = path.join(path.resolve(target), '.claude', 'security-scans');
318
381
  fs.mkdirSync(scanDirOut, { recursive: true });
319
- const record = {
320
- timestamp: new Date().toISOString(),
321
- target,
322
- depth,
323
- type: scanType,
324
- summary: {
325
- critical: criticalCount,
326
- high: highCount,
327
- medium: mediumCount,
328
- low: lowCount,
329
- total: findings.length,
330
- },
331
- findings,
332
- };
333
382
  // Deterministic name keyed on scan config so repeated runs overwrite
334
383
  // rather than accumulate stale reports.
335
384
  const outFile = path.join(scanDirOut, `scan-${scanType}-${depth}.json`);
@@ -340,24 +389,33 @@ const scanCommand = {
340
389
  }
341
390
  // Auto-fix if requested
342
391
  if (fix && criticalCount + highCount > 0) {
343
- output.writeln();
392
+ if (outputFormat === 'text')
393
+ output.writeln();
344
394
  const fixSpinner = output.createSpinner({ text: 'Attempting to fix vulnerabilities...', spinner: 'dots' });
345
- fixSpinner.start();
395
+ if (outputFormat === 'text')
396
+ fixSpinner.start();
346
397
  try {
347
398
  try {
348
399
  execSync('npm audit fix', { cwd: path.resolve(target), encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
349
400
  }
350
401
  catch { /* npm audit fix may exit non-zero */ }
351
- fixSpinner.succeed('Applied available fixes (run scan again to verify)');
402
+ if (outputFormat === 'text')
403
+ fixSpinner.succeed('Applied available fixes (run scan again to verify)');
352
404
  }
353
405
  catch {
354
- fixSpinner.fail('Some fixes could not be applied automatically');
406
+ if (outputFormat === 'text')
407
+ fixSpinner.fail('Some fixes could not be applied automatically');
355
408
  }
356
409
  }
410
+ if (outputFormat === 'json')
411
+ output.printJson(record);
412
+ else if (outputFormat === 'sarif')
413
+ output.printJson(scanSarif(findings));
357
414
  return { success: findings.length === 0 || (criticalCount === 0 && highCount === 0) };
358
415
  }
359
416
  catch (error) {
360
- spinner.fail('Scan failed');
417
+ if (outputFormat === 'text')
418
+ spinner.fail('Scan failed');
361
419
  output.printError(`Error: ${error}`);
362
420
  return { success: false };
363
421
  }
@@ -1039,7 +1097,7 @@ const defendCommand = {
1039
1097
  medium: output.info,
1040
1098
  low: output.dim,
1041
1099
  }[threat.severity] || output.dim;
1042
- output.writeln(` ${severityColor(`[${threat.severity.toUpperCase()}]`)} ${threat.type}`);
1100
+ output.writeln(` ${severityColor.call(output, `[${threat.severity.toUpperCase()}]`)} ${threat.type}`);
1043
1101
  output.writeln(` ${output.dim(threat.description)}`);
1044
1102
  output.writeln(` Confidence: ${(threat.confidence * 100).toFixed(1)}%`);
1045
1103
  output.writeln();
@@ -324,18 +324,18 @@ const restoreCommand = {
324
324
  data: [
325
325
  {
326
326
  component: 'Memory',
327
- status: result.restored.memory ? output.success('Restored') : output.dim('Skipped'),
328
- count: result.stats.memoryEntriesRestored
327
+ status: result.restoredComponents.memory ? output.success('Restored') : output.dim('Skipped'),
328
+ count: result.restoredComponents.memory ? result.stats.memoryEntries : 0
329
329
  },
330
330
  {
331
331
  component: 'Agents',
332
- status: result.restored.agents ? output.success('Restored') : output.dim('Skipped'),
333
- count: result.stats.agentsRestored
332
+ status: result.restoredComponents.agents ? output.success('Restored') : output.dim('Skipped'),
333
+ count: result.restoredComponents.agents ? result.stats.agents : 0
334
334
  },
335
335
  {
336
336
  component: 'Tasks',
337
- status: result.restored.tasks ? output.success('Restored') : output.dim('Skipped'),
338
- count: result.stats.tasksRestored
337
+ status: result.restoredComponents.tasks ? output.success('Restored') : output.dim('Skipped'),
338
+ count: result.restoredComponents.tasks ? result.stats.tasks : 0
339
339
  }
340
340
  ]
341
341
  });
@@ -154,7 +154,7 @@ const createCommand = {
154
154
  type: taskType,
155
155
  description,
156
156
  priority: priority || 'normal',
157
- assignedTo: ctx.flags.assign ? [ctx.flags.assign] : undefined,
157
+ assignTo: ctx.flags.assign ? String(ctx.flags.assign).split(',').map(id => id.trim()).filter(Boolean) : undefined,
158
158
  parentId: ctx.flags.parent,
159
159
  dependencies,
160
160
  tags,
@@ -245,14 +245,15 @@ const listCommand = {
245
245
  }
246
246
  ],
247
247
  action: async (ctx) => {
248
- const status = ctx.flags.all ? 'all' : ctx.flags.status || 'pending,running';
248
+ const requestedStatus = ctx.flags.all ? 'all' : ctx.flags.status || 'pending,in_progress';
249
+ const status = requestedStatus === 'all' ? undefined : requestedStatus.replace(/\brunning\b/g, 'in_progress');
249
250
  const limit = ctx.flags.limit;
250
251
  try {
251
252
  const result = await callMCPTool('task_list', {
252
253
  status,
253
254
  type: ctx.flags.type,
254
255
  priority: ctx.flags.priority,
255
- agentId: ctx.flags.agent,
256
+ assignedTo: ctx.flags.agent,
256
257
  limit,
257
258
  offset: 0
258
259
  });
@@ -277,7 +278,7 @@ const listCommand = {
277
278
  { key: 'progress', header: 'Progress', width: 10 }
278
279
  ],
279
280
  data: result.tasks.map(t => ({
280
- id: t.id,
281
+ id: t.taskId,
281
282
  type: t.type,
282
283
  description: t.description.length > 27
283
284
  ? t.description.slice(0, 27) + '...'
@@ -1005,12 +1005,14 @@ export function generateAutoMemoryHook() {
1005
1005
  */
1006
1006
 
1007
1007
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
1008
- import { join, dirname } from 'path';
1008
+ import { join, dirname, resolve } from 'path';
1009
1009
  import { fileURLToPath } from 'url';
1010
1010
 
1011
1011
  const __filename = fileURLToPath(import.meta.url);
1012
1012
  const __dirname = dirname(__filename);
1013
- const PROJECT_ROOT = join(__dirname, '../..');
1013
+ const PROJECT_ROOT = process.env.CLAUDE_PROJECT_DIR
1014
+ ? resolve(process.env.CLAUDE_PROJECT_DIR)
1015
+ : join(__dirname, '../..');
1014
1016
  const DATA_DIR = join(PROJECT_ROOT, '.claude-flow', 'data');
1015
1017
  const STORE_PATH = join(DATA_DIR, 'auto-memory-store.json');
1016
1018
 
@@ -27,6 +27,12 @@ import { fileURLToPath } from 'url';
27
27
  import { dirname } from 'path';
28
28
  import { trackRequest } from './mcp-tools/request-tracker.js';
29
29
  import { isPolicyEnforcementEnabled, loadMcpPolicy, evaluateToolCall, } from './mcp-tools/policy-enforcer.js';
30
+ /** A failed task can be valid data; only an explicit error denotes tool failure. */
31
+ function hasToolError(result) {
32
+ return result !== null && typeof result === 'object'
33
+ && typeof result.error === 'string'
34
+ && result.error.trim().length > 0;
35
+ }
30
36
  // ESM-compatible __dirname
31
37
  const __filename = fileURLToPath(import.meta.url);
32
38
  const __dirname = dirname(__filename);
@@ -666,11 +672,12 @@ export class MCPServerManager extends EventEmitter {
666
672
  }
667
673
  try {
668
674
  const result = await callMCPTool(toolName, toolParams, { sessionId });
669
- trackRequest(toolName, true);
675
+ const isError = hasToolError(result);
676
+ trackRequest(toolName, !isError);
670
677
  return {
671
678
  jsonrpc: '2.0',
672
679
  id: message.id,
673
- result: { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] },
680
+ result: { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }], isError },
674
681
  };
675
682
  }
676
683
  catch (error) {
@@ -741,7 +748,7 @@ export class MCPServerManager extends EventEmitter {
741
748
  handler: async (input, context) => {
742
749
  try {
743
750
  const result = await callMCPTool(tool.name, input || {}, { sessionId: context?.sessionId || fallbackSessionId });
744
- trackRequest(tool.name, true);
751
+ trackRequest(tool.name, !hasToolError(result));
745
752
  return result;
746
753
  }
747
754
  catch (error) {
@@ -1,15 +1,4 @@
1
- /**
2
- * AgentDB MCP Tools — Phase 6 of ADR-053
3
- *
4
- * Exposes AgentDB v3 controller operations as MCP tools.
5
- * Provides direct access to ReasoningBank, CausalGraph, SkillLibrary,
6
- * AttestationLog, and bridge health through the MCP protocol.
7
- *
8
- * Security: All handlers validate input types, enforce length bounds,
9
- * and sanitize error messages before returning to MCP callers.
10
- *
11
- * @module v3/cli/mcp-tools/agentdb-tools
12
- */
1
+ import { feedbackPatternsSchema, validateFeedbackPatterns } from '../memory/feedback-patterns.js';
13
2
  import { validateIdentifier, validateText } from './validate-input.js';
14
3
  // ===== Shared validation helpers =====
15
4
  const MAX_STRING_LENGTH = 100_000; // 100KB max for any string input
@@ -66,12 +55,6 @@ async function getBridge() {
66
55
  // Lazy-cached modules used by graph-query / graph-pathfinder dispatch.
67
56
  // Caching the resolved namespace avoids per-call dynamic-import overhead
68
57
  // in the ADR-130 hot path (smoke harness measures elapsedMs of every call).
69
- let graphBackendMod = null;
70
- async function getGraphBackend() {
71
- if (!graphBackendMod)
72
- graphBackendMod = await import('../ruvector/graph-backend.js');
73
- return graphBackendMod;
74
- }
75
58
  let graphEdgeWriterMod = null;
76
59
  async function getGraphEdgeWriter() {
77
60
  if (!graphEdgeWriterMod)
@@ -396,6 +379,7 @@ export const agentdbFeedback = {
396
379
  type: 'object',
397
380
  properties: {
398
381
  taskId: { type: 'string', description: 'Task identifier' },
382
+ patterns: feedbackPatternsSchema,
399
383
  success: { type: 'boolean', description: 'Whether task succeeded' },
400
384
  quality: { type: 'number', description: 'Quality score (0-1)' },
401
385
  agent: { type: 'string', description: 'Agent that performed the task' },
@@ -415,9 +399,11 @@ export const agentdbFeedback = {
415
399
  const taskId = validateString(params.taskId, 'taskId', 500);
416
400
  if (!taskId)
417
401
  return { success: false, error: 'taskId is required (non-empty string, max 500 chars)' };
402
+ const patterns = validateFeedbackPatterns(params.patterns);
418
403
  const bridge = await getBridge();
419
404
  const result = await bridge.bridgeRecordFeedback({
420
405
  taskId,
406
+ patterns,
421
407
  success: params.success === true,
422
408
  quality: validateScore(params.quality, 0.85),
423
409
  agent: validateString(params.agent, 'agent', 200) ?? undefined,
@@ -802,21 +788,18 @@ export const agentdbHierarchicalRecall = {
802
788
  // ===== agentdb_consolidate — Run memory consolidation =====
803
789
  export const agentdbConsolidate = {
804
790
  name: 'agentdb_consolidate',
805
- description: 'Run memory consolidation to promote entries across tiers and compress old data Use when generic memory_* tools are wrong because you need AgentDB-specific controllers (HNSW vector search, hierarchical tiers, causal-graph links, pattern store/recall, RaBitQ quantization). For simple key-value persistence, memory_store/memory_retrieve are simpler. For unrelated file work, native Read/Write are fine.',
791
+ description: 'Request memory consolidation. Use when checking whether AgentDB can consolidate retained memories; native file edits cannot perform controller consolidation. Currently returns unsupported when the installed controller is a no-op stub; no entries are promoted or compressed in that case.',
806
792
  inputSchema: {
807
793
  type: 'object',
808
- properties: {
809
- minAge: { type: 'number', description: 'Minimum age in hours since store (optional)' },
810
- maxEntries: { type: 'number', description: 'Maximum entries to consolidate (optional)' },
811
- },
794
+ properties: {},
812
795
  },
813
796
  handler: async (params) => {
814
797
  try {
798
+ if (Object.keys(params).length > 0) {
799
+ return { success: false, status: 'unsupported', error: 'agentdb_consolidate does not support options' };
800
+ }
815
801
  const bridge = await getBridge();
816
- const result = await bridge.bridgeConsolidate({
817
- minAge: typeof params.minAge === 'number' ? Math.max(0, params.minAge) : undefined,
818
- maxEntries: validatePositiveInt(params.maxEntries, 1000, 10_000),
819
- });
802
+ const result = await bridge.bridgeConsolidate({});
820
803
  return result ?? { success: false, error: 'AgentDB bridge not available. Use memory_store/memory_search instead.' };
821
804
  }
822
805
  catch (error) {
@@ -1060,7 +1043,7 @@ export const agentdbCausalNodeDelete = {
1060
1043
  };
1061
1044
  export const agentdbGraphQuery = {
1062
1045
  name: 'agentdb_graph-query',
1063
- description: 'Unified graph traversal across the knowledge graph (ADR-130). Native graph-node k-hop cannot filter by relation and returns an explicit unsupported error for that request; the SQL fallback can filter by relation. Inspect appliedDepth/truncated because SQL is bounded at 3 hops. Semantic and PageRank modes use their own backends. Use when you need structured graph traversal beyond flat memory search.',
1046
+ description: 'Unified graph traversal across the knowledge graph (ADR-130). K-hop queries read committed graph_edges relationships through SQL; a separately populated native graph is not evidence of retained-history coverage. Inspect appliedDepth/truncated because SQL is bounded at 3 hops. maxNodesVisited bounds returned rows, not traversal work or elapsed time. Semantic and PageRank modes use their own backends. Use when you need relationship traversal beyond flat memory search.',
1064
1047
  inputSchema: {
1065
1048
  type: 'object',
1066
1049
  properties: {
@@ -1101,53 +1084,38 @@ export const agentdbGraphQuery = {
1101
1084
  }
1102
1085
  const budgetRaw = (params.complexityBudget ?? {});
1103
1086
  const budget = {
1104
- maxNodesVisited: budgetRaw.maxNodesVisited ?? 10_000,
1105
- maxDepth: budgetRaw.maxDepth ?? 5,
1087
+ maxNodesVisited: validatePositiveInt(budgetRaw.maxNodesVisited, 10_000, 10_000),
1088
+ maxDepth: validatePositiveInt(budgetRaw.maxDepth, 5, 5),
1106
1089
  maxMillis: budgetRaw.maxMillis ?? 50,
1107
1090
  maxMemoryMB: budgetRaw.maxMemoryMB ?? 32,
1108
1091
  };
1109
- const depth = Math.min(validatePositiveInt(params.depth, 2, budget.maxDepth), budget.maxDepth);
1092
+ const requestedDepth = validatePositiveInt(params.depth, 2, 5);
1093
+ const depth = Math.min(requestedDepth, budget.maxDepth);
1110
1094
  const topK = validatePositiveInt(params.topK, 10, MAX_TOP_K);
1111
1095
  const relation = validateString(params.relation, 'relation', 200) ?? undefined;
1112
1096
  // ── k-hop mode ──────────────────────────────────────────────────────────
1113
1097
  if (mode === 'k-hop') {
1114
- // graph-node's kHopNeighbors API has no relation parameter. Its graph
1115
- // and the SQL graph are separate stores, and SQL writes can be async;
1116
- // switching stores here could report a false negative. Refuse the
1117
- // unsupported native query instead of silently widening it.
1118
- try {
1119
- const graphBackend = await getGraphBackend();
1120
- if (await graphBackend.isGraphBackendAvailable()) {
1121
- if (relation) {
1122
- return { success: false, error: 'Native graph-node k-hop does not support relation filtering', unsupported: 'relation', mode, nodeId, relation, backend: 'graph-node' };
1123
- }
1124
- const neighbors = await graphBackend.getNeighbors(nodeId, depth);
1125
- return {
1126
- success: true, mode, nodeId, depth, appliedDepth: depth,
1127
- results: neighbors.map(id => ({ nodeId: id })),
1128
- count: neighbors.length,
1129
- backend: 'graph-node',
1130
- elapsedMs: Date.now() - t0,
1131
- };
1132
- }
1133
- }
1134
- catch { /* fall through to SQL */ }
1135
- // SQL CTE fallback is bounded at depth 3. Report that bound so callers
1136
- // cannot mistake an incomplete depth-5 cycle check for a complete one.
1098
+ // #3315: graph-node and retained SQL are populated independently.
1099
+ // Native availability does not establish historical coverage. Query
1100
+ // the committed relationship source, and fail if it cannot be read.
1137
1101
  try {
1138
1102
  const { getBridgeDb } = await getGraphEdgeWriter();
1139
1103
  const db = await getBridgeDb();
1140
1104
  if (db) {
1141
1105
  const appliedDepth = Math.min(depth, 3);
1142
- const cteSql = buildKHopCTE(nodeId, appliedDepth, relation, budget.maxNodesVisited);
1106
+ const cteSql = buildKHopCTE(nodeId, appliedDepth, relation, budget.maxNodesVisited + 1);
1143
1107
  // graph-edge-writer returns a better-sqlite3 Database after #2431.
1144
1108
  // `db.exec(sql, params)` (sql.js style) is a runner with no result
1145
1109
  // on better-sqlite3 — use `prepare(sql).raw().all(...)` to get the
1146
1110
  // same array-of-arrays shape the downstream code expects.
1147
- const rows = db.prepare(cteSql).raw().all();
1111
+ const allRows = db.prepare(cteSql).raw().all();
1112
+ const resultLimitReached = allRows.length > budget.maxNodesVisited;
1113
+ const rows = allRows.slice(0, budget.maxNodesVisited);
1114
+ const depthLimited = appliedDepth < requestedDepth;
1148
1115
  return {
1149
- success: true, mode, nodeId, depth, appliedDepth,
1150
- ...(appliedDepth < depth ? { truncated: true } : {}),
1116
+ success: true, mode, nodeId, depth, requestedDepth, appliedDepth,
1117
+ depthLimited, resultLimitReached, truncated: depthLimited || resultLimitReached,
1118
+ resultLimit: budget.maxNodesVisited,
1151
1119
  results: rows.map((r) => ({ nodeId: r[0], depth: r[1] })),
1152
1120
  count: rows.length,
1153
1121
  backend: 'sql-cte',
@@ -1155,11 +1123,10 @@ export const agentdbGraphQuery = {
1155
1123
  };
1156
1124
  }
1157
1125
  }
1158
- catch { /* db unavailable */ }
1159
- if (relation) {
1160
- return { success: false, error: 'Relation-filtered k-hop query requires the SQL graph_edges backend', mode, nodeId, relation };
1126
+ catch (error) {
1127
+ return { success: false, error: `Retained SQL graph query failed: ${sanitizeError(error)}`, mode, nodeId };
1161
1128
  }
1162
- return { success: false, error: 'No graph backend available for k-hop query', mode, nodeId };
1129
+ return { success: false, error: 'Retained SQL graph_edges backend is unavailable for k-hop query', mode, nodeId };
1163
1130
  }
1164
1131
  // ── semantic mode ────────────────────────────────────────────────────────
1165
1132
  if (mode === 'semantic') {
@@ -1,7 +1,3 @@
1
- /**
2
- * Hooks MCP Tools
3
- * Provides intelligent hooks functionality via MCP protocol
4
- */
5
1
  import { type MCPTool } from './types.js';
6
2
  import { type RouterEmbedderKind } from '../ruvector/router-embedder.js';
7
3
  /**