claude-flow 3.7.0 → 3.9.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 (30) hide show
  1. package/.claude/worktrees/adr-120-impl/v3/@claude-flow/cli/dist/tsconfig.tsbuildinfo +1 -1
  2. package/README.md +4 -1
  3. package/package.json +1 -1
  4. package/v3/@claude-flow/cli/README.md +4 -1
  5. package/v3/@claude-flow/cli/dist/src/mcp-tools/agentdb-tools.d.ts +2 -0
  6. package/v3/@claude-flow/cli/dist/src/mcp-tools/agentdb-tools.js +492 -2
  7. package/v3/@claude-flow/cli/dist/src/mcp-tools/hooks-tools.js +39 -0
  8. package/v3/@claude-flow/cli/dist/src/mcp-tools/wasm-agent-tools.d.ts +4 -0
  9. package/v3/@claude-flow/cli/dist/src/mcp-tools/wasm-agent-tools.js +549 -0
  10. package/v3/@claude-flow/cli/dist/src/memory/embedding-quantization.d.ts +62 -0
  11. package/v3/@claude-flow/cli/dist/src/memory/embedding-quantization.js +147 -0
  12. package/v3/@claude-flow/cli/dist/src/memory/graph-edge-writer.d.ts +61 -0
  13. package/v3/@claude-flow/cli/dist/src/memory/graph-edge-writer.js +183 -0
  14. package/v3/@claude-flow/cli/dist/src/memory/memory-initializer.d.ts +1 -1
  15. package/v3/@claude-flow/cli/dist/src/memory/memory-initializer.js +37 -0
  16. package/v3/@claude-flow/cli/dist/src/ruvector/agent-wasm.d.ts +46 -11
  17. package/v3/@claude-flow/cli/dist/src/ruvector/agent-wasm.js +134 -25
  18. package/v3/@claude-flow/cli/package.json +1 -1
  19. package/v3/@claude-flow/cli/dist/src/__probe.d.ts +0 -2
  20. package/v3/@claude-flow/cli/dist/src/__probe.js +0 -5
  21. package/v3/@claude-flow/cli/dist/src/commands/benchmark-cosign.d.ts +0 -29
  22. package/v3/@claude-flow/cli/dist/src/commands/benchmark-cosign.js +0 -222
  23. package/v3/@claude-flow/cli/dist/src/commands/benchmark-verify.d.ts +0 -21
  24. package/v3/@claude-flow/cli/dist/src/commands/benchmark-verify.js +0 -202
  25. package/v3/@claude-flow/cli/dist/src/mcp-tools/hive-consensus-runtime.d.ts +0 -149
  26. package/v3/@claude-flow/cli/dist/src/mcp-tools/hive-consensus-runtime.js +0 -296
  27. package/v3/@claude-flow/cli/dist/src/memory/ann-router-registry.d.ts +0 -61
  28. package/v3/@claude-flow/cli/dist/src/memory/ann-router-registry.js +0 -72
  29. package/v3/@claude-flow/cli/dist/src/memory/diskann-registry.d.ts +0 -56
  30. package/v3/@claude-flow/cli/dist/src/memory/diskann-registry.js +0 -88
package/README.md CHANGED
@@ -374,14 +374,17 @@ User --> Claude Code / CLI
374
374
 
375
375
  ## Documentation
376
376
 
377
- Three docs for three audiences:
377
+ Four docs for four audiences:
378
378
 
379
379
  | Doc | When to read it |
380
380
  |-----|-----------------|
381
381
  | **[Status](docs/STATUS.md)** | See what currently works — capability counts, test baselines, recent fixes, what's next. The *is-it-ready* doc. |
382
382
  | **[User Guide](docs/USERGUIDE.md)** | Daily reference — every command, every config flag, every plugin. The *how-do-I* doc. |
