@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/README.md +6 -5
- package/dist/index.d.ts +22 -2
- package/dist/index.js +617 -44
- package/dist/viewer/assets/{index-f8JPYMw_.js → index-CmEm2Pd_.js} +11 -11
- package/dist/viewer/index.html +1 -1
- package/dist/viewer/lib.d.ts +20 -2
- package/package.json +7 -7
- package/LICENSE +0 -21
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
|
|
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
|
-
|
|
6718
|
-
|
|
6719
|
-
|
|
6720
|
-
|
|
6721
|
-
|
|
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
|
|
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 =
|
|
6747
|
-
|
|
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
|
|
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.
|
|
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
|
|
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,
|