@swarmvaultai/engine 0.1.23 → 0.1.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -192,6 +192,9 @@ function graphPageById(graph) {
192
192
  function graphNodeById(graph) {
193
193
  return new Map(graph.nodes.map((node) => [node.id, node]));
194
194
  }
195
+ function exportHyperedgeNodeId(hyperedge) {
196
+ return `hyperedge:${hyperedge.id}`;
197
+ }
195
198
  function sortedCommunities(graph) {
196
199
  const known = (graph.communities ?? []).map((community) => ({
197
200
  ...community,
@@ -356,6 +359,11 @@ function renderGraphMl(graph) {
356
359
  { id: "n_community", for: "node", name: "communityId", type: "string" },
357
360
  { id: "n_degree", for: "node", name: "degree", type: "double" },
358
361
  { id: "n_bridge", for: "node", name: "bridgeScore", type: "double" },
362
+ { id: "n_relation", for: "node", name: "relation", type: "string" },
363
+ { id: "n_evidence", for: "node", name: "evidenceClass", type: "string" },
364
+ { id: "n_confidence", for: "node", name: "confidence", type: "double" },
365
+ { id: "n_source_pages", for: "node", name: "sourcePageIds", type: "string" },
366
+ { id: "n_why", for: "node", name: "why", type: "string" },
359
367
  { id: "e_relation", for: "edge", name: "relation", type: "string" },
360
368
  { id: "e_status", for: "edge", name: "status", type: "string" },
361
369
  { id: "e_evidence", for: "edge", name: "evidenceClass", type: "string" },
@@ -394,6 +402,21 @@ function renderGraphMl(graph) {
394
402
  }
395
403
  lines.push(" </node>");
396
404
  }
405
+ for (const hyperedge of [...graph.hyperedges ?? []].sort((left, right) => left.id.localeCompare(right.id))) {
406
+ lines.push(` <node id="${xmlEscape(exportHyperedgeNodeId(hyperedge))}">`);
407
+ for (const [key, value] of [
408
+ ["n_label", hyperedge.label],
409
+ ["n_type", "hyperedge"],
410
+ ["n_relation", hyperedge.relation],
411
+ ["n_evidence", hyperedge.evidenceClass],
412
+ ["n_confidence", hyperedge.confidence],
413
+ ["n_source_pages", hyperedge.sourcePageIds],
414
+ ["n_why", hyperedge.why]
415
+ ]) {
416
+ lines.push(` <data key="${key}">${xmlEscape(graphMlData(value))}</data>`);
417
+ }
418
+ lines.push(" </node>");
419
+ }
397
420
  for (const edge of [...graph.edges].sort((left, right) => left.id.localeCompare(right.id))) {
398
421
  lines.push(` <edge id="${xmlEscape(edge.id)}" source="${xmlEscape(edge.source)}" target="${xmlEscape(edge.target)}">`);
399
422
  for (const [key, value] of [
@@ -407,6 +430,23 @@ function renderGraphMl(graph) {
407
430
  }
408
431
  lines.push(" </edge>");
409
432
  }
433
+ for (const hyperedge of [...graph.hyperedges ?? []].sort((left, right) => left.id.localeCompare(right.id))) {
434
+ for (const nodeId of hyperedge.nodeIds) {
435
+ lines.push(
436
+ ` <edge id="${xmlEscape(`member:${hyperedge.id}:${nodeId}`)}" source="${xmlEscape(exportHyperedgeNodeId(hyperedge))}" target="${xmlEscape(nodeId)}">`
437
+ );
438
+ for (const [key, value] of [
439
+ ["e_relation", "group_member"],
440
+ ["e_status", "inferred"],
441
+ ["e_evidence", hyperedge.evidenceClass],
442
+ ["e_confidence", hyperedge.confidence],
443
+ ["e_provenance", hyperedge.sourcePageIds]
444
+ ]) {
445
+ lines.push(` <data key="${key}">${xmlEscape(graphMlData(value))}</data>`);
446
+ }
447
+ lines.push(" </edge>");
448
+ }
449
+ }
410
450
  lines.push(" </graph>", "</graphml>", "");
411
451
  return lines.join("\n");
412
452
  }
@@ -433,13 +473,41 @@ function renderCypher(graph) {
433
473
  lines.push(`MERGE (n:SwarmNode {id: '${cypherEscape(node.id)}'}) SET n += { ${props} };`);
434
474
  }
435
475
  lines.push("");
476
+ for (const hyperedge of [...graph.hyperedges ?? []].sort((left, right) => left.id.localeCompare(right.id))) {
477
+ const hyperedgeNodeId = exportHyperedgeNodeId(hyperedge);
478
+ lines.push(
479
+ `MERGE (h:SwarmNode {id: '${cypherEscape(hyperedgeNodeId)}'}) SET h += { id: '${cypherEscape(hyperedgeNodeId)}', label: '${cypherEscape(
480
+ hyperedge.label
481
+ )}', type: 'hyperedge', relation: '${cypherEscape(hyperedge.relation)}', evidenceClass: '${cypherEscape(
482
+ hyperedge.evidenceClass
483
+ )}', confidence: ${hyperedge.confidence}, sourcePageIds: '${cypherEscape(JSON.stringify(hyperedge.sourcePageIds))}', why: '${cypherEscape(
484
+ hyperedge.why
485
+ )}' };`
486
+ );
487
+ }
488
+ if ((graph.hyperedges ?? []).length) {
489
+ lines.push("");
490
+ }
491
+ for (const hyperedge of [...graph.hyperedges ?? []].sort((left, right) => left.id.localeCompare(right.id))) {
492
+ const hyperedgeNodeId = exportHyperedgeNodeId(hyperedge);
493
+ for (const nodeId of hyperedge.nodeIds) {
494
+ lines.push(
495
+ `MATCH (h:SwarmNode {id: '${cypherEscape(hyperedgeNodeId)}'}), (n:SwarmNode {id: '${cypherEscape(nodeId)}'})`,
496
+ `MERGE (h)-[r:GROUP_MEMBER {id: '${cypherEscape(`member:${hyperedge.id}:${nodeId}`)}'}]->(n)`,
497
+ `SET r += { relation: 'group_member', status: 'inferred', evidenceClass: '${cypherEscape(
498
+ hyperedge.evidenceClass
499
+ )}', confidence: ${hyperedge.confidence}, provenance: '${cypherEscape(JSON.stringify(hyperedge.sourcePageIds))}' };`
500
+ );
501
+ }
502
+ }
503
+ lines.push("");
436
504
  for (const edge of [...graph.edges].sort((left, right) => left.id.localeCompare(right.id))) {
437
505
  lines.push(
438
506
  `MATCH (a:SwarmNode {id: '${cypherEscape(edge.source)}'}), (b:SwarmNode {id: '${cypherEscape(edge.target)}'})`,
439
507
  `MERGE (a)-[r:${relationType(edge.relation)} {id: '${cypherEscape(edge.id)}'}]->(b)`,
440
508
  `SET r += { relation: '${cypherEscape(edge.relation)}', status: '${cypherEscape(edge.status)}', evidenceClass: '${cypherEscape(
441
509
  edge.evidenceClass
442
- )}', confidence: ${edge.confidence}, provenance: '${cypherEscape(JSON.stringify(edge.provenance))}' };`
510
+ )}', confidence: ${edge.confidence}, provenance: '${cypherEscape(JSON.stringify(edge.provenance))}'${edge.similarityReasons?.length ? `, similarityReasons: '${cypherEscape(JSON.stringify(edge.similarityReasons))}'` : ""} };`
443
511
  );
444
512
  }
445
513
  lines.push("");
@@ -5910,6 +5978,331 @@ async function runDeepLint(rootDir, structuralFindings, options = {}) {
5910
5978
  );
5911
5979
  }
5912
5980
 
5981
+ // src/graph-enrichment.ts
5982
+ var STOPWORDS2 = /* @__PURE__ */ new Set([
5983
+ "about",
5984
+ "after",
5985
+ "also",
5986
+ "among",
5987
+ "and",
5988
+ "around",
5989
+ "because",
5990
+ "been",
5991
+ "being",
5992
+ "between",
5993
+ "both",
5994
+ "does",
5995
+ "from",
5996
+ "into",
5997
+ "just",
5998
+ "like",
5999
+ "many",
6000
+ "more",
6001
+ "most",
6002
+ "much",
6003
+ "note",
6004
+ "only",
6005
+ "other",
6006
+ "over",
6007
+ "same",
6008
+ "such",
6009
+ "than",
6010
+ "that",
6011
+ "their",
6012
+ "them",
6013
+ "there",
6014
+ "these",
6015
+ "this",
6016
+ "through",
6017
+ "under",
6018
+ "very",
6019
+ "what",
6020
+ "when",
6021
+ "where",
6022
+ "which",
6023
+ "while",
6024
+ "with",
6025
+ "would",
6026
+ "your"
6027
+ ]);
6028
+ function normalizeValue(value) {
6029
+ return normalizeWhitespace(value).toLowerCase();
6030
+ }
6031
+ function addFeature(bucket, reason, value) {
6032
+ if (!value) {
6033
+ return;
6034
+ }
6035
+ const normalized = normalizeValue(value);
6036
+ if (!normalized) {
6037
+ return;
6038
+ }
6039
+ if (!bucket.has(reason)) {
6040
+ bucket.set(reason, /* @__PURE__ */ new Set());
6041
+ }
6042
+ bucket.get(reason)?.add(normalized);
6043
+ }
6044
+ function themeTokens(value) {
6045
+ return uniqueBy(
6046
+ normalizeValue(value).split(/[^a-z0-9]+/i).filter((token) => token.length >= 4 && !STOPWORDS2.has(token)),
6047
+ (token) => token
6048
+ ).slice(0, 6);
6049
+ }
6050
+ function pairKey(left, right) {
6051
+ return [left, right].sort((a, b) => a.localeCompare(b)).join("|");
6052
+ }
6053
+ function hasDistinctScope(left, right) {
6054
+ if (left.pageId && right.pageId && left.pageId !== right.pageId) {
6055
+ return true;
6056
+ }
6057
+ const leftSources = new Set(left.sourceIds);
6058
+ const rightSources = new Set(right.sourceIds);
6059
+ const leftOnly = [...leftSources].some((sourceId) => !rightSources.has(sourceId));
6060
+ const rightOnly = [...rightSources].some((sourceId) => !leftSources.has(sourceId));
6061
+ return leftOnly || rightOnly;
6062
+ }
6063
+ function supportCount(values) {
6064
+ return values?.size ?? 0;
6065
+ }
6066
+ function similarityScore(reasons) {
6067
+ const concept = supportCount(reasons.get("shared_concept"));
6068
+ const entity = supportCount(reasons.get("shared_entity"));
6069
+ const symbol = supportCount(reasons.get("shared_symbol"));
6070
+ const rationale = supportCount(reasons.get("shared_rationale_theme"));
6071
+ const sourceType = supportCount(reasons.get("shared_source_type"));
6072
+ const tag = supportCount(reasons.get("shared_tag"));
6073
+ const categoryCount = [...reasons.keys()].length;
6074
+ const weighted = (concept ? 0.46 + Math.min(0.12, (concept - 1) * 0.04) : 0) + (entity ? 0.34 + Math.min(0.1, (entity - 1) * 0.03) : 0) + (symbol ? 0.24 + Math.min(0.08, (symbol - 1) * 0.02) : 0) + (rationale ? 0.18 + Math.min(0.08, (rationale - 1) * 0.03) : 0) + (sourceType ? 0.1 : 0) + (tag ? 0.12 + Math.min(0.04, (tag - 1) * 0.02) : 0);
6075
+ const categoryBonus = categoryCount >= 3 ? 0.08 : categoryCount === 2 ? 0.04 : 0;
6076
+ return Math.min(0.96, weighted + categoryBonus);
6077
+ }
6078
+ function describeSimilarityReasons(reasons) {
6079
+ if (!reasons?.length) {
6080
+ return "This link is inferred from multiple shared graph features.";
6081
+ }
6082
+ const labels = reasons.map(
6083
+ (reason) => reason === "shared_concept" ? "shared concepts" : reason === "shared_entity" ? "shared entities" : reason === "shared_symbol" ? "shared symbols" : reason === "shared_rationale_theme" ? "shared rationale themes" : reason === "shared_source_type" ? "shared source type" : "shared tags"
6084
+ );
6085
+ return `This link is inferred from ${labels.join(", ")}.`;
6086
+ }
6087
+ function nodeContexts(nodes, manifests, analyses) {
6088
+ const manifestsBySourceId = new Map(manifests.map((manifest) => [manifest.sourceId, manifest]));
6089
+ const analysesBySourceId = new Map(analyses.map((analysis) => [analysis.sourceId, analysis]));
6090
+ return nodes.filter((node) => node.type !== "symbol" && node.type !== "concept" && node.type !== "entity").map((node) => {
6091
+ const features = /* @__PURE__ */ new Map();
6092
+ if (node.type === "source" || node.type === "module") {
6093
+ for (const sourceId of node.sourceIds) {
6094
+ const analysis = analysesBySourceId.get(sourceId);
6095
+ const manifest = manifestsBySourceId.get(sourceId);
6096
+ if (!analysis) {
6097
+ continue;
6098
+ }
6099
+ for (const concept of analysis.concepts) {
6100
+ addFeature(features, "shared_concept", concept.name);
6101
+ }
6102
+ for (const entity of analysis.entities) {
6103
+ addFeature(features, "shared_entity", entity.name);
6104
+ }
6105
+ if (manifest?.sourceType) {
6106
+ addFeature(features, "shared_source_type", manifest.sourceType);
6107
+ }
6108
+ if (analysis.code) {
6109
+ const exportedSymbols = analysis.code.symbols.filter((symbol) => symbol.exported);
6110
+ for (const symbol of (exportedSymbols.length ? exportedSymbols : analysis.code.symbols).slice(0, 12)) {
6111
+ addFeature(features, "shared_symbol", symbol.name);
6112
+ }
6113
+ }
6114
+ for (const rationale of analysis.rationales) {
6115
+ for (const token of themeTokens(rationale.text)) {
6116
+ addFeature(features, "shared_rationale_theme", token);
6117
+ }
6118
+ }
6119
+ }
6120
+ } else if (node.type === "rationale") {
6121
+ for (const sourceId of node.sourceIds) {
6122
+ const analysis = analysesBySourceId.get(sourceId);
6123
+ const manifest = manifestsBySourceId.get(sourceId);
6124
+ if (manifest?.sourceType) {
6125
+ addFeature(features, "shared_source_type", manifest.sourceType);
6126
+ }
6127
+ const rationale = analysis?.rationales.find((item) => item.id === node.id);
6128
+ for (const token of themeTokens(rationale?.text ?? node.label)) {
6129
+ addFeature(features, "shared_rationale_theme", token);
6130
+ }
6131
+ }
6132
+ }
6133
+ return { node, featureValues: features };
6134
+ }).filter((context) => context.featureValues.size > 0);
6135
+ }
6136
+ function buildSemanticSimilarityEdges(nodes, edges, manifests, analyses) {
6137
+ const contexts = nodeContexts(nodes, manifests, analyses);
6138
+ const contextsById = new Map(contexts.map((context) => [context.node.id, context]));
6139
+ const directPairs = new Set(edges.map((edge) => pairKey(edge.source, edge.target)));
6140
+ const pairReasons = /* @__PURE__ */ new Map();
6141
+ for (const reason of ["shared_concept", "shared_entity", "shared_symbol", "shared_rationale_theme", "shared_source_type"]) {
6142
+ const buckets = /* @__PURE__ */ new Map();
6143
+ for (const context of contexts) {
6144
+ for (const value of context.featureValues.get(reason) ?? []) {
6145
+ const bucketId = `${context.node.type}:${reason}:${value}`;
6146
+ if (!buckets.has(bucketId)) {
6147
+ buckets.set(bucketId, []);
6148
+ }
6149
+ buckets.get(bucketId)?.push(context.node.id);
6150
+ }
6151
+ }
6152
+ for (const [bucketId, nodeIds] of buckets.entries()) {
6153
+ if (nodeIds.length < 2) {
6154
+ continue;
6155
+ }
6156
+ const value = bucketId.slice(bucketId.indexOf(`${reason}:`) + `${reason}:`.length);
6157
+ const uniqueNodeIds = uniqueBy(nodeIds, (nodeId) => nodeId).sort((left, right) => left.localeCompare(right));
6158
+ for (let index = 0; index < uniqueNodeIds.length; index++) {
6159
+ const left = contextsById.get(uniqueNodeIds[index]);
6160
+ if (!left) {
6161
+ continue;
6162
+ }
6163
+ for (let cursor = index + 1; cursor < uniqueNodeIds.length; cursor++) {
6164
+ const right = contextsById.get(uniqueNodeIds[cursor]);
6165
+ if (!right || !hasDistinctScope(left.node, right.node)) {
6166
+ continue;
6167
+ }
6168
+ const key = pairKey(left.node.id, right.node.id);
6169
+ if (directPairs.has(key)) {
6170
+ continue;
6171
+ }
6172
+ if (!pairReasons.has(key)) {
6173
+ pairReasons.set(key, /* @__PURE__ */ new Map());
6174
+ }
6175
+ if (!pairReasons.get(key)?.has(reason)) {
6176
+ pairReasons.get(key)?.set(reason, /* @__PURE__ */ new Set());
6177
+ }
6178
+ pairReasons.get(key)?.get(reason)?.add(value);
6179
+ }
6180
+ }
6181
+ }
6182
+ }
6183
+ return [...pairReasons.entries()].flatMap(([key, reasons]) => {
6184
+ const [leftId, rightId] = key.split("|");
6185
+ const left = contextsById.get(leftId)?.node;
6186
+ const right = contextsById.get(rightId)?.node;
6187
+ if (!left || !right) {
6188
+ return [];
6189
+ }
6190
+ const confidence = similarityScore(reasons);
6191
+ if (confidence < 0.5) {
6192
+ return [];
6193
+ }
6194
+ return [
6195
+ {
6196
+ id: `similar:${sha256(`${left.id}|${right.id}|${[...reasons.keys()].sort().join(",")}`).slice(0, 16)}`,
6197
+ source: left.id,
6198
+ target: right.id,
6199
+ relation: "semantically_similar_to",
6200
+ status: "inferred",
6201
+ evidenceClass: "inferred",
6202
+ confidence,
6203
+ provenance: uniqueBy(
6204
+ [...left.sourceIds, ...right.sourceIds].sort((a, b) => a.localeCompare(b)),
6205
+ (value) => value
6206
+ ),
6207
+ similarityReasons: [...reasons.keys()].sort((a, b) => a.localeCompare(b))
6208
+ }
6209
+ ];
6210
+ }).sort((left, right) => right.confidence - left.confidence || left.id.localeCompare(right.id));
6211
+ }
6212
+ function buildTopicHyperedges(graph) {
6213
+ const nodesById = new Map(graph.nodes.map((node) => [node.id, node]));
6214
+ const connectedSources = /* @__PURE__ */ new Map();
6215
+ for (const edge of graph.edges) {
6216
+ if (edge.relation !== "mentions" || edge.evidenceClass !== "extracted") {
6217
+ continue;
6218
+ }
6219
+ const sourceNode = nodesById.get(edge.source);
6220
+ const targetNode = nodesById.get(edge.target);
6221
+ if (sourceNode?.type !== "source" || !(targetNode?.type === "concept" || targetNode?.type === "entity")) {
6222
+ continue;
6223
+ }
6224
+ if (!connectedSources.has(targetNode.id)) {
6225
+ connectedSources.set(targetNode.id, []);
6226
+ }
6227
+ connectedSources.get(targetNode.id)?.push(sourceNode.id);
6228
+ }
6229
+ return [...connectedSources.entries()].flatMap(([anchorId, members]) => {
6230
+ const anchor = nodesById.get(anchorId);
6231
+ const uniqueMembers = uniqueBy(members, (member) => member).sort((left, right) => left.localeCompare(right));
6232
+ if (!anchor || uniqueMembers.length < 3) {
6233
+ return [];
6234
+ }
6235
+ const nodeIds = [anchor.id, ...uniqueMembers];
6236
+ const sourcePageIds = uniqueBy(nodeIds.map((nodeId) => nodesById.get(nodeId)?.pageId ?? "").filter(Boolean), (value) => value);
6237
+ return [
6238
+ {
6239
+ id: `hyper:${sha256(`participate_in|${anchor.id}|${uniqueMembers.join("|")}`).slice(0, 16)}`,
6240
+ label: anchor.label,
6241
+ relation: "participate_in",
6242
+ nodeIds,
6243
+ evidenceClass: "extracted",
6244
+ confidence: Math.min(0.96, 0.72 + uniqueMembers.length * 0.06),
6245
+ sourcePageIds,
6246
+ why: `${uniqueMembers.length} source nodes converge on ${anchor.label} through extracted mention edges.`
6247
+ }
6248
+ ];
6249
+ });
6250
+ }
6251
+ function buildModuleFormHyperedges(graph) {
6252
+ const nodesById = new Map(graph.nodes.map((node) => [node.id, node]));
6253
+ const definedSymbols = /* @__PURE__ */ new Map();
6254
+ for (const edge of graph.edges) {
6255
+ if (edge.relation !== "defines" || edge.evidenceClass !== "extracted") {
6256
+ continue;
6257
+ }
6258
+ const moduleNode = nodesById.get(edge.source);
6259
+ const symbolNode = nodesById.get(edge.target);
6260
+ if (moduleNode?.type !== "module" || symbolNode?.type !== "symbol") {
6261
+ continue;
6262
+ }
6263
+ if (!definedSymbols.has(moduleNode.id)) {
6264
+ definedSymbols.set(moduleNode.id, []);
6265
+ }
6266
+ definedSymbols.get(moduleNode.id)?.push(symbolNode.id);
6267
+ }
6268
+ return [...definedSymbols.entries()].flatMap(([moduleId, members]) => {
6269
+ const moduleNode = nodesById.get(moduleId);
6270
+ const uniqueMembers = uniqueBy(members, (member) => member).sort((left, right) => left.localeCompare(right));
6271
+ if (!moduleNode || uniqueMembers.length < 3) {
6272
+ return [];
6273
+ }
6274
+ const nodeIds = [moduleNode.id, ...uniqueMembers];
6275
+ const sourcePageIds = uniqueBy(nodeIds.map((nodeId) => nodesById.get(nodeId)?.pageId ?? "").filter(Boolean), (value) => value);
6276
+ return [
6277
+ {
6278
+ id: `hyper:${sha256(`form|${moduleNode.id}|${uniqueMembers.join("|")}`).slice(0, 16)}`,
6279
+ label: `${moduleNode.label} API`,
6280
+ relation: "form",
6281
+ nodeIds,
6282
+ evidenceClass: "extracted",
6283
+ confidence: Math.min(0.98, 0.78 + uniqueMembers.length * 0.04),
6284
+ sourcePageIds,
6285
+ why: `${moduleNode.label} and ${uniqueMembers.length} defined symbols form one local module surface.`
6286
+ }
6287
+ ];
6288
+ });
6289
+ }
6290
+ function enrichGraph(graph, manifests, analyses) {
6291
+ const similarityEdges = buildSemanticSimilarityEdges(graph.nodes, graph.edges, manifests, analyses);
6292
+ const enrichedEdges = [...graph.edges, ...similarityEdges].sort((left, right) => left.id.localeCompare(right.id));
6293
+ const hyperedges = uniqueBy(
6294
+ [
6295
+ ...buildTopicHyperedges({ ...graph, edges: enrichedEdges, hyperedges: [] }),
6296
+ ...buildModuleFormHyperedges({ ...graph, edges: enrichedEdges, hyperedges: [] })
6297
+ ].sort((left, right) => right.confidence - left.confidence || left.label.localeCompare(right.label)),
6298
+ (hyperedge) => hyperedge.id
6299
+ );
6300
+ return {
6301
+ edges: enrichedEdges,
6302
+ hyperedges
6303
+ };
6304
+ }
6305
+
5913
6306
  // src/graph-tools.ts
5914
6307
  function normalizeTarget(value) {
5915
6308
  return normalizeWhitespace(value).toLowerCase();
@@ -5920,6 +6313,9 @@ function nodeById(graph) {
5920
6313
  function pageById(graph) {
5921
6314
  return new Map(graph.pages.map((page) => [page.id, page]));
5922
6315
  }
6316
+ function hyperedgesForNode(graph, nodeId) {
6317
+ return (graph.hyperedges ?? []).filter((hyperedge) => hyperedge.nodeIds.includes(nodeId)).sort((left, right) => right.confidence - left.confidence || left.label.localeCompare(right.label));
6318
+ }
5923
6319
  function scoreMatch(query, candidate) {
5924
6320
  const normalizedQuery = normalizeTarget(query);
5925
6321
  const normalizedCandidate = normalizeTarget(candidate);
@@ -5968,6 +6364,14 @@ function nodeMatches(graph, query) {
5968
6364
  score: Math.max(scoreMatch(query, node.label), scoreMatch(query, node.id))
5969
6365
  })).filter((match) => match.score > 0).sort((left, right) => right.score - left.score || left.label.localeCompare(right.label));
5970
6366
  }
6367
+ function hyperedgeMatches(graph, query) {
6368
+ return (graph.hyperedges ?? []).map((hyperedge) => ({
6369
+ type: "hyperedge",
6370
+ id: hyperedge.id,
6371
+ label: hyperedge.label,
6372
+ score: Math.max(scoreMatch(query, hyperedge.label), scoreMatch(query, hyperedge.why), scoreMatch(query, hyperedge.relation))
6373
+ })).filter((match) => match.score > 0).sort((left, right) => right.score - left.score || left.label.localeCompare(right.label));
6374
+ }
5971
6375
  function graphAdjacency(graph) {
5972
6376
  const adjacency = /* @__PURE__ */ new Map();
5973
6377
  const push = (nodeId, item) => {
@@ -6016,14 +6420,15 @@ function queryGraph(graph, question, searchResults, options) {
6016
6420
  const traversal = options?.traversal ?? "bfs";
6017
6421
  const budget = Math.max(3, Math.min(options?.budget ?? 12, 50));
6018
6422
  const matches = uniqueBy(
6019
- [...pageSearchMatches(graph, question, searchResults), ...nodeMatches(graph, question)],
6423
+ [...pageSearchMatches(graph, question, searchResults), ...nodeMatches(graph, question), ...hyperedgeMatches(graph, question)],
6020
6424
  (match) => `${match.type}:${match.id}`
6021
6425
  ).sort((left, right) => right.score - left.score || left.label.localeCompare(right.label)).slice(0, 12);
6022
6426
  const pages = pageById(graph);
6023
6427
  const seeds = uniqueBy(
6024
6428
  [
6025
6429
  ...searchResults.flatMap((result) => pages.get(result.pageId)?.nodeIds ?? []),
6026
- ...matches.filter((match) => match.type === "node").map((match) => match.id)
6430
+ ...matches.filter((match) => match.type === "node").map((match) => match.id),
6431
+ ...matches.filter((match) => match.type === "hyperedge").flatMap((match) => graph.hyperedges.find((hyperedge) => hyperedge.id === match.id)?.nodeIds ?? [])
6027
6432
  ],
6028
6433
  (item) => item
6029
6434
  ).filter(Boolean);
@@ -6064,6 +6469,10 @@ function queryGraph(graph, question, searchResults, options) {
6064
6469
  visitedNodeIds.map((nodeId) => nodes.get(nodeId)?.communityId).filter((communityId) => Boolean(communityId)),
6065
6470
  (item) => item
6066
6471
  );
6472
+ const hyperedgeIds = uniqueBy(
6473
+ (graph.hyperedges ?? []).filter((hyperedge) => hyperedge.nodeIds.some((nodeId) => visitedNodeIds.includes(nodeId))).map((hyperedge) => hyperedge.id),
6474
+ (item) => item
6475
+ );
6067
6476
  return {
6068
6477
  question,
6069
6478
  traversal,
@@ -6074,6 +6483,7 @@ function queryGraph(graph, question, searchResults, options) {
6074
6483
  ),
6075
6484
  visitedNodeIds,
6076
6485
  visitedEdgeIds: [...visitedEdgeIds],
6486
+ hyperedgeIds,
6077
6487
  pageIds,
6078
6488
  communities,
6079
6489
  matches,
@@ -6081,6 +6491,7 @@ function queryGraph(graph, question, searchResults, options) {
6081
6491
  `Seeds: ${seeds.join(", ") || "none"}`,
6082
6492
  `Visited nodes: ${visitedNodeIds.length}`,
6083
6493
  `Visited edges: ${visitedEdgeIds.size}`,
6494
+ `Touched group patterns: ${hyperedgeIds.length}`,
6084
6495
  `Communities: ${communities.join(", ") || "none"}`,
6085
6496
  `Pages: ${pageIds.join(", ") || "none"}`
6086
6497
  ].join("\n")
@@ -6200,11 +6611,13 @@ function explainGraphTarget(graph, target) {
6200
6611
  page,
6201
6612
  community: communityLabel(graph, node.communityId),
6202
6613
  neighbors,
6614
+ hyperedges: hyperedgesForNode(graph, node.id),
6203
6615
  summary: [
6204
6616
  `Node: ${node.label}`,
6205
6617
  `Type: ${node.type}`,
6206
6618
  `Community: ${node.communityId ?? "none"}`,
6207
6619
  `Neighbors: ${neighbors.length}`,
6620
+ `Group patterns: ${hyperedgesForNode(graph, node.id).length}`,
6208
6621
  `Page: ${page?.path ?? "none"}`
6209
6622
  ].join("\n")
6210
6623
  };
@@ -6212,6 +6625,20 @@ function explainGraphTarget(graph, target) {
6212
6625
  function topGodNodes(graph, limit = 10) {
6213
6626
  return graph.nodes.filter((node) => node.isGodNode).sort((left, right) => (right.degree ?? 0) - (left.degree ?? 0)).slice(0, limit);
6214
6627
  }
6628
+ function listHyperedges(graph, target, limit = 25) {
6629
+ if (!target) {
6630
+ return [...graph.hyperedges ?? []].sort((left, right) => right.confidence - left.confidence || left.label.localeCompare(right.label)).slice(0, limit);
6631
+ }
6632
+ const node = resolveNode(graph, target);
6633
+ if (node) {
6634
+ return hyperedgesForNode(graph, node.id).slice(0, limit);
6635
+ }
6636
+ const page = graph.pages.find((candidate) => normalizeTarget(candidate.path) === normalizeTarget(target) || candidate.id === target);
6637
+ if (!page) {
6638
+ return [];
6639
+ }
6640
+ return (graph.hyperedges ?? []).filter((hyperedge) => hyperedge.sourcePageIds.includes(page.id) || page.nodeIds.some((nodeId) => hyperedge.nodeIds.includes(nodeId))).sort((left, right) => right.confidence - left.confidence || left.label.localeCompare(right.label)).slice(0, limit);
6641
+ }
6215
6642
 
6216
6643
  // src/markdown.ts
6217
6644
  import matter5 from "gray-matter";
@@ -6712,13 +7139,120 @@ function nodeSummary(node) {
6712
7139
  const bridge = typeof node.bridgeScore === "number" ? `bridge=${node.bridgeScore}` : "";
6713
7140
  return [node.type, degree, bridge].filter(Boolean).join(", ");
6714
7141
  }
6715
- function crossCommunityEdges(graph) {
7142
+ function sourceTypeForNode(node, pagesById) {
7143
+ if (!node?.pageId) {
7144
+ return void 0;
7145
+ }
7146
+ return pagesById.get(node.pageId)?.sourceType;
7147
+ }
7148
+ function supportingPathDetails(graph, edge) {
7149
+ const path23 = shortestGraphPath(graph, edge.source, edge.target);
7150
+ const edgesById = new Map(graph.edges.map((item) => [item.id, item]));
7151
+ const pathEdges = path23.edgeIds.map((edgeId) => edgesById.get(edgeId)).filter((item) => Boolean(item));
7152
+ return {
7153
+ pathNodeIds: path23.nodeIds,
7154
+ pathEdgeIds: path23.edgeIds,
7155
+ pathRelations: pathEdges.map((item) => item.relation),
7156
+ pathEvidenceClasses: pathEdges.map((item) => item.evidenceClass),
7157
+ pathSummary: path23.summary
7158
+ };
7159
+ }
7160
+ function surpriseScore(edge, graph, pagesById, hyperedgesByNodeId) {
6716
7161
  const nodesById = new Map(graph.nodes.map((node) => [node.id, node]));
6717
- return graph.edges.filter((edge) => {
6718
- const source = nodesById.get(edge.source);
6719
- const target = nodesById.get(edge.target);
6720
- return source?.communityId && target?.communityId && source.communityId !== target.communityId;
6721
- }).sort((left, right) => right.confidence - left.confidence || left.relation.localeCompare(right.relation));
7162
+ const source = nodesById.get(edge.source);
7163
+ const target = nodesById.get(edge.target);
7164
+ const reasons = [];
7165
+ let score = edge.confidence * 0.45;
7166
+ if (source?.communityId && target?.communityId && source.communityId !== target.communityId) {
7167
+ score += 0.18;
7168
+ reasons.push(`it crosses communities ${source.communityId} and ${target.communityId}`);
7169
+ }
7170
+ if (source?.pageId && target?.pageId && source.pageId !== target.pageId) {
7171
+ score += 0.12;
7172
+ reasons.push("it spans different canonical pages");
7173
+ }
7174
+ if (source?.type && target?.type && source.type !== target.type) {
7175
+ score += 0.08;
7176
+ reasons.push(`it bridges ${source.type} and ${target.type} nodes`);
7177
+ }
7178
+ const sourceType = sourceTypeForNode(source, pagesById);
7179
+ const targetType = sourceTypeForNode(target, pagesById);
7180
+ if (sourceType && targetType && sourceType !== targetType) {
7181
+ score += 0.07;
7182
+ reasons.push(`it crosses source types (${sourceType} and ${targetType})`);
7183
+ }
7184
+ if ((source?.bridgeScore ?? 0) > 0 || (target?.bridgeScore ?? 0) > 0) {
7185
+ score += 0.08;
7186
+ reasons.push("a bridge node is involved");
7187
+ }
7188
+ if (edge.relation === "semantically_similar_to") {
7189
+ score += 0.12;
7190
+ reasons.push(describeSimilarityReasons(edge.similarityReasons));
7191
+ }
7192
+ if (edge.evidenceClass === "ambiguous") {
7193
+ score += 0.08;
7194
+ reasons.push("the supporting evidence is ambiguous");
7195
+ }
7196
+ const overlappingHyperedges = (hyperedgesByNodeId.get(edge.source) ?? []).filter((hyperedge) => hyperedge.nodeIds.includes(edge.target));
7197
+ if (overlappingHyperedges.length) {
7198
+ score += 0.06;
7199
+ reasons.push(`it also appears in ${overlappingHyperedges.length} group pattern${overlappingHyperedges.length === 1 ? "" : "s"}`);
7200
+ }
7201
+ const why = normalizeWhitespace(reasons.join("; ")) || "it links graph regions that are otherwise weakly connected";
7202
+ const explanation = normalizeWhitespace(`${source?.label ?? edge.source} connects to ${target?.label ?? edge.target} because ${why}.`);
7203
+ return { score: Math.min(0.99, score), why, explanation };
7204
+ }
7205
+ function topSurprisingConnections(graph, pagesById) {
7206
+ const nodesById = new Map(graph.nodes.map((node) => [node.id, node]));
7207
+ const hyperedgesByNodeId = /* @__PURE__ */ new Map();
7208
+ for (const hyperedge of graph.hyperedges ?? []) {
7209
+ for (const nodeId of hyperedge.nodeIds) {
7210
+ if (!hyperedgesByNodeId.has(nodeId)) {
7211
+ hyperedgesByNodeId.set(nodeId, []);
7212
+ }
7213
+ hyperedgesByNodeId.get(nodeId)?.push(hyperedge);
7214
+ }
7215
+ }
7216
+ return uniqueBy(
7217
+ graph.edges.filter((edge) => {
7218
+ const source = nodesById.get(edge.source);
7219
+ const target = nodesById.get(edge.target);
7220
+ return Boolean(
7221
+ source?.communityId && target?.communityId && source.communityId !== target.communityId || edge.relation === "semantically_similar_to" || edge.evidenceClass === "ambiguous" || source?.type && target?.type && source.type !== target.type
7222
+ );
7223
+ }).map((edge) => {
7224
+ const source = nodesById.get(edge.source);
7225
+ const target = nodesById.get(edge.target);
7226
+ const path23 = supportingPathDetails(graph, edge);
7227
+ const scored = surpriseScore(edge, graph, pagesById, hyperedgesByNodeId);
7228
+ return {
7229
+ id: edge.id,
7230
+ sourceNodeId: edge.source,
7231
+ sourceLabel: source?.label ?? edge.source,
7232
+ targetNodeId: edge.target,
7233
+ targetLabel: target?.label ?? edge.target,
7234
+ relation: edge.relation,
7235
+ evidenceClass: edge.evidenceClass,
7236
+ confidence: edge.confidence,
7237
+ pathNodeIds: path23.pathNodeIds,
7238
+ pathEdgeIds: path23.pathEdgeIds,
7239
+ pathRelations: path23.pathRelations,
7240
+ pathEvidenceClasses: path23.pathEvidenceClasses,
7241
+ pathSummary: path23.pathSummary,
7242
+ why: scored.why,
7243
+ explanation: scored.explanation,
7244
+ surpriseScore: scored.score
7245
+ };
7246
+ }).sort(
7247
+ (left, right) => right.surpriseScore - left.surpriseScore || right.confidence - left.confidence || left.id.localeCompare(right.id)
7248
+ ).slice(0, 8),
7249
+ (connection) => connection.id
7250
+ ).map(({ surpriseScore: _surpriseScore, ...connection }) => connection);
7251
+ }
7252
+ function topGroupPatterns(graph) {
7253
+ return [...graph.hyperedges ?? []].sort(
7254
+ (left, right) => right.confidence - left.confidence || right.nodeIds.length - left.nodeIds.length || left.label.localeCompare(right.label)
7255
+ ).slice(0, 8);
6722
7256
  }
6723
7257
  function suggestedGraphQuestions(graph) {
6724
7258
  const thinCommunities = (graph.communities ?? []).filter((community) => community.nodeIds.length <= 2);
@@ -6729,7 +7263,7 @@ function suggestedGraphQuestions(graph) {
6729
7263
  ]).slice(0, 6);
6730
7264
  }
6731
7265
  function buildGraphReportArtifact(input) {
6732
- const nodesById = new Map(input.graph.nodes.map((node) => [node.id, node]));
7266
+ const pagesById = new Map(input.graph.pages.map((page) => [page.id, page]));
6733
7267
  const godNodes = input.graph.nodes.filter((node) => node.isGodNode).sort((left, right) => (right.degree ?? 0) - (left.degree ?? 0)).slice(0, 8);
6734
7268
  const bridgeNodes = input.graph.nodes.filter((node) => (node.bridgeScore ?? 0) > 0).sort((left, right) => (right.bridgeScore ?? 0) - (left.bridgeScore ?? 0)).slice(0, 8);
6735
7269
  const thinCommunities = (input.graph.communities ?? []).filter((community) => community.nodeIds.length <= 2).map((community) => {
@@ -6743,33 +7277,8 @@ function buildGraphReportArtifact(input) {
6743
7277
  title: page?.title
6744
7278
  };
6745
7279
  });
6746
- const surprisingConnections = crossCommunityEdges(input.graph).slice(0, 8).map((edge) => {
6747
- const source = nodesById.get(edge.source);
6748
- const target = nodesById.get(edge.target);
6749
- const path23 = shortestGraphPath(input.graph, edge.source, edge.target);
6750
- const sourceCommunity = source?.communityId ? input.graph.communities?.find((community) => community.id === source.communityId) : void 0;
6751
- const targetCommunity = target?.communityId ? input.graph.communities?.find((community) => community.id === target.communityId) : void 0;
6752
- return {
6753
- id: edge.id,
6754
- sourceNodeId: edge.source,
6755
- sourceLabel: source?.label ?? edge.source,
6756
- targetNodeId: edge.target,
6757
- targetLabel: target?.label ?? edge.target,
6758
- relation: edge.relation,
6759
- evidenceClass: edge.evidenceClass,
6760
- confidence: edge.confidence,
6761
- pathNodeIds: path23.nodeIds,
6762
- pathEdgeIds: path23.edgeIds,
6763
- pathSummary: path23.summary,
6764
- explanation: normalizeWhitespace(
6765
- [
6766
- `${source?.label ?? edge.source} links ${sourceCommunity?.label ? `from ${sourceCommunity.label}` : ""}`.trim(),
6767
- `to ${target?.label ?? edge.target}${targetCommunity?.label ? ` in ${targetCommunity.label}` : ""}`.trim(),
6768
- `through ${edge.relation} with ${edge.evidenceClass} evidence at ${edge.confidence.toFixed(2)} confidence.`
6769
- ].join(" ")
6770
- )
6771
- };
6772
- });
7280
+ const surprisingConnections = topSurprisingConnections(input.graph, pagesById);
7281
+ const groupPatterns = topGroupPatterns(input.graph);
6773
7282
  return {
6774
7283
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
6775
7284
  graphHash: input.graphHash,
@@ -6801,6 +7310,7 @@ function buildGraphReportArtifact(input) {
6801
7310
  })),
6802
7311
  thinCommunities,
6803
7312
  surprisingConnections,
7313
+ groupPatterns,
6804
7314
  suggestedQuestions: suggestedGraphQuestions(input.graph),
6805
7315
  communityPages: input.communityPages.map((page) => ({
6806
7316
  id: page.id,
@@ -6823,13 +7333,20 @@ function buildGraphReportPage(input) {
6823
7333
  const nodesById = new Map(input.graph.nodes.map((node) => [node.id, node]));
6824
7334
  const relatedNodeIds = uniqueStrings2([
6825
7335
  ...input.report.godNodes.map((node) => node.nodeId),
6826
- ...input.report.bridgeNodes.map((node) => node.nodeId)
7336
+ ...input.report.bridgeNodes.map((node) => node.nodeId),
7337
+ ...input.report.surprisingConnections.flatMap((connection) => [
7338
+ connection.sourceNodeId,
7339
+ connection.targetNodeId,
7340
+ ...connection.pathNodeIds
7341
+ ]),
7342
+ ...input.report.groupPatterns.flatMap((hyperedge) => hyperedge.nodeIds)
6827
7343
  ]);
6828
7344
  const relatedPageIds = uniqueStrings2([
6829
7345
  ...input.report.godNodes.map((node) => node.pageId ?? ""),
6830
7346
  ...input.report.bridgeNodes.map((node) => node.pageId ?? ""),
6831
7347
  ...input.report.communityPages.map((page) => page.id),
6832
- ...input.report.recentResearchSources.map((page) => page.pageId)
7348
+ ...input.report.recentResearchSources.map((page) => page.pageId),
7349
+ ...input.report.groupPatterns.flatMap((hyperedge) => hyperedge.sourcePageIds)
6833
7350
  ]);
6834
7351
  const relatedSourceIds = uniqueStrings2([
6835
7352
  ...relatedNodeIds.flatMap((nodeId) => nodesById.get(nodeId)?.sourceIds ?? []),
@@ -6910,9 +7427,16 @@ function buildGraphReportPage(input) {
6910
7427
  const target = nodesById.get(connection.targetNodeId);
6911
7428
  const sourceLabel = source ? graphNodeLink(source, pagesById) : `\`${connection.sourceNodeId}\``;
6912
7429
  const targetLabel = target ? graphNodeLink(target, pagesById) : `\`${connection.targetNodeId}\``;
6913
- return `- ${sourceLabel} ${connection.relation} ${targetLabel} (${connection.evidenceClass}, ${connection.confidence.toFixed(2)}). ${connection.explanation} Path: ${connection.pathSummary}.`;
7430
+ return `- ${sourceLabel} ${connection.relation} ${targetLabel} (${connection.evidenceClass}, ${connection.confidence.toFixed(2)}). Why: ${connection.why}. ${connection.explanation} Path: ${connection.pathSummary}.`;
6914
7431
  }) : ["- No cross-community links detected."],
6915
7432
  "",
7433
+ "## Group Patterns",
7434
+ "",
7435
+ ...input.report.groupPatterns.length ? input.report.groupPatterns.map((hyperedge) => {
7436
+ const linkedNodes = hyperedge.nodeIds.map((nodeId) => nodesById.get(nodeId)).filter((node) => Boolean(node)).map((node) => graphNodeLink(node, pagesById)).join(", ");
7437
+ return `- ${hyperedge.label} (${hyperedge.relation}, ${hyperedge.evidenceClass}, ${hyperedge.confidence.toFixed(2)}). ${hyperedge.why} Members: ${linkedNodes}.`;
7438
+ }) : ["- No multi-node group patterns detected."],
7439
+ "",
6916
7440
  "## New Research Sources",
6917
7441
  "",
6918
7442
  ...input.report.recentResearchSources.length ? input.report.recentResearchSources.map(
@@ -9045,11 +9569,24 @@ function buildGraph(manifests, analyses, pages, sourceProjects, _codeIndex) {
9045
9569
  ...conceptMap.values(),
9046
9570
  ...entityMap.values()
9047
9571
  ];
9048
- const metrics = deriveGraphMetrics(graphNodes, edges);
9572
+ const enriched = enrichGraph(
9573
+ {
9574
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
9575
+ nodes: graphNodes,
9576
+ edges,
9577
+ communities: [],
9578
+ sources: manifests,
9579
+ pages
9580
+ },
9581
+ manifests,
9582
+ analyses
9583
+ );
9584
+ const metrics = deriveGraphMetrics(graphNodes, enriched.edges);
9049
9585
  return {
9050
9586
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
9051
9587
  nodes: metrics.nodes,
9052
- edges,
9588
+ edges: enriched.edges,
9589
+ hyperedges: enriched.hyperedges,
9053
9590
  communities: metrics.communities,
9054
9591
  sources: manifests,
9055
9592
  pages
@@ -9937,6 +10474,7 @@ async function stageOutputApprovalBundle(rootDir, stagedPages) {
9937
10474
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
9938
10475
  nodes: previousGraph?.nodes ?? [],
9939
10476
  edges: previousGraph?.edges ?? [],
10477
+ hyperedges: previousGraph?.hyperedges ?? [],
9940
10478
  sources: previousGraph?.sources ?? [],
9941
10479
  pages: nextPages
9942
10480
  };
@@ -10245,6 +10783,7 @@ async function acceptApproval(rootDir, approvalId, targets = []) {
10245
10783
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
10246
10784
  nodes: currentGraph?.nodes ?? bundleGraph?.nodes ?? [],
10247
10785
  edges: currentGraph?.edges ?? bundleGraph?.edges ?? [],
10786
+ hyperedges: currentGraph?.hyperedges ?? bundleGraph?.hyperedges ?? [],
10248
10787
  sources: currentGraph?.sources ?? bundleGraph?.sources ?? [],
10249
10788
  pages: sortGraphPages(nextPages)
10250
10789
  };
@@ -10345,6 +10884,7 @@ async function promoteCandidate(rootDir, target) {
10345
10884
  generatedAt: nextUpdatedAt,
10346
10885
  nodes: graph?.nodes ?? [],
10347
10886
  edges: graph?.edges ?? [],
10887
+ hyperedges: graph?.hyperedges ?? [],
10348
10888
  sources: graph?.sources ?? [],
10349
10889
  pages: nextPages
10350
10890
  };
@@ -10386,6 +10926,7 @@ async function archiveCandidate(rootDir, target) {
10386
10926
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
10387
10927
  nodes: graph?.nodes ?? [],
10388
10928
  edges: graph?.edges ?? [],
10929
+ hyperedges: graph?.hyperedges ?? [],
10389
10930
  sources: graph?.sources ?? [],
10390
10931
  pages: nextPages
10391
10932
  };
@@ -11226,6 +11767,14 @@ async function explainGraphVault(rootDir, target) {
11226
11767
  const graph = await ensureCompiledGraph(rootDir);
11227
11768
  return explainGraphTarget(graph, target);
11228
11769
  }
11770
+ async function listGraphHyperedges(rootDir, target, limit = 25) {
11771
+ const graph = await ensureCompiledGraph(rootDir);
11772
+ return listHyperedges(graph, target, limit);
11773
+ }
11774
+ async function readGraphReport(rootDir) {
11775
+ const { paths } = await loadVaultConfig(rootDir);
11776
+ return readJsonFile(path18.join(paths.wikiDir, "graph", "report.json"));
11777
+ }
11229
11778
  async function listGodNodes(rootDir, limit = 10) {
11230
11779
  const graph = await ensureCompiledGraph(rootDir);
11231
11780
  return topGodNodes(graph, limit);
@@ -11389,7 +11938,7 @@ async function bootstrapDemo(rootDir, input) {
11389
11938
  }
11390
11939
 
11391
11940
  // src/mcp.ts
11392
- var SERVER_VERSION = "0.1.22";
11941
+ var SERVER_VERSION = "0.1.24";
11393
11942
  async function createMcpServer(rootDir) {
11394
11943
  const server = new McpServer({
11395
11944
  name: "swarmvault",
@@ -11467,10 +12016,19 @@ async function createMcpServer(rootDir) {
11467
12016
  return asToolText(result);
11468
12017
  }
11469
12018
  );
12019
+ server.registerTool(
12020
+ "graph_report",
12021
+ {
12022
+ description: "Return the machine-readable graph report and trust artifact."
12023
+ },
12024
+ async () => {
12025
+ return asToolText(await readGraphReport(rootDir) ?? { error: "Graph report not found. Run `swarmvault compile` first." });
12026
+ }
12027
+ );
11470
12028
  server.registerTool(
11471
12029
  "get_node",
11472
12030
  {
11473
- description: "Explain a graph node, its page, community, and neighbors.",
12031
+ description: "Explain a graph node, its page, community, neighbors, and group patterns.",
11474
12032
  inputSchema: {
11475
12033
  target: z8.string().min(1).describe("Node or page label/id")
11476
12034
  }
@@ -11479,6 +12037,19 @@ async function createMcpServer(rootDir) {
11479
12037
  return asToolText(await explainGraphVault(rootDir, target));
11480
12038
  }
11481
12039
  );
12040
+ server.registerTool(
12041
+ "get_hyperedges",
12042
+ {
12043
+ description: "List graph hyperedges, optionally filtered to a node or page target.",
12044
+ inputSchema: {
12045
+ target: z8.string().optional().describe("Optional node/page label or id to filter by"),
12046
+ limit: z8.number().int().min(1).max(50).optional().describe("Maximum hyperedges to return")
12047
+ }
12048
+ },
12049
+ async ({ target, limit }) => {
12050
+ return asToolText(await listGraphHyperedges(rootDir, target, limit ?? 25));
12051
+ }
12052
+ );
11482
12053
  server.registerTool(
11483
12054
  "get_neighbors",
11484
12055
  {
@@ -12755,6 +13326,7 @@ export {
12755
13326
  listApprovals,
12756
13327
  listCandidates,
12757
13328
  listGodNodes,
13329
+ listGraphHyperedges,
12758
13330
  listManifests,
12759
13331
  listPages,
12760
13332
  listSchedules,
@@ -12768,6 +13340,7 @@ export {
12768
13340
  queryVault,
12769
13341
  readApproval,
12770
13342
  readExtractedText,
13343
+ readGraphReport,
12771
13344
  readPage,
12772
13345
  rejectApproval,
12773
13346
  resolvePaths,