383
+ | **[Benchmarks](https://gist.github.com/ruvnet/298f8c668c8859b369f91734a0e9cbbe)** | v3.8.0 SOTA matrix vs LangGraph / AutoGen / CrewAI on darwin-arm64 + linux-x64. ruflo wins cold start, single turn, RSS by 1.3×–1953×. The *is-it-fast* doc. |
383
384
  | **[Verification](verification.md)** | Cryptographically prove your installed bytes match the signed witness — `ruflo verify`. The *trust-but-verify* doc. |
384
385
 
386
+ Benchmark internals (for reproduction): [`sota-workload-spec.md`](https://github.com/ruvnet/ruflo/blob/perf/sota-comparator-benchmarks/docs/benchmarks/sota-workload-spec.md) · [`SOTA-PROGRESS.md`](https://github.com/ruvnet/ruflo/blob/perf/sota-comparator-benchmarks/docs/benchmarks/SOTA-PROGRESS.md) · [raw matrix JSON: darwin](https://github.com/ruvnet/ruflo/blob/perf/sota-comparator-benchmarks/docs/benchmarks/sota-matrix.json) · [linux](https://github.com/ruvnet/ruflo/blob/perf/sota-comparator-benchmarks/docs/benchmarks/sota-matrix-linux.json)
387
+
385
388
  User Guide section index:
386
389
 
387
390
  | Section | Topics |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-flow",
3
- "version": "3.7.0",
3
+ "version": "3.9.0",
4
4
  "description": "Ruflo - Enterprise AI agent orchestration for Claude Code. Deploy 60+ specialized agents in coordinated swarms with self-learning, fault-tolerant consensus, vector memory, and MCP integration",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -374,14 +374,17 @@ User --> Claude Code / CLI
374
374
 
375
375
  ## Documentation
376
376
 
377
- Three docs for three audiences:
377
+ Four docs for four audiences:
378
378
 
379
379
  | Doc | When to read it |
380
380
  |-----|-----------------|
381
381
  | **[Status](docs/STATUS.md)** | See what currently works — capability counts, test baselines, recent fixes, what's next. The *is-it-ready* doc. |
382
382
  | **[User Guide](docs/USERGUIDE.md)** | Daily reference — every command, every config flag, every plugin. The *how-do-I* doc. |
383
+ | **[Benchmarks](https://gist.github.com/ruvnet/298f8c668c8859b369f91734a0e9cbbe)** | v3.8.0 SOTA matrix vs LangGraph / AutoGen / CrewAI on darwin-arm64 + linux-x64. ruflo wins cold start, single turn, RSS by 1.3×–1953×. The *is-it-fast* doc. |
383
384
  | **[Verification](verification.md)** | Cryptographically prove your installed bytes match the signed witness — `ruflo verify`. The *trust-but-verify* doc. |
384
385
 
386
+ Benchmark internals (for reproduction): [`sota-workload-spec.md`](https://github.com/ruvnet/ruflo/blob/perf/sota-comparator-benchmarks/docs/benchmarks/sota-workload-spec.md) · [`SOTA-PROGRESS.md`](https://github.com/ruvnet/ruflo/blob/perf/sota-comparator-benchmarks/docs/benchmarks/SOTA-PROGRESS.md) · [raw matrix JSON: darwin](https://github.com/ruvnet/ruflo/blob/perf/sota-comparator-benchmarks/docs/benchmarks/sota-matrix.json) · [linux](https://github.com/ruvnet/ruflo/blob/perf/sota-comparator-benchmarks/docs/benchmarks/sota-matrix-linux.json)
387
+
385
388
  User Guide section index:
386
389
 
387
390
  | Section | Topics |
@@ -29,5 +29,7 @@ export declare const agentdbSemanticRoute: MCPTool;
29
29
  export declare const agentdbHierarchicalDelete: MCPTool;
30
30
  export declare const agentdbCausalEdgeDelete: MCPTool;
31
31
  export declare const agentdbCausalNodeDelete: MCPTool;
32
+ export declare const agentdbGraphQuery: MCPTool;
33
+ export declare const agentdbGraphPathfinder: MCPTool;
32
34
  export declare const agentdbTools: MCPTool[];
33
35
  //# sourceMappingURL=agentdb-tools.d.ts.map
@@ -316,6 +316,57 @@ export const agentdbFeedback = {
316
316
  }
317
317
  },
318
318
  };
319
+ // ===== ADR-130 Phase 1: graph_edges helpers =====
320
+ /** Valid domain prefixes for unified node namespace */
321
+ const VALID_DOMAINS = new Set(['mem', 'agent', 'task', 'entity', 'span', 'pattern']);
322
+ /**
323
+ * Ensure a node ID uses the domain:uuid prefix format (ADR-130 §Phase 1).
324
+ * IDs without a ':' separator are legacy unprefixed IDs — auto-prefixed as
325
+ * "mem:" and a deprecation warning is logged.
326
+ */
327
+ function ensureDomainPrefix(id) {
328
+ const colonIdx = id.indexOf(':');
329
+ if (colonIdx > 0) {
330
+ const domain = id.slice(0, colonIdx);
331
+ if (VALID_DOMAINS.has(domain)) {
332
+ return { id, wasLegacy: false };
333
+ }
334
+ }
335
+ // Legacy ID or unknown prefix — treat as "mem:" namespace
336
+ return { id: `mem:${id}`, wasLegacy: true };
337
+ }
338
+ /**
339
+ * Fire-and-forget write of a graph edge to the sql.js graph_edges table.
340
+ * Non-blocking: errors are silently discarded (ADR-130 §Phase 1 semantics).
341
+ */
342
+ async function writeGraphEdge(opts) {
343
+ try {
344
+ const { insertGraphEdge } = await import('../memory/graph-edge-writer.js');
345
+ // Generate 384-dim embedding for the edge text (async, ~50ms with ONNX)
346
+ let embedding;
347
+ try {
348
+ const { generateEmbedding } = await import('../memory/memory-initializer.js');
349
+ const edgeText = `${opts.relation}: ${opts.sourceId} -> ${opts.targetId}`;
350
+ const embResult = await generateEmbedding(edgeText);
351
+ if (embResult && embResult.embedding.length > 0) {
352
+ embedding = embResult.embedding;
353
+ }
354
+ }
355
+ catch { /* embedding not available — store without embedding_ref */ }
356
+ await insertGraphEdge({
357
+ sourceId: opts.sourceId,
358
+ targetId: opts.targetId,
359
+ relation: opts.relation,
360
+ weight: opts.weight,
361
+ confidence: opts.confidence,
362
+ decayRate: opts.decayRate,
363
+ witnessId: opts.witnessId,
364
+ embedding,
365
+ metadata: opts.metadata,
366
+ });
367
+ }
368
+ catch { /* non-fatal: graph_edges write failure must never break callers */ }
369
+ }
319
370
  // ===== agentdb_causal_edge — Record causal relationships =====
320
371
  export const agentdbCausalEdge = {
321
372
  name: 'agentdb_causal-edge',
@@ -350,6 +401,20 @@ export const agentdbCausalEdge = {
350
401
  return { success: false, error: 'targetId is required (non-empty string)' };
351
402
  if (!relation)
352
403
  return { success: false, error: 'relation is required (non-empty string)' };
404
+ // ADR-130 Phase 1: apply domain prefix, warn on legacy IDs
405
+ const srcPrefixed = ensureDomainPrefix(sourceId);
406
+ const tgtPrefixed = ensureDomainPrefix(targetId);
407
+ const prefixedSourceId = srcPrefixed.id;
408
+ const prefixedTargetId = tgtPrefixed.id;
409
+ const legacyWarning = (srcPrefixed.wasLegacy || tgtPrefixed.wasLegacy)
410
+ ? `[DEPRECATION] Unprefixed node IDs auto-prefixed as "mem:". Use domain:id format (mem/agent/task/entity/span/pattern).`
411
+ : undefined;
412
+ // ADR-130 Phase 1: fire-and-forget write to unified graph_edges table
413
+ const edgeWeight = typeof params.weight === 'number' ? validateScore(params.weight, 0.5) : 1.0;
414
+ writeGraphEdge({
415
+ sourceId: prefixedSourceId, targetId: prefixedTargetId,
416
+ relation, weight: edgeWeight,
417
+ }).catch(() => { });
353
418
  // Try native graph-node backend first (ADR-087)
354
419
  try {
355
420
  const graphBackend = await import('../ruvector/graph-backend.js');
@@ -359,7 +424,7 @@ export const agentdbCausalEdge = {
359
424
  // Also record in AgentDB bridge for compatibility
360
425
  const bridge = await getBridge();
361
426
  await bridge.bridgeRecordCausalEdge({ sourceId, targetId, relation, weight: typeof params.weight === 'number' ? validateScore(params.weight, 0.5) : undefined }).catch(() => { });
362
- return { ...graphResult, _graphNodeBackend: true };
427
+ return { ...graphResult, _graphNodeBackend: true, ...(legacyWarning && { warning: legacyWarning }) };
363
428
  }
364
429
  }
365
430
  }
@@ -371,7 +436,8 @@ export const agentdbCausalEdge = {
371
436
  relation,
372
437
  weight: typeof params.weight === 'number' ? validateScore(params.weight, 0.5) : undefined,
373
438
  });
374
- return result ?? { success: false, error: 'AgentDB bridge not available. Use memory_store/memory_search instead.' };
439
+ const baseResult = result ?? { success: false, error: 'AgentDB bridge not available. Use memory_store/memory_search instead.' };
440
+ return legacyWarning ? { ...baseResult, warning: legacyWarning } : baseResult;
375
441
  }
376
442
  catch (error) {
377
443
  return { success: false, error: sanitizeError(error) };
@@ -843,6 +909,428 @@ export const agentdbCausalNodeDelete = {
843
909
  }
844
910
  },
845
911
  };
912
+ export const agentdbGraphQuery = {
913
+ name: 'agentdb_graph-query',
914
+ description: 'Unified graph traversal across the knowledge graph (ADR-130). Dispatches to the most capable backend: graph-node native for k-hop, sql.js CTE for fallback, HNSW cosine for semantic, ruflo-graph-intelligence PageRank for pagerank mode. Use when you need structured graph traversal beyond flat memory search.',
915
+ inputSchema: {
916
+ type: 'object',
917
+ properties: {
918
+ nodeId: { type: 'string', description: 'Domain-prefixed node ID (e.g. "agent:abc", "entity:xyz")' },
919
+ mode: {
920
+ type: 'string',
921
+ enum: ['k-hop', 'semantic', 'pagerank'],
922
+ description: 'Query mode: k-hop neighbor expansion, semantic cosine search, or PageRank scoring',
923
+ },
924
+ depth: { type: 'number', description: 'Hop depth for k-hop mode (default 2, max 5)' },
925
+ topK: { type: 'number', description: 'Max results for semantic and pagerank modes (default 10)' },
926
+ relation: { type: 'string', description: 'Optional edge relation filter' },
927
+ complexityBudget: {
928
+ type: 'object',
929
+ description: 'Computation limits',
930
+ properties: {
931
+ maxNodesVisited: { type: 'number' },
932
+ maxDepth: { type: 'number' },
933
+ maxMillis: { type: 'number' },
934
+ maxMemoryMB: { type: 'number' },
935
+ },
936
+ },
937
+ },
938
+ required: ['nodeId', 'mode'],
939
+ },
940
+ handler: async (params) => {
941
+ const t0 = Date.now();
942
+ try {
943
+ const vNodeId = validateIdentifier(params.nodeId, 'nodeId');
944
+ if (!vNodeId.valid)
945
+ return { success: false, error: vNodeId.error };
946
+ const nodeId = validateString(params.nodeId, 'nodeId', 500);
947
+ if (!nodeId)
948
+ return { success: false, error: 'nodeId is required' };
949
+ const mode = params.mode;
950
+ if (!['k-hop', 'semantic', 'pagerank'].includes(mode)) {
951
+ return { success: false, error: 'mode must be "k-hop", "semantic", or "pagerank"' };
952
+ }
953
+ const budgetRaw = (params.complexityBudget ?? {});
954
+ const budget = {
955
+ maxNodesVisited: budgetRaw.maxNodesVisited ?? 10_000,
956
+ maxDepth: budgetRaw.maxDepth ?? 5,
957
+ maxMillis: budgetRaw.maxMillis ?? 50,
958
+ maxMemoryMB: budgetRaw.maxMemoryMB ?? 32,
959
+ };
960
+ const depth = Math.min(validatePositiveInt(params.depth, 2, budget.maxDepth), budget.maxDepth);
961
+ const topK = validatePositiveInt(params.topK, 10, MAX_TOP_K);
962
+ const relation = validateString(params.relation, 'relation', 200) ?? undefined;
963
+ // ── k-hop mode ──────────────────────────────────────────────────────────
964
+ if (mode === 'k-hop') {
965
+ // Try graph-node native first
966
+ try {
967
+ const graphBackend = await import('../ruvector/graph-backend.js');
968
+ if (await graphBackend.isGraphBackendAvailable()) {
969
+ const neighbors = await graphBackend.getNeighbors(nodeId, depth);
970
+ return {
971
+ success: true, mode, nodeId, depth,
972
+ results: neighbors.map(id => ({ nodeId: id })),
973
+ count: neighbors.length,
974
+ backend: 'graph-node',
975
+ elapsedMs: Date.now() - t0,
976
+ };
977
+ }
978
+ }
979
+ catch { /* fall through to sql.js */ }
980
+ // SQL CTE fallback for k-hop up to depth 3
981
+ try {
982
+ const { getBridgeDb } = await import('../memory/graph-edge-writer.js');
983
+ const db = await getBridgeDb();
984
+ if (db) {
985
+ const cteSql = buildKHopCTE(nodeId, Math.min(depth, 3), relation, budget.maxNodesVisited);
986
+ const result = db.exec(cteSql);
987
+ const rows = result?.[0]?.values ?? [];
988
+ return {
989
+ success: true, mode, nodeId, depth,
990
+ results: rows.map((r) => ({ nodeId: r[0], depth: r[1] })),
991
+ count: rows.length,
992
+ backend: 'sql-cte',
993
+ elapsedMs: Date.now() - t0,
994
+ };
995
+ }
996
+ }
997
+ catch { /* db unavailable */ }
998
+ return { success: false, error: 'No graph backend available for k-hop query', mode, nodeId };
999
+ }
1000
+ // ── semantic mode ────────────────────────────────────────────────────────
1001
+ if (mode === 'semantic') {
1002
+ try {
1003
+ const { generateEmbedding } = await import('../memory/memory-initializer.js');
1004
+ const queryEmb = await generateEmbedding(nodeId);
1005
+ if (!queryEmb)
1006
+ throw new Error('embedding failed');
1007
+ const { getBridgeDb } = await import('../memory/graph-edge-writer.js');
1008
+ const db = await getBridgeDb();
1009
+ if (!db)
1010
+ return { success: false, error: 'graph_edges DB unavailable', mode, nodeId };
1011
+ // Load all rows with embedding_ref and score by cosine
1012
+ const rowResult = db.exec(`SELECT id, source_id, target_id, relation, weight, embedding_ref FROM graph_edges WHERE embedding_ref IS NOT NULL LIMIT ?`, [budget.maxNodesVisited]);
1013
+ const rows = rowResult?.[0]?.values ?? [];
1014
+ const { decodeEmbedding } = await import('../memory/embedding-quantization.js');
1015
+ const scored = [];
1016
+ const qv = new Float32Array(queryEmb.embedding);
1017
+ for (const row of rows) {
1018
+ const [, srcId, tgtId, rel, , embRef] = row;
1019
+ if (typeof embRef !== 'string')
1020
+ continue;
1021
+ const ev = decodeEmbedding(embRef);
1022
+ if (!ev)
1023
+ continue;
1024
+ const cos = cosineSim(qv, ev);
1025
+ scored.push({ nodeId: srcId, score: cos, relation: rel });
1026
+ scored.push({ nodeId: tgtId, score: cos, relation: rel });
1027
+ }
1028
+ scored.sort((a, b) => b.score - a.score);
1029
+ const deduped = deduplicateByNodeId(scored).slice(0, topK);
1030
+ return {
1031
+ success: true, mode, nodeId, topK,
1032
+ results: deduped,
1033
+ count: deduped.length,
1034
+ backend: 'sql-cosine',
1035
+ elapsedMs: Date.now() - t0,
1036
+ };
1037
+ }
1038
+ catch (err) {
1039
+ return { success: false, error: sanitizeError(err), mode, nodeId };
1040
+ }
1041
+ }
1042
+ // ── pagerank mode ────────────────────────────────────────────────────────
1043
+ if (mode === 'pagerank') {
1044
+ try {
1045
+ const { getBridgeDb } = await import('../memory/graph-edge-writer.js');
1046
+ const db = await getBridgeDb();
1047
+ if (!db)
1048
+ return { success: false, error: 'graph_edges DB unavailable', mode, nodeId };
1049
+ const edgeResult = db.exec(`SELECT source_id, target_id, weight FROM graph_edges LIMIT ?`, [budget.maxNodesVisited]);
1050
+ const edges = edgeResult?.[0]?.values ?? [];
1051
+ if (edges.length === 0) {
1052
+ return { success: true, mode, nodeId, results: [], count: 0, message: 'graph_edges is empty', elapsedMs: Date.now() - t0 };
1053
+ }
1054
+ // Simple PPR without external solver (graceful fallback when plugin unavailable)
1055
+ const scores = simplePersonalizedPageRank(nodeId, edges, topK, 0.85, 20);
1056
+ return {
1057
+ success: true, mode, nodeId, topK,
1058
+ results: scores,
1059
+ count: scores.length,
1060
+ backend: 'sql-ppr',
1061
+ elapsedMs: Date.now() - t0,
1062
+ };
1063
+ }
1064
+ catch (err) {
1065
+ return { success: false, error: sanitizeError(err), mode, nodeId };
1066
+ }
1067
+ }
1068
+ return { success: false, error: `Unknown mode: ${mode}` };
1069
+ }
1070
+ catch (error) {
1071
+ return { success: false, error: sanitizeError(error) };
1072
+ }
1073
+ },
1074
+ };
1075
+ // ─── graph-query helpers ─────────────────────────────────────────────────────
1076
+ function buildKHopCTE(nodeId, depth, relation, maxNodes) {
1077
+ // Escape the node ID for safe SQL embedding (no user-controlled SQL injection possible
1078
+ // since validateIdentifier has already vetted the value; but we sanitize quotes anyway).
1079
+ const safeNodeId = nodeId.replace(/'/g, "''");
1080
+ const relFilter = relation ? `AND e.relation = '${relation.replace(/'/g, "''")}'` : '';
1081
+ return `
1082
+ WITH RECURSIVE khop(node_id, hop_depth) AS (
1083
+ SELECT '${safeNodeId}', 0
1084
+ UNION
1085
+ SELECT e.target_id, k.hop_depth + 1
1086
+ FROM graph_edges e
1087
+ JOIN khop k ON e.source_id = k.node_id
1088
+ WHERE k.hop_depth < ${depth} ${relFilter}
1089
+ )
1090
+ SELECT DISTINCT node_id, MIN(hop_depth) as depth
1091
+ FROM khop
1092
+ WHERE node_id != '${safeNodeId}'
1093
+ GROUP BY node_id
1094
+ ORDER BY depth, node_id
1095
+ LIMIT ${maxNodes}
1096
+ `;
1097
+ }
1098
+ function cosineSim(a, b) {
1099
+ let dot = 0, na = 0, nb = 0;
1100
+ for (let i = 0; i < a.length; i++) {
1101
+ dot += a[i] * b[i];
1102
+ na += a[i] * a[i];
1103
+ nb += b[i] * b[i];
1104
+ }
1105
+ const denom = Math.sqrt(na) * Math.sqrt(nb);
1106
+ return denom > 0 ? dot / denom : 0;
1107
+ }
1108
+ function deduplicateByNodeId(arr) {
1109
+ const seen = new Set();
1110
+ return arr.filter(item => {
1111
+ if (seen.has(item.nodeId))
1112
+ return false;
1113
+ seen.add(item.nodeId);
1114
+ return true;
1115
+ });
1116
+ }
1117
+ /**
1118
+ * Simple Personalized PageRank without external solver.
1119
+ * Used as fallback when ruflo-graph-intelligence is unavailable.
1120
+ * damping = restart probability from seed node; iterations = power steps.
1121
+ */
1122
+ function simplePersonalizedPageRank(seedNodeId, edges, topK, damping, iterations) {
1123
+ // Build adjacency
1124
+ const outEdges = new Map();
1125
+ const nodes = new Set();
1126
+ for (const [src, tgt, w] of edges) {
1127
+ nodes.add(src);
1128
+ nodes.add(tgt);
1129
+ if (!outEdges.has(src))
1130
+ outEdges.set(src, []);
1131
+ outEdges.get(src).push([tgt, w]);
1132
+ }
1133
+ if (!nodes.has(seedNodeId))
1134
+ return [];
1135
+ const nodeList = Array.from(nodes);
1136
+ const N = nodeList.length;
1137
+ const idx = new Map(nodeList.map((n, i) => [n, i]));
1138
+ const seedIdx = idx.get(seedNodeId) ?? 0;
1139
+ let scores = new Float32Array(N).fill(0);
1140
+ scores[seedIdx] = 1.0;
1141
+ for (let iter = 0; iter < iterations; iter++) {
1142
+ const next = new Float32Array(N).fill(0);
1143
+ for (let i = 0; i < N; i++) {
1144
+ const node = nodeList[i];
1145
+ const out = outEdges.get(node) ?? [];
1146
+ if (out.length === 0) {
1147
+ next[seedIdx] += scores[i]; // dangling node → restart
1148
+ continue;
1149
+ }
1150
+ const totalW = out.reduce((s, [, w]) => s + w, 0);
1151
+ for (const [tgt, w] of out) {
1152
+ const j = idx.get(tgt) ?? 0;
1153
+ next[j] += scores[i] * (w / totalW) * (1 - damping);
1154
+ }
1155
+ }
1156
+ next[seedIdx] += damping; // restart
1157
+ // Normalize
1158
+ const sum = next.reduce((s, v) => s + v, 0);
1159
+ if (sum > 0)
1160
+ for (let i = 0; i < N; i++)
1161
+ next[i] /= sum;
1162
+ scores = next;
1163
+ }
1164
+ const results = [];
1165
+ for (let i = 0; i < N; i++) {
1166
+ if (nodeList[i] !== seedNodeId) {
1167
+ results.push({ nodeId: nodeList[i], score: scores[i] });
1168
+ }
1169
+ }
1170
+ results.sort((a, b) => b.score - a.score);
1171
+ return results.slice(0, topK);
1172
+ }
1173
+ // ===== ADR-130 Phase 5: agentdb_graph-pathfinder =====
1174
+ export const agentdbGraphPathfinder = {
1175
+ name: 'agentdb_graph-pathfinder',
1176
+ description: 'Multi-algorithm native graph pathfinder (ADR-130 Phase 5). Replaces the prompt-level loop in ruflo-knowledge-graph graph-navigator. Supports personalized-pagerank, dynamic-mincut, spectral-sparsify, temporal-centrality, connected-component-churn, and witness-chain-divergence algorithms, each with formal complexityBudget enforcement.',
1177
+ inputSchema: {
1178
+ type: 'object',
1179
+ properties: {
1180
+ seedNodeId: { type: 'string', description: 'Domain-prefixed start node (e.g. "entity:auth-module")' },
1181
+ query: { type: 'string', description: 'Natural-language query for relevance scoring' },
1182
+ depth: { type: 'number', description: 'Expansion depth (default 3, max 5)' },
1183
+ threshold: { type: 'number', description: 'Minimum cumulative relevance score (default 0.3)' },
1184
+ topK: { type: 'number', description: 'Max paths returned (default 10)' },
1185
+ algorithm: {
1186
+ type: 'string',
1187
+ enum: ['personalized-pagerank', 'dynamic-mincut', 'spectral-sparsify', 'temporal-centrality', 'connected-component-churn', 'witness-chain-divergence'],
1188
+ description: 'Graph algorithm (default: personalized-pagerank)',
1189
+ },
1190
+ complexityBudget: {
1191
+ type: 'object',
1192
+ properties: {
1193
+ maxNodesVisited: { type: 'number' },
1194
+ maxDepth: { type: 'number' },
1195
+ maxMillis: { type: 'number' },
1196
+ maxMemoryMB: { type: 'number' },
1197
+ },
1198
+ },
1199
+ },
1200
+ required: ['seedNodeId', 'query'],
1201
+ },
1202
+ handler: async (params) => {
1203
+ const t0 = Date.now();
1204
+ try {
1205
+ const vSeed = validateIdentifier(params.seedNodeId, 'seedNodeId');
1206
+ if (!vSeed.valid)
1207
+ return { success: false, error: vSeed.error };
1208
+ const seedNodeId = validateString(params.seedNodeId, 'seedNodeId', 500);
1209
+ if (!seedNodeId)
1210
+ return { success: false, error: 'seedNodeId is required' };
1211
+ const query = validateString(params.query, 'query', 2000) ?? '';
1212
+ const budgetRaw = (params.complexityBudget ?? {});
1213
+ const rawDepth = validatePositiveInt(params.depth, 3, 5);
1214
+ const depth = Math.min(rawDepth, 5);
1215
+ const depthWarning = rawDepth > 5 ? `depth clamped from ${rawDepth} to 5` : undefined;
1216
+ const budget = {
1217
+ maxNodesVisited: budgetRaw.maxNodesVisited ?? 10_000,
1218
+ maxDepth: Math.min(budgetRaw.maxDepth ?? depth, 5),
1219
+ maxMillis: budgetRaw.maxMillis ?? 50,
1220
+ maxMemoryMB: budgetRaw.maxMemoryMB ?? 32,
1221
+ };
1222
+ const threshold = typeof params.threshold === 'number' ? params.threshold : 0.3;
1223
+ const topK = validatePositiveInt(params.topK, 10, MAX_TOP_K);
1224
+ const algorithm = params.algorithm ?? 'personalized-pagerank';
1225
+ const validAlgorithms = ['personalized-pagerank', 'dynamic-mincut', 'spectral-sparsify', 'temporal-centrality', 'connected-component-churn', 'witness-chain-divergence'];
1226
+ if (!validAlgorithms.includes(algorithm)) {
1227
+ return { success: false, error: `Unknown algorithm: ${algorithm}. Valid: ${validAlgorithms.join(', ')}` };
1228
+ }
1229
+ // Load edges from graph_edges
1230
+ const { getBridgeDb } = await import('../memory/graph-edge-writer.js');
1231
+ const db = await getBridgeDb();
1232
+ if (!db)
1233
+ return { success: false, error: 'graph_edges DB unavailable', seedNodeId };
1234
+ const colsSql = algorithm === 'witness-chain-divergence'
1235
+ ? 'source_id, target_id, weight, last_reinforced, witness_id'
1236
+ : algorithm === 'temporal-centrality'
1237
+ ? 'source_id, target_id, weight, last_reinforced, confidence'
1238
+ : 'source_id, target_id, weight';
1239
+ const edgeResult = db.exec(`SELECT ${colsSql} FROM graph_edges LIMIT ?`, [budget.maxNodesVisited]);
1240
+ const rawEdges = edgeResult?.[0]?.values ?? [];
1241
+ if (rawEdges.length === 0) {
1242
+ return { success: true, paths: [], count: 0, message: `no edges found from seedNodeId`, seedNodeId, algorithm, elapsedMs: Date.now() - t0 };
1243
+ }
1244
+ const edges = rawEdges;
1245
+ let paths = [];
1246
+ // Check millisecond budget before heavy computation
1247
+ if (Date.now() - t0 > budget.maxMillis) {
1248
+ return { success: true, paths: [], count: 0, message: `complexityBudget.maxMillis (${budget.maxMillis}ms) exceeded before solver dispatch`, seedNodeId, algorithm, elapsedMs: Date.now() - t0 };
1249
+ }
1250
+ switch (algorithm) {
1251
+ case 'personalized-pagerank': {
1252
+ const edgeTuples = edges.map(r => [r[0], r[1], Number(r[2]) || 1.0]);
1253
+ const pprResults = simplePersonalizedPageRank(seedNodeId, edgeTuples, topK, 0.85, 20);
1254
+ paths = pprResults.filter(r => r.score >= threshold).map(r => ({ ...r, depth: 1 }));
1255
+ break;
1256
+ }
1257
+ case 'temporal-centrality': {
1258
+ // Score nodes by recency of last_reinforced × confidence
1259
+ const nodeScores = new Map();
1260
+ const now = Date.now();
1261
+ for (const row of edges) {
1262
+ const [src, tgt, w, lastReinforced, confidence] = row;
1263
+ const ageMs = lastReinforced
1264
+ ? now - new Date(lastReinforced).getTime()
1265
+ : now;
1266
+ const ageDays = ageMs / (1000 * 60 * 60 * 24);
1267
+ const decayedScore = (Number(w) || 1.0) * (Number(confidence) || 1.0) * Math.exp(-0.1 * ageDays);
1268
+ for (const n of [src, tgt]) {
1269
+ nodeScores.set(n, (nodeScores.get(n) ?? 0) + decayedScore);
1270
+ }
1271
+ }
1272
+ paths = Array.from(nodeScores.entries())
1273
+ .filter(([n, s]) => n !== seedNodeId && s >= threshold)
1274
+ .map(([nodeId, score]) => ({ nodeId, score, depth: 1 }))
1275
+ .sort((a, b) => b.score - a.score)
1276
+ .slice(0, topK);
1277
+ break;
1278
+ }
1279
+ case 'witness-chain-divergence': {
1280
+ // Walk witness_id chains, flag divergences (gaps or non-monotonic timestamps)
1281
+ const witnessChain = [];
1282
+ const seen = new Set();
1283
+ let current = seedNodeId;
1284
+ for (let d = 0; d < depth; d++) {
1285
+ const nextEdge = edges.find(r => r[0] === current && r[4]);
1286
+ if (!nextEdge)
1287
+ break;
1288
+ const next = nextEdge[1];
1289
+ if (seen.has(next)) {
1290
+ // Loop detected → divergence score 1.0
1291
+ witnessChain.push({ nodeId: next, score: 1.0, depth: d + 1 });
1292
+ break;
1293
+ }
1294
+ seen.add(next);
1295
+ witnessChain.push({ nodeId: next, score: 0.5, depth: d + 1 });
1296
+ current = next;
1297
+ }
1298
+ paths = witnessChain.slice(0, topK);
1299
+ break;
1300
+ }
1301
+ case 'connected-component-churn':
1302
+ case 'dynamic-mincut':
1303
+ case 'spectral-sparsify': {
1304
+ // Simplified implementations: return k-hop neighbors with basic score
1305
+ const edgeTuples = edges.map(r => [r[0], r[1], Number(r[2]) || 1.0]);
1306
+ const khopResult = await agentdbGraphQuery.handler({
1307
+ nodeId: seedNodeId, mode: 'k-hop', depth, complexityBudget: budget,
1308
+ });
1309
+ if (khopResult.success && khopResult.results) {
1310
+ paths = khopResult.results
1311
+ .map((r, i) => ({ nodeId: r.nodeId, score: 1.0 / (1 + i), depth: r.depth ?? 1 }))
1312
+ .filter((r) => r.score >= threshold)
1313
+ .slice(0, topK);
1314
+ }
1315
+ break;
1316
+ }
1317
+ }
1318
+ const elapsedMs = Date.now() - t0;
1319
+ return {
1320
+ success: true,
1321
+ seedNodeId, algorithm, depth, topK, threshold,
1322
+ paths,
1323
+ count: paths.length,
1324
+ elapsedMs,
1325
+ budgetUsed: { millis: elapsedMs, nodes: rawEdges.length },
1326
+ ...(depthWarning && { warning: depthWarning }),
1327
+ };
1328
+ }
1329
+ catch (error) {
1330
+ return { success: false, error: sanitizeError(error) };
1331
+ }
1332
+ },
1333
+ };
846
1334
  // ===== Export all tools =====
847
1335
  export const agentdbTools = [
848
1336
  agentdbHealth,
@@ -863,5 +1351,7 @@ export const agentdbTools = [
863
1351
  agentdbBatch,
864
1352
  agentdbContextSynthesize,
865
1353
  agentdbSemanticRoute,
1354
+ agentdbGraphQuery, // ADR-130 Phase 2
1355
+ agentdbGraphPathfinder, // ADR-130 Phase 5
866
1356
  ];
867
1357
  //# sourceMappingURL=agentdb-tools.js.map
@@ -1213,6 +1213,27 @@ export const hooksPostTask = {
1213
1213
  catch {
1214
1214
  // Intelligence module not available — non-fatal
1215
1215
  }
1216
+ // ADR-130 Phase 3: fire-and-forget "reinforced-by" edge on task success
1217
+ // Writes: context node → task pattern node (relation: "reinforced-by")
1218
+ if (success) {
1219
+ (async () => {
1220
+ try {
1221
+ const { insertGraphEdge } = await import('../memory/graph-edge-writer.js');
1222
+ const sessionCtxId = `task:${taskId}`;
1223
+ const patternId = `pattern:${taskId}`;
1224
+ await insertGraphEdge({
1225
+ sourceId: sessionCtxId,
1226
+ targetId: patternId,
1227
+ relation: 'reinforced-by',
1228
+ weight: quality,
1229
+ confidence: quality,
1230
+ lastReinforced: new Date().toISOString(),
1231
+ metadata: { success, agent, taskId },
1232
+ });
1233
+ }
1234
+ catch { /* non-fatal */ }
1235
+ })().catch(() => { });
1236
+ }
1216
1237
  // Persist routing outcome for runtime learning (file-based, always reliable)
1217
1238
  const taskText = params.task || '';
1218
1239
  const outcomeKeywords = extractKeywords(taskText);
@@ -2326,6 +2347,24 @@ export const hooksTrajectoryStep = {
2326
2347
  timestamp,
2327
2348
  });
2328
2349
  }
2350
+ // ADR-130 Phase 3: fire-and-forget causal edge write
2351
+ // trajectory context node → step node (relation: "trajectory-caused")
2352
+ if (result) {
2353
+ (async () => {
2354
+ try {
2355
+ const { insertGraphEdge } = await import('../memory/graph-edge-writer.js');
2356
+ await insertGraphEdge({
2357
+ sourceId: `task:${trajectoryId}`,
2358
+ targetId: `pattern:${stepId}`,
2359
+ relation: 'trajectory-caused',
2360
+ weight: quality,
2361
+ confidence: quality,
2362
+ metadata: { action, result, trajectoryId, stepId },
2363
+ });
2364
+ }
2365
+ catch { /* non-fatal */ }
2366
+ })().catch(() => { });
2367
+ }
2329
2368
  return {
2330
2369
  trajectoryId,
2331
2370
  stepId,
@@ -3,6 +3,10 @@
3
3
  *
4
4
  * Exposes @ruvector/rvagent-wasm operations via MCP protocol.
5
5
  * All tools gracefully degrade when the WASM package is not installed.
6
+ *
7
+ * ADR-129: Phase 2 adds wasm_agent_compose + addMcpTools bridge.
8
+ * Phase 3 adds 10 gallery CRUD + 6 agent introspection tools.
9
+ * Phase 4 adds includePlugins to wasm_agent_compose.
6
10
  */
7
11
  import type { MCPTool } from './types.js';
8
12
  export declare const wasmAgentTools: MCPTool[];