@dreamtree-org/graphify 1.3.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-K322XB4U.js → chunk-7LTO76UD.js} +2 -1
- package/dist/chunk-7LTO76UD.js.map +1 -0
- package/dist/{chunk-UBXYMMXJ.js → chunk-EHMBINRV.js} +115 -102
- package/dist/chunk-EHMBINRV.js.map +1 -0
- package/dist/cli/index.cjs +19 -7
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.js +2 -2
- package/dist/index.cjs +564 -108
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +229 -17
- package/dist/index.d.ts +229 -17
- package/dist/index.js +442 -2
- package/dist/index.js.map +1 -1
- package/dist/mcp/server.cjs.map +1 -1
- package/dist/mcp/server.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-K322XB4U.js.map +0 -1
- package/dist/chunk-UBXYMMXJ.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -44,15 +44,18 @@ __export(index_exports, {
|
|
|
44
44
|
collectFiles: () => collectFiles,
|
|
45
45
|
deserializeGraph: () => deserializeGraph,
|
|
46
46
|
diffGraphs: () => diffGraphs,
|
|
47
|
+
expandRetrieval: () => expandRetrieval,
|
|
47
48
|
explainNode: () => explainNode,
|
|
48
49
|
exportGraph: () => exportGraph,
|
|
49
50
|
extract: () => extract,
|
|
50
51
|
extractMysql: () => extractMysql,
|
|
51
52
|
globToRegExp: () => globToRegExp,
|
|
52
53
|
graphForDirectory: () => graphForDirectory,
|
|
54
|
+
ingestDocument: () => ingestDocument,
|
|
53
55
|
isTestFile: () => isTestFile,
|
|
54
56
|
loadGraph: () => loadGraph,
|
|
55
57
|
loadResults: () => loadResults,
|
|
58
|
+
mergeExtractionsInto: () => mergeExtractionsInto,
|
|
56
59
|
mergeGraphs: () => mergeGraphs,
|
|
57
60
|
queryGraph: () => queryGraph,
|
|
58
61
|
rankForTask: () => rankForTask,
|
|
@@ -70,6 +73,7 @@ __export(index_exports, {
|
|
|
70
73
|
shortestPath: () => shortestPath,
|
|
71
74
|
testsForChangedFiles: () => testsForChangedFiles,
|
|
72
75
|
testsForNode: () => testsForNode,
|
|
76
|
+
updateCorpusGraph: () => updateCorpusGraph,
|
|
73
77
|
validateExtraction: () => validateExtraction,
|
|
74
78
|
validateRules: () => validateRules
|
|
75
79
|
});
|
|
@@ -2028,13 +2032,15 @@ var RelationSchema = import_zod.z.enum([
|
|
|
2028
2032
|
"references",
|
|
2029
2033
|
"contains",
|
|
2030
2034
|
"method",
|
|
2031
|
-
"re_exports"
|
|
2035
|
+
"re_exports",
|
|
2036
|
+
"follows"
|
|
2032
2037
|
]);
|
|
2033
2038
|
var GraphNodeSchema = import_zod.z.object({
|
|
2034
2039
|
id: import_zod.z.string().min(1, "node id must be non-empty"),
|
|
2035
2040
|
label: import_zod.z.string(),
|
|
2036
2041
|
sourceFile: import_zod.z.string(),
|
|
2037
|
-
sourceLocation: import_zod.z.string()
|
|
2042
|
+
sourceLocation: import_zod.z.string(),
|
|
2043
|
+
kind: import_zod.z.string().optional()
|
|
2038
2044
|
});
|
|
2039
2045
|
var GraphEdgeSchema = import_zod.z.object({
|
|
2040
2046
|
source: import_zod.z.string().min(1, "edge source must be non-empty"),
|
|
@@ -2082,16 +2088,27 @@ function buildGraph(extractions) {
|
|
|
2082
2088
|
const validated = extractions.map(
|
|
2083
2089
|
(extraction, index) => validateExtraction(extraction, `extraction #${index}`)
|
|
2084
2090
|
);
|
|
2091
|
+
mergeExtractionsInto(graph, validated);
|
|
2092
|
+
return graph;
|
|
2093
|
+
}
|
|
2094
|
+
function mergeExtractionsInto(graph, validated) {
|
|
2085
2095
|
const allNodes = validated.flatMap((extraction) => extraction.nodes);
|
|
2086
2096
|
const allEdges = validated.flatMap((extraction) => extraction.edges);
|
|
2087
2097
|
const sortedNodes = [...allNodes].sort((a, b) => a.id.localeCompare(b.id));
|
|
2088
2098
|
for (const node of sortedNodes) {
|
|
2089
|
-
|
|
2090
|
-
graph.addNode(node.id, {
|
|
2099
|
+
const attributes = {
|
|
2091
2100
|
label: node.label,
|
|
2092
2101
|
sourceFile: node.sourceFile,
|
|
2093
|
-
sourceLocation: node.sourceLocation
|
|
2094
|
-
|
|
2102
|
+
sourceLocation: node.sourceLocation,
|
|
2103
|
+
...node.kind !== void 0 ? { kind: node.kind } : {}
|
|
2104
|
+
};
|
|
2105
|
+
if (graph.hasNode(node.id)) {
|
|
2106
|
+
if (graph.getNodeAttribute(node.id, "sourceFile") === "<unknown>") {
|
|
2107
|
+
graph.mergeNodeAttributes(node.id, attributes);
|
|
2108
|
+
}
|
|
2109
|
+
continue;
|
|
2110
|
+
}
|
|
2111
|
+
graph.addNode(node.id, attributes);
|
|
2095
2112
|
}
|
|
2096
2113
|
const sortedEdges = [...allEdges].sort(
|
|
2097
2114
|
(a, b) => a.source.localeCompare(b.source) || a.target.localeCompare(b.target) || a.relation.localeCompare(b.relation)
|
|
@@ -2110,9 +2127,402 @@ function buildGraph(extractions) {
|
|
|
2110
2127
|
confidence: edge.confidence
|
|
2111
2128
|
});
|
|
2112
2129
|
}
|
|
2130
|
+
}
|
|
2131
|
+
|
|
2132
|
+
// src/ingest/document.ts
|
|
2133
|
+
var import_node_crypto = require("crypto");
|
|
2134
|
+
var posix2 = __toESM(require("path/posix"), 1);
|
|
2135
|
+
var DEFAULT_TARGET_TOKENS = 400;
|
|
2136
|
+
var DEFAULT_MAX_TOKENS = 512;
|
|
2137
|
+
var CHUNK_LABEL_LEN = 60;
|
|
2138
|
+
function estimateTokens(text) {
|
|
2139
|
+
return Math.ceil(text.length / 4);
|
|
2140
|
+
}
|
|
2141
|
+
function contentHash(content) {
|
|
2142
|
+
const normalized = content.replace(/\r\n/g, "\n").trim();
|
|
2143
|
+
return (0, import_node_crypto.createHash)("sha256").update(normalized, "utf8").digest("hex");
|
|
2144
|
+
}
|
|
2145
|
+
function chunkLabel(content) {
|
|
2146
|
+
const collapsed = content.replace(/\s+/g, " ").trim();
|
|
2147
|
+
return collapsed.length > CHUNK_LABEL_LEN ? `${collapsed.slice(0, CHUNK_LABEL_LEN - 1)}\u2026` : collapsed;
|
|
2148
|
+
}
|
|
2149
|
+
function parseBlocks(text, honorHeadings) {
|
|
2150
|
+
const lines = text.replace(/\r\n/g, "\n").split("\n");
|
|
2151
|
+
const blocks = [];
|
|
2152
|
+
let paragraph = [];
|
|
2153
|
+
let paragraphLine = 0;
|
|
2154
|
+
let inFence = false;
|
|
2155
|
+
const flush = () => {
|
|
2156
|
+
if (paragraph.length === 0) return;
|
|
2157
|
+
blocks.push({ type: "paragraph", text: paragraph.join("\n"), depth: 0, line: paragraphLine });
|
|
2158
|
+
paragraph = [];
|
|
2159
|
+
};
|
|
2160
|
+
for (let i = 0; i < lines.length; i++) {
|
|
2161
|
+
const line = lines[i];
|
|
2162
|
+
if (/^(```|~~~)/.test(line.trim())) {
|
|
2163
|
+
if (paragraph.length === 0) paragraphLine = i + 1;
|
|
2164
|
+
paragraph.push(line);
|
|
2165
|
+
inFence = !inFence;
|
|
2166
|
+
continue;
|
|
2167
|
+
}
|
|
2168
|
+
if (inFence) {
|
|
2169
|
+
paragraph.push(line);
|
|
2170
|
+
continue;
|
|
2171
|
+
}
|
|
2172
|
+
const heading = honorHeadings ? /^(#{1,6})\s+(.+?)\s*#*\s*$/.exec(line) : null;
|
|
2173
|
+
if (heading) {
|
|
2174
|
+
flush();
|
|
2175
|
+
blocks.push({
|
|
2176
|
+
type: "heading",
|
|
2177
|
+
text: heading[2].trim(),
|
|
2178
|
+
depth: heading[1].length,
|
|
2179
|
+
line: i + 1
|
|
2180
|
+
});
|
|
2181
|
+
continue;
|
|
2182
|
+
}
|
|
2183
|
+
if (line.trim() === "") {
|
|
2184
|
+
flush();
|
|
2185
|
+
continue;
|
|
2186
|
+
}
|
|
2187
|
+
if (paragraph.length === 0) paragraphLine = i + 1;
|
|
2188
|
+
paragraph.push(line);
|
|
2189
|
+
}
|
|
2190
|
+
flush();
|
|
2191
|
+
return blocks;
|
|
2192
|
+
}
|
|
2193
|
+
function splitLongParagraph(text, maxTokens) {
|
|
2194
|
+
const maxChars = maxTokens * 4;
|
|
2195
|
+
const pieces = [];
|
|
2196
|
+
let rest = text;
|
|
2197
|
+
while (rest.length > maxChars) {
|
|
2198
|
+
let cut = rest.lastIndexOf(" ", maxChars);
|
|
2199
|
+
const newlineCut = rest.lastIndexOf("\n", maxChars);
|
|
2200
|
+
cut = Math.max(cut, newlineCut);
|
|
2201
|
+
if (cut <= 0) cut = maxChars;
|
|
2202
|
+
pieces.push(rest.slice(0, cut));
|
|
2203
|
+
rest = rest.slice(cut).trimStart();
|
|
2204
|
+
}
|
|
2205
|
+
if (rest.length > 0) pieces.push(rest);
|
|
2206
|
+
return pieces;
|
|
2207
|
+
}
|
|
2208
|
+
function extractLinkTargets(content, sourceRef) {
|
|
2209
|
+
const targets = /* @__PURE__ */ new Set();
|
|
2210
|
+
const linkPattern = /\[[^\]]*\]\(([^)\s]+)(?:\s+"[^"]*")?\)/g;
|
|
2211
|
+
const dir = posix2.dirname(sourceRef);
|
|
2212
|
+
for (const match of content.matchAll(linkPattern)) {
|
|
2213
|
+
const raw = match[1].split("#")[0];
|
|
2214
|
+
if (raw === "" || /^[a-z][a-z0-9+.-]*:/i.test(raw)) continue;
|
|
2215
|
+
targets.add(posix2.normalize(posix2.join(dir === "." ? "" : dir, raw)));
|
|
2216
|
+
}
|
|
2217
|
+
return [...targets].sort((a, b) => a.localeCompare(b));
|
|
2218
|
+
}
|
|
2219
|
+
async function ingestDocument(input, options = {}) {
|
|
2220
|
+
if (input.sourceRef.trim() === "") throw new Error("ingestDocument: sourceRef must be non-empty");
|
|
2221
|
+
const targetTokens = options.chunking?.targetTokens ?? DEFAULT_TARGET_TOKENS;
|
|
2222
|
+
const maxTokens = Math.max(options.chunking?.maxTokens ?? DEFAULT_MAX_TOKENS, targetTokens);
|
|
2223
|
+
const overlapTokens = options.chunking?.overlapTokens ?? 0;
|
|
2224
|
+
const metadata = input.metadata ?? {};
|
|
2225
|
+
const sourceRef = input.sourceRef;
|
|
2226
|
+
const honorHeadings = !/^text\/plain\b/i.test(input.mimeType ?? "");
|
|
2227
|
+
const blocks = parseBlocks(input.text, honorHeadings);
|
|
2228
|
+
const nodes = [];
|
|
2229
|
+
const edges = [];
|
|
2230
|
+
const documentId = sourceRef;
|
|
2231
|
+
nodes.push({
|
|
2232
|
+
id: documentId,
|
|
2233
|
+
label: input.title ?? posix2.basename(sourceRef),
|
|
2234
|
+
sourceFile: sourceRef,
|
|
2235
|
+
sourceLocation: "L1",
|
|
2236
|
+
kind: "document"
|
|
2237
|
+
});
|
|
2238
|
+
const sectionStack = [];
|
|
2239
|
+
const sectionIdCounts = /* @__PURE__ */ new Map();
|
|
2240
|
+
let sectionCount = 0;
|
|
2241
|
+
const chunks = [];
|
|
2242
|
+
let buffer = [];
|
|
2243
|
+
let bufferLine = 0;
|
|
2244
|
+
let bufferSection = null;
|
|
2245
|
+
let previousChunkId = null;
|
|
2246
|
+
const flushChunk = () => {
|
|
2247
|
+
const content = buffer.join("\n\n");
|
|
2248
|
+
buffer = [];
|
|
2249
|
+
if (content.trim() === "") return;
|
|
2250
|
+
const index = chunks.length;
|
|
2251
|
+
const nodeId = `${sourceRef}::chunk:${String(index).padStart(4, "0")}`;
|
|
2252
|
+
chunks.push({
|
|
2253
|
+
nodeId,
|
|
2254
|
+
content,
|
|
2255
|
+
tokenEstimate: estimateTokens(content),
|
|
2256
|
+
index,
|
|
2257
|
+
sectionPath: bufferSection?.path ?? [],
|
|
2258
|
+
contentHash: contentHash(content),
|
|
2259
|
+
metadata: { ...metadata }
|
|
2260
|
+
});
|
|
2261
|
+
nodes.push({
|
|
2262
|
+
id: nodeId,
|
|
2263
|
+
label: chunkLabel(content),
|
|
2264
|
+
sourceFile: sourceRef,
|
|
2265
|
+
sourceLocation: `L${bufferLine}`,
|
|
2266
|
+
kind: "chunk"
|
|
2267
|
+
});
|
|
2268
|
+
edges.push({
|
|
2269
|
+
source: bufferSection?.id ?? documentId,
|
|
2270
|
+
target: nodeId,
|
|
2271
|
+
relation: "contains",
|
|
2272
|
+
confidence: "EXTRACTED"
|
|
2273
|
+
});
|
|
2274
|
+
if (previousChunkId !== null) {
|
|
2275
|
+
edges.push({ source: previousChunkId, target: nodeId, relation: "follows", confidence: "EXTRACTED" });
|
|
2276
|
+
}
|
|
2277
|
+
previousChunkId = nodeId;
|
|
2278
|
+
for (const target of extractLinkTargets(content, sourceRef)) {
|
|
2279
|
+
if (target === sourceRef) continue;
|
|
2280
|
+
edges.push({ source: nodeId, target, relation: "references", confidence: "INFERRED" });
|
|
2281
|
+
}
|
|
2282
|
+
if (overlapTokens > 0) {
|
|
2283
|
+
const tailChars = overlapTokens * 4;
|
|
2284
|
+
const tail = content.length > tailChars ? content.slice(content.indexOf(" ", content.length - tailChars) + 1) : "";
|
|
2285
|
+
if (tail.trim() !== "") {
|
|
2286
|
+
buffer.push(tail);
|
|
2287
|
+
}
|
|
2288
|
+
}
|
|
2289
|
+
};
|
|
2290
|
+
for (const block of blocks) {
|
|
2291
|
+
if (block.type === "heading") {
|
|
2292
|
+
flushChunk();
|
|
2293
|
+
buffer = [];
|
|
2294
|
+
while (sectionStack.length > 0 && sectionStack[sectionStack.length - 1].depth >= block.depth) {
|
|
2295
|
+
sectionStack.pop();
|
|
2296
|
+
}
|
|
2297
|
+
const parent = sectionStack[sectionStack.length - 1];
|
|
2298
|
+
const path12 = [...parent?.path ?? [], block.text];
|
|
2299
|
+
const baseId = `${sourceRef}::sec:${path12.join("/")}`;
|
|
2300
|
+
const seen = sectionIdCounts.get(baseId) ?? 0;
|
|
2301
|
+
sectionIdCounts.set(baseId, seen + 1);
|
|
2302
|
+
const id = seen === 0 ? baseId : `${baseId}#${seen + 1}`;
|
|
2303
|
+
nodes.push({
|
|
2304
|
+
id,
|
|
2305
|
+
label: block.text,
|
|
2306
|
+
sourceFile: sourceRef,
|
|
2307
|
+
sourceLocation: `L${block.line}`,
|
|
2308
|
+
kind: "section"
|
|
2309
|
+
});
|
|
2310
|
+
edges.push({ source: parent?.id ?? documentId, target: id, relation: "contains", confidence: "EXTRACTED" });
|
|
2311
|
+
sectionStack.push({ id, depth: block.depth, path: path12 });
|
|
2312
|
+
sectionCount += 1;
|
|
2313
|
+
bufferSection = sectionStack[sectionStack.length - 1];
|
|
2314
|
+
bufferLine = block.line + 1;
|
|
2315
|
+
continue;
|
|
2316
|
+
}
|
|
2317
|
+
const pieces = estimateTokens(block.text) > maxTokens ? splitLongParagraph(block.text, maxTokens) : [block.text];
|
|
2318
|
+
for (const piece of pieces) {
|
|
2319
|
+
const bufferTokens = estimateTokens(buffer.join("\n\n"));
|
|
2320
|
+
if (buffer.length > 0 && bufferTokens + estimateTokens(piece) > targetTokens) {
|
|
2321
|
+
flushChunk();
|
|
2322
|
+
}
|
|
2323
|
+
if (buffer.length === 0) bufferLine = block.line;
|
|
2324
|
+
buffer.push(piece);
|
|
2325
|
+
}
|
|
2326
|
+
}
|
|
2327
|
+
flushChunk();
|
|
2328
|
+
let entities = 0;
|
|
2329
|
+
if (options.semanticExtractor) {
|
|
2330
|
+
const semantic = validateExtraction(
|
|
2331
|
+
await options.semanticExtractor.extractSemantic(sourceRef, input.text),
|
|
2332
|
+
`semantic pass for ${sourceRef}`
|
|
2333
|
+
);
|
|
2334
|
+
entities = semantic.nodes.length;
|
|
2335
|
+
nodes.push(...semantic.nodes);
|
|
2336
|
+
edges.push(...semantic.edges);
|
|
2337
|
+
}
|
|
2338
|
+
const extraction = validateExtraction({ nodes, edges }, `ingestDocument(${sourceRef})`);
|
|
2339
|
+
return {
|
|
2340
|
+
chunks,
|
|
2341
|
+
extraction,
|
|
2342
|
+
stats: {
|
|
2343
|
+
sections: sectionCount,
|
|
2344
|
+
entities,
|
|
2345
|
+
tokenTotal: chunks.reduce((sum, chunk) => sum + chunk.tokenEstimate, 0)
|
|
2346
|
+
}
|
|
2347
|
+
};
|
|
2348
|
+
}
|
|
2349
|
+
|
|
2350
|
+
// src/ingest/corpus.ts
|
|
2351
|
+
var import_graphology3 = __toESM(require("graphology"), 1);
|
|
2352
|
+
|
|
2353
|
+
// src/cluster.ts
|
|
2354
|
+
var import_node_crypto2 = require("crypto");
|
|
2355
|
+
var import_graphology2 = __toESM(require("graphology"), 1);
|
|
2356
|
+
var import_graphology_communities_leiden = __toESM(require("@aflsolutions/graphology-communities-leiden"), 1);
|
|
2357
|
+
var import_graphology_communities_louvain = __toESM(require("graphology-communities-louvain"), 1);
|
|
2358
|
+
var FIXED_SEED = 1592594996;
|
|
2359
|
+
function mulberry32(seed) {
|
|
2360
|
+
let a = seed >>> 0;
|
|
2361
|
+
return function next() {
|
|
2362
|
+
a = a + 1831565813 | 0;
|
|
2363
|
+
let t = Math.imul(a ^ a >>> 15, 1 | a);
|
|
2364
|
+
t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
|
|
2365
|
+
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
2366
|
+
};
|
|
2367
|
+
}
|
|
2368
|
+
function sortedWorkingCopy(graph) {
|
|
2369
|
+
const copy = new import_graphology2.default({ type: graph.type, multi: graph.multi, allowSelfLoops: graph.allowSelfLoops });
|
|
2370
|
+
const nodeIds = graph.nodes().sort((a, b) => a.localeCompare(b));
|
|
2371
|
+
for (const id of nodeIds) {
|
|
2372
|
+
copy.addNode(id, { ...graph.getNodeAttributes(id) });
|
|
2373
|
+
}
|
|
2374
|
+
const edgeEntries = [];
|
|
2375
|
+
graph.forEachEdge((edge, attributes, source, target) => {
|
|
2376
|
+
edgeEntries.push({ key: edge, source, target, attributes });
|
|
2377
|
+
});
|
|
2378
|
+
edgeEntries.sort(
|
|
2379
|
+
(a, b) => a.source.localeCompare(b.source) || a.target.localeCompare(b.target) || String(a.attributes.relation).localeCompare(String(b.attributes.relation))
|
|
2380
|
+
);
|
|
2381
|
+
for (const entry of edgeEntries) {
|
|
2382
|
+
copy.addEdgeWithKey(entry.key, entry.source, entry.target, { ...entry.attributes });
|
|
2383
|
+
}
|
|
2384
|
+
return copy;
|
|
2385
|
+
}
|
|
2386
|
+
function toUndirectedCopy(graph) {
|
|
2387
|
+
const copy = new import_graphology2.default({ type: "undirected", multi: true, allowSelfLoops: graph.allowSelfLoops });
|
|
2388
|
+
graph.forEachNode((nodeId, attributes) => copy.addNode(nodeId, { ...attributes }));
|
|
2389
|
+
graph.forEachEdge((edgeKey, attributes, source, target) => {
|
|
2390
|
+
copy.addEdgeWithKey(edgeKey, source, target, { ...attributes });
|
|
2391
|
+
});
|
|
2392
|
+
return copy;
|
|
2393
|
+
}
|
|
2394
|
+
function runAlgorithm(working, options) {
|
|
2395
|
+
const rng = mulberry32(FIXED_SEED);
|
|
2396
|
+
if (options.algorithm === "leiden") {
|
|
2397
|
+
const undirected = toUndirectedCopy(working);
|
|
2398
|
+
import_graphology_communities_leiden.default.assign(undirected, { rng, maxIterations: options.maxIterations ?? 0 });
|
|
2399
|
+
undirected.forEachNode((nodeId, attributes) => {
|
|
2400
|
+
working.setNodeAttribute(nodeId, "community", attributes.community);
|
|
2401
|
+
});
|
|
2402
|
+
return;
|
|
2403
|
+
}
|
|
2404
|
+
import_graphology_communities_louvain.default.assign(working, { rng });
|
|
2405
|
+
}
|
|
2406
|
+
function cluster(graph, options = {}) {
|
|
2407
|
+
if (graph.order === 0) return graph;
|
|
2408
|
+
const working = sortedWorkingCopy(graph);
|
|
2409
|
+
runAlgorithm(working, options);
|
|
2410
|
+
const hubs = /* @__PURE__ */ new Map();
|
|
2411
|
+
working.forEachNode((nodeId) => {
|
|
2412
|
+
const community = working.getNodeAttribute(nodeId, "community");
|
|
2413
|
+
const degree = working.degree(nodeId);
|
|
2414
|
+
const current = hubs.get(community);
|
|
2415
|
+
if (!current || degree > current.degree) {
|
|
2416
|
+
hubs.set(community, { id: nodeId, degree });
|
|
2417
|
+
}
|
|
2418
|
+
});
|
|
2419
|
+
const membersByCommunity = /* @__PURE__ */ new Map();
|
|
2420
|
+
working.forEachNode((nodeId) => {
|
|
2421
|
+
const community = working.getNodeAttribute(nodeId, "community");
|
|
2422
|
+
const list = membersByCommunity.get(community);
|
|
2423
|
+
if (list) list.push(nodeId);
|
|
2424
|
+
else membersByCommunity.set(community, [nodeId]);
|
|
2425
|
+
});
|
|
2426
|
+
const labelByCommunity = /* @__PURE__ */ new Map();
|
|
2427
|
+
const hashByCommunity = /* @__PURE__ */ new Map();
|
|
2428
|
+
for (const [community, members] of membersByCommunity) {
|
|
2429
|
+
members.sort((a, b) => a.localeCompare(b));
|
|
2430
|
+
hashByCommunity.set(community, (0, import_node_crypto2.createHash)("sha256").update(members.join("\n")).digest("hex"));
|
|
2431
|
+
const hub = hubs.get(community);
|
|
2432
|
+
const hubLabel = hub ? working.getNodeAttribute(hub.id, "label") : void 0;
|
|
2433
|
+
labelByCommunity.set(community, hubLabel && hubLabel.length > 0 ? hubLabel : `community-${community}`);
|
|
2434
|
+
}
|
|
2435
|
+
working.forEachNode((nodeId) => {
|
|
2436
|
+
const community = working.getNodeAttribute(nodeId, "community");
|
|
2437
|
+
graph.mergeNodeAttributes(nodeId, {
|
|
2438
|
+
community,
|
|
2439
|
+
communityLabel: labelByCommunity.get(community),
|
|
2440
|
+
communityHash: hashByCommunity.get(community)
|
|
2441
|
+
});
|
|
2442
|
+
});
|
|
2113
2443
|
return graph;
|
|
2114
2444
|
}
|
|
2115
2445
|
|
|
2446
|
+
// src/ingest/corpus.ts
|
|
2447
|
+
var CLUSTER_STALE_ATTR = "graphifyClusterStale";
|
|
2448
|
+
var DEFAULT_RECLUSTER_RATIO = 0.1;
|
|
2449
|
+
function assignLocalCommunities(graph) {
|
|
2450
|
+
const communityMeta = /* @__PURE__ */ new Map();
|
|
2451
|
+
let maxCommunity = -1;
|
|
2452
|
+
graph.forEachNode((_id, attrs) => {
|
|
2453
|
+
const community = attrs.community;
|
|
2454
|
+
if (typeof community !== "number") return;
|
|
2455
|
+
if (community > maxCommunity) maxCommunity = community;
|
|
2456
|
+
if (!communityMeta.has(community)) {
|
|
2457
|
+
communityMeta.set(community, {
|
|
2458
|
+
label: attrs.communityLabel,
|
|
2459
|
+
hash: attrs.communityHash
|
|
2460
|
+
});
|
|
2461
|
+
}
|
|
2462
|
+
});
|
|
2463
|
+
const unassigned = graph.filterNodes((_id, attrs) => attrs.community === void 0).sort((a, b) => a.localeCompare(b));
|
|
2464
|
+
for (const id of unassigned) {
|
|
2465
|
+
const votes = /* @__PURE__ */ new Map();
|
|
2466
|
+
graph.forEachNeighbor(id, (_neighbor, attrs) => {
|
|
2467
|
+
const community = attrs.community;
|
|
2468
|
+
if (typeof community === "number") votes.set(community, (votes.get(community) ?? 0) + 1);
|
|
2469
|
+
});
|
|
2470
|
+
let adopted = null;
|
|
2471
|
+
let bestVotes = 0;
|
|
2472
|
+
for (const [community, count] of votes) {
|
|
2473
|
+
if (count > bestVotes || count === bestVotes && adopted !== null && community < adopted) {
|
|
2474
|
+
adopted = community;
|
|
2475
|
+
bestVotes = count;
|
|
2476
|
+
}
|
|
2477
|
+
}
|
|
2478
|
+
if (adopted === null) {
|
|
2479
|
+
adopted = ++maxCommunity;
|
|
2480
|
+
communityMeta.set(adopted, {
|
|
2481
|
+
label: graph.getNodeAttribute(id, "label") ?? id
|
|
2482
|
+
});
|
|
2483
|
+
}
|
|
2484
|
+
const meta = communityMeta.get(adopted);
|
|
2485
|
+
graph.mergeNodeAttributes(id, {
|
|
2486
|
+
community: adopted,
|
|
2487
|
+
communityLabel: meta?.label ?? `community-${adopted}`,
|
|
2488
|
+
...meta?.hash !== void 0 ? { communityHash: meta.hash } : {}
|
|
2489
|
+
});
|
|
2490
|
+
}
|
|
2491
|
+
}
|
|
2492
|
+
function updateCorpusGraph(graph, extraction, options = {}) {
|
|
2493
|
+
const validated = validateExtraction(extraction, "updateCorpusGraph");
|
|
2494
|
+
const target = graph ?? new import_graphology3.default({ type: "directed", multi: true, allowSelfLoops: true });
|
|
2495
|
+
const sourceFiles = new Set(validated.nodes.map((node) => node.sourceFile));
|
|
2496
|
+
const stale = target.filterNodes((_id, attrs) => sourceFiles.has(attrs.sourceFile));
|
|
2497
|
+
for (const id of stale) target.dropNode(id);
|
|
2498
|
+
const extractionIds = new Set(validated.nodes.map((node) => node.id));
|
|
2499
|
+
const addedReal = validated.nodes.filter((node) => !target.hasNode(node.id)).length;
|
|
2500
|
+
const newPlaceholders = /* @__PURE__ */ new Set();
|
|
2501
|
+
for (const edge of validated.edges) {
|
|
2502
|
+
for (const endpoint of [edge.source, edge.target]) {
|
|
2503
|
+
if (!extractionIds.has(endpoint) && !target.hasNode(endpoint)) newPlaceholders.add(endpoint);
|
|
2504
|
+
}
|
|
2505
|
+
}
|
|
2506
|
+
mergeExtractionsInto(target, [validated]);
|
|
2507
|
+
const orphaned = target.filterNodes(
|
|
2508
|
+
(id, attrs) => attrs.sourceFile === "<unknown>" && target.degree(id) === 0
|
|
2509
|
+
);
|
|
2510
|
+
for (const id of orphaned) target.dropNode(id);
|
|
2511
|
+
const churn = stale.length + addedReal + newPlaceholders.size + orphaned.length;
|
|
2512
|
+
const staleCount = (Number(target.getAttribute(CLUSTER_STALE_ATTR)) || 0) + churn;
|
|
2513
|
+
const mode = options.recluster ?? "auto";
|
|
2514
|
+
const ratio = options.reclusterRatio ?? DEFAULT_RECLUSTER_RATIO;
|
|
2515
|
+
const neverClustered = target.order > 0 && target.findNode((_id, attrs) => attrs.community !== void 0) === void 0;
|
|
2516
|
+
if (mode === true || mode === "auto" && (neverClustered || staleCount > target.order * ratio)) {
|
|
2517
|
+
cluster(target, { algorithm: options.algorithm });
|
|
2518
|
+
target.setAttribute(CLUSTER_STALE_ATTR, 0);
|
|
2519
|
+
} else {
|
|
2520
|
+
if (mode === "auto") assignLocalCommunities(target);
|
|
2521
|
+
target.setAttribute(CLUSTER_STALE_ATTR, staleCount);
|
|
2522
|
+
}
|
|
2523
|
+
return target;
|
|
2524
|
+
}
|
|
2525
|
+
|
|
2116
2526
|
// src/resolve.ts
|
|
2117
2527
|
var fs9 = __toESM(require("fs/promises"), 1);
|
|
2118
2528
|
var path5 = __toESM(require("path"), 1);
|
|
@@ -2307,99 +2717,6 @@ function resolveCrossFileReferences(extractions, options = {}) {
|
|
|
2307
2717
|
return { resolvedEndpoints, droppedPlaceholders };
|
|
2308
2718
|
}
|
|
2309
2719
|
|
|
2310
|
-
// src/cluster.ts
|
|
2311
|
-
var import_node_crypto = require("crypto");
|
|
2312
|
-
var import_graphology2 = __toESM(require("graphology"), 1);
|
|
2313
|
-
var import_graphology_communities_leiden = __toESM(require("@aflsolutions/graphology-communities-leiden"), 1);
|
|
2314
|
-
var import_graphology_communities_louvain = __toESM(require("graphology-communities-louvain"), 1);
|
|
2315
|
-
var FIXED_SEED = 1592594996;
|
|
2316
|
-
function mulberry32(seed) {
|
|
2317
|
-
let a = seed >>> 0;
|
|
2318
|
-
return function next() {
|
|
2319
|
-
a = a + 1831565813 | 0;
|
|
2320
|
-
let t = Math.imul(a ^ a >>> 15, 1 | a);
|
|
2321
|
-
t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
|
|
2322
|
-
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
2323
|
-
};
|
|
2324
|
-
}
|
|
2325
|
-
function sortedWorkingCopy(graph) {
|
|
2326
|
-
const copy = new import_graphology2.default({ type: graph.type, multi: graph.multi, allowSelfLoops: graph.allowSelfLoops });
|
|
2327
|
-
const nodeIds = graph.nodes().sort((a, b) => a.localeCompare(b));
|
|
2328
|
-
for (const id of nodeIds) {
|
|
2329
|
-
copy.addNode(id, { ...graph.getNodeAttributes(id) });
|
|
2330
|
-
}
|
|
2331
|
-
const edgeEntries = [];
|
|
2332
|
-
graph.forEachEdge((edge, attributes, source, target) => {
|
|
2333
|
-
edgeEntries.push({ key: edge, source, target, attributes });
|
|
2334
|
-
});
|
|
2335
|
-
edgeEntries.sort(
|
|
2336
|
-
(a, b) => a.source.localeCompare(b.source) || a.target.localeCompare(b.target) || String(a.attributes.relation).localeCompare(String(b.attributes.relation))
|
|
2337
|
-
);
|
|
2338
|
-
for (const entry of edgeEntries) {
|
|
2339
|
-
copy.addEdgeWithKey(entry.key, entry.source, entry.target, { ...entry.attributes });
|
|
2340
|
-
}
|
|
2341
|
-
return copy;
|
|
2342
|
-
}
|
|
2343
|
-
function toUndirectedCopy(graph) {
|
|
2344
|
-
const copy = new import_graphology2.default({ type: "undirected", multi: true, allowSelfLoops: graph.allowSelfLoops });
|
|
2345
|
-
graph.forEachNode((nodeId, attributes) => copy.addNode(nodeId, { ...attributes }));
|
|
2346
|
-
graph.forEachEdge((edgeKey, attributes, source, target) => {
|
|
2347
|
-
copy.addEdgeWithKey(edgeKey, source, target, { ...attributes });
|
|
2348
|
-
});
|
|
2349
|
-
return copy;
|
|
2350
|
-
}
|
|
2351
|
-
function runAlgorithm(working, options) {
|
|
2352
|
-
const rng = mulberry32(FIXED_SEED);
|
|
2353
|
-
if (options.algorithm === "leiden") {
|
|
2354
|
-
const undirected = toUndirectedCopy(working);
|
|
2355
|
-
import_graphology_communities_leiden.default.assign(undirected, { rng, maxIterations: options.maxIterations ?? 0 });
|
|
2356
|
-
undirected.forEachNode((nodeId, attributes) => {
|
|
2357
|
-
working.setNodeAttribute(nodeId, "community", attributes.community);
|
|
2358
|
-
});
|
|
2359
|
-
return;
|
|
2360
|
-
}
|
|
2361
|
-
import_graphology_communities_louvain.default.assign(working, { rng });
|
|
2362
|
-
}
|
|
2363
|
-
function cluster(graph, options = {}) {
|
|
2364
|
-
if (graph.order === 0) return graph;
|
|
2365
|
-
const working = sortedWorkingCopy(graph);
|
|
2366
|
-
runAlgorithm(working, options);
|
|
2367
|
-
const hubs = /* @__PURE__ */ new Map();
|
|
2368
|
-
working.forEachNode((nodeId) => {
|
|
2369
|
-
const community = working.getNodeAttribute(nodeId, "community");
|
|
2370
|
-
const degree = working.degree(nodeId);
|
|
2371
|
-
const current = hubs.get(community);
|
|
2372
|
-
if (!current || degree > current.degree) {
|
|
2373
|
-
hubs.set(community, { id: nodeId, degree });
|
|
2374
|
-
}
|
|
2375
|
-
});
|
|
2376
|
-
const membersByCommunity = /* @__PURE__ */ new Map();
|
|
2377
|
-
working.forEachNode((nodeId) => {
|
|
2378
|
-
const community = working.getNodeAttribute(nodeId, "community");
|
|
2379
|
-
const list = membersByCommunity.get(community);
|
|
2380
|
-
if (list) list.push(nodeId);
|
|
2381
|
-
else membersByCommunity.set(community, [nodeId]);
|
|
2382
|
-
});
|
|
2383
|
-
const labelByCommunity = /* @__PURE__ */ new Map();
|
|
2384
|
-
const hashByCommunity = /* @__PURE__ */ new Map();
|
|
2385
|
-
for (const [community, members] of membersByCommunity) {
|
|
2386
|
-
members.sort((a, b) => a.localeCompare(b));
|
|
2387
|
-
hashByCommunity.set(community, (0, import_node_crypto.createHash)("sha256").update(members.join("\n")).digest("hex"));
|
|
2388
|
-
const hub = hubs.get(community);
|
|
2389
|
-
const hubLabel = hub ? working.getNodeAttribute(hub.id, "label") : void 0;
|
|
2390
|
-
labelByCommunity.set(community, hubLabel && hubLabel.length > 0 ? hubLabel : `community-${community}`);
|
|
2391
|
-
}
|
|
2392
|
-
working.forEachNode((nodeId) => {
|
|
2393
|
-
const community = working.getNodeAttribute(nodeId, "community");
|
|
2394
|
-
graph.mergeNodeAttributes(nodeId, {
|
|
2395
|
-
community,
|
|
2396
|
-
communityLabel: labelByCommunity.get(community),
|
|
2397
|
-
communityHash: hashByCommunity.get(community)
|
|
2398
|
-
});
|
|
2399
|
-
});
|
|
2400
|
-
return graph;
|
|
2401
|
-
}
|
|
2402
|
-
|
|
2403
2720
|
// src/analyze.ts
|
|
2404
2721
|
var ABSOLUTE_DEGREE_FLOOR = 10;
|
|
2405
2722
|
var MEAN_DEGREE_MULTIPLIER = 3;
|
|
@@ -2577,7 +2894,7 @@ var fs12 = __toESM(require("fs/promises"), 1);
|
|
|
2577
2894
|
var path6 = __toESM(require("path"), 1);
|
|
2578
2895
|
|
|
2579
2896
|
// src/security.ts
|
|
2580
|
-
var
|
|
2897
|
+
var import_node_crypto3 = require("crypto");
|
|
2581
2898
|
var dns = __toESM(require("dns"), 1);
|
|
2582
2899
|
var http = __toESM(require("http"), 1);
|
|
2583
2900
|
var https = __toESM(require("https"), 1);
|
|
@@ -2652,12 +2969,12 @@ function escapeHtml(text) {
|
|
|
2652
2969
|
var fs11 = __toESM(require("fs/promises"), 1);
|
|
2653
2970
|
|
|
2654
2971
|
// src/store/serialize.ts
|
|
2655
|
-
var
|
|
2972
|
+
var import_graphology4 = __toESM(require("graphology"), 1);
|
|
2656
2973
|
function serializeGraph(graph) {
|
|
2657
2974
|
return graph.export();
|
|
2658
2975
|
}
|
|
2659
2976
|
function deserializeGraph(data) {
|
|
2660
|
-
return
|
|
2977
|
+
return import_graphology4.default.from(data);
|
|
2661
2978
|
}
|
|
2662
2979
|
|
|
2663
2980
|
// src/store/localFile.ts
|
|
@@ -2819,7 +3136,7 @@ var fs14 = __toESM(require("fs/promises"), 1);
|
|
|
2819
3136
|
var path8 = __toESM(require("path"), 1);
|
|
2820
3137
|
|
|
2821
3138
|
// src/extractionCache.ts
|
|
2822
|
-
var
|
|
3139
|
+
var import_node_crypto4 = require("crypto");
|
|
2823
3140
|
var fs13 = __toESM(require("fs/promises"), 1);
|
|
2824
3141
|
var path7 = __toESM(require("path"), 1);
|
|
2825
3142
|
var ExtractionCache = class {
|
|
@@ -2828,7 +3145,7 @@ var ExtractionCache = class {
|
|
|
2828
3145
|
this.dir = path7.join(outDir, "cache");
|
|
2829
3146
|
}
|
|
2830
3147
|
key(relFile, content) {
|
|
2831
|
-
return (0,
|
|
3148
|
+
return (0, import_node_crypto4.createHash)("sha256").update(relFile).update("\0").update(content).digest("hex");
|
|
2832
3149
|
}
|
|
2833
3150
|
async get(key) {
|
|
2834
3151
|
try {
|
|
@@ -3200,6 +3517,141 @@ function explainNode(graph, query) {
|
|
|
3200
3517
|
};
|
|
3201
3518
|
}
|
|
3202
3519
|
|
|
3520
|
+
// src/retrieval.ts
|
|
3521
|
+
var DEFAULT_TOKEN_BUDGET2 = 2e3;
|
|
3522
|
+
var DEFAULT_MAX_HOPS = 2;
|
|
3523
|
+
function resolveSeeds(graph, hits) {
|
|
3524
|
+
const seeds = /* @__PURE__ */ new Map();
|
|
3525
|
+
for (const hit of hits) {
|
|
3526
|
+
let id = null;
|
|
3527
|
+
if (hit.nodeId !== void 0 && graph.hasNode(hit.nodeId)) {
|
|
3528
|
+
id = hit.nodeId;
|
|
3529
|
+
} else if (hit.text !== void 0) {
|
|
3530
|
+
id = resolveNode(graph, hit.text)?.id ?? null;
|
|
3531
|
+
}
|
|
3532
|
+
const key = id ?? hit.nodeId ?? hit.text ?? "";
|
|
3533
|
+
if (key === "") continue;
|
|
3534
|
+
const existing = seeds.get(key);
|
|
3535
|
+
if (existing) {
|
|
3536
|
+
if (hit.score !== void 0 && (existing.score === void 0 || hit.score > existing.score)) {
|
|
3537
|
+
existing.score = hit.score;
|
|
3538
|
+
}
|
|
3539
|
+
continue;
|
|
3540
|
+
}
|
|
3541
|
+
seeds.set(key, { id: key, score: hit.score, inGraph: id !== null, text: hit.text });
|
|
3542
|
+
}
|
|
3543
|
+
return [...seeds.values()];
|
|
3544
|
+
}
|
|
3545
|
+
function expandRetrieval(graph, hits, options = {}) {
|
|
3546
|
+
const tokenBudget = options.tokenBudget ?? DEFAULT_TOKEN_BUDGET2;
|
|
3547
|
+
const maxHops = options.maxHops ?? DEFAULT_MAX_HOPS;
|
|
3548
|
+
const relations = options.relations ? new Set(options.relations) : null;
|
|
3549
|
+
const seeds = resolveSeeds(graph, hits);
|
|
3550
|
+
const graphSeeds = seeds.filter((s) => s.inGraph);
|
|
3551
|
+
const seedScore = new Map(graphSeeds.map((s) => [s.id, s.score]));
|
|
3552
|
+
const hops = /* @__PURE__ */ new Map();
|
|
3553
|
+
const via = /* @__PURE__ */ new Map();
|
|
3554
|
+
const frontier = [];
|
|
3555
|
+
let spentTokens = 0;
|
|
3556
|
+
for (const seed of graphSeeds) {
|
|
3557
|
+
hops.set(seed.id, 0);
|
|
3558
|
+
frontier.push(seed.id);
|
|
3559
|
+
spentTokens += nodeTokenCost(graph, seed.id);
|
|
3560
|
+
}
|
|
3561
|
+
let truncated = false;
|
|
3562
|
+
while (frontier.length > 0) {
|
|
3563
|
+
const current = frontier.shift();
|
|
3564
|
+
const currentHops = hops.get(current);
|
|
3565
|
+
if (currentHops >= maxHops) continue;
|
|
3566
|
+
const candidates = [];
|
|
3567
|
+
graph.forEachEdge(current, (_key, attrs, source, target) => {
|
|
3568
|
+
const relation = String(attrs.relation);
|
|
3569
|
+
if (relations && !relations.has(relation)) return;
|
|
3570
|
+
const neighbor = source === current ? target : source;
|
|
3571
|
+
candidates.push({
|
|
3572
|
+
neighbor,
|
|
3573
|
+
edge: { source, target, relation, confidence: String(attrs.confidence) }
|
|
3574
|
+
});
|
|
3575
|
+
});
|
|
3576
|
+
candidates.sort((a, b) => a.neighbor.localeCompare(b.neighbor));
|
|
3577
|
+
for (const { neighbor, edge } of candidates) {
|
|
3578
|
+
if (hops.has(neighbor)) continue;
|
|
3579
|
+
const cost = nodeTokenCost(graph, neighbor);
|
|
3580
|
+
if (spentTokens + cost > tokenBudget) {
|
|
3581
|
+
truncated = true;
|
|
3582
|
+
continue;
|
|
3583
|
+
}
|
|
3584
|
+
spentTokens += cost;
|
|
3585
|
+
hops.set(neighbor, currentHops + 1);
|
|
3586
|
+
via.set(neighbor, edge);
|
|
3587
|
+
frontier.push(neighbor);
|
|
3588
|
+
}
|
|
3589
|
+
}
|
|
3590
|
+
const degree = (id) => graph.degree(id);
|
|
3591
|
+
const nodes = [...hops.entries()].map(([id, hopCount]) => {
|
|
3592
|
+
const attrs = graph.getNodeAttributes(id);
|
|
3593
|
+
return {
|
|
3594
|
+
id,
|
|
3595
|
+
label: attrs.label ?? id,
|
|
3596
|
+
...attrs.kind !== void 0 ? { kind: String(attrs.kind) } : {},
|
|
3597
|
+
sourceFile: attrs.sourceFile ?? "",
|
|
3598
|
+
sourceLocation: attrs.sourceLocation ?? "",
|
|
3599
|
+
hops: hopCount,
|
|
3600
|
+
...via.has(id) ? { via: via.get(id) } : {},
|
|
3601
|
+
...hopCount === 0 && seedScore.get(id) !== void 0 ? { seedScore: seedScore.get(id) } : {}
|
|
3602
|
+
};
|
|
3603
|
+
});
|
|
3604
|
+
for (const seed of seeds) {
|
|
3605
|
+
if (seed.inGraph) continue;
|
|
3606
|
+
nodes.push({
|
|
3607
|
+
id: seed.id,
|
|
3608
|
+
label: seed.text ?? seed.id,
|
|
3609
|
+
sourceFile: "",
|
|
3610
|
+
sourceLocation: "",
|
|
3611
|
+
hops: 0,
|
|
3612
|
+
...seed.score !== void 0 ? { seedScore: seed.score } : {}
|
|
3613
|
+
});
|
|
3614
|
+
}
|
|
3615
|
+
nodes.sort((a, b) => {
|
|
3616
|
+
if (a.hops !== b.hops) return a.hops - b.hops;
|
|
3617
|
+
if (a.hops === 0) {
|
|
3618
|
+
return (b.seedScore ?? -Infinity) - (a.seedScore ?? -Infinity) || a.id.localeCompare(b.id);
|
|
3619
|
+
}
|
|
3620
|
+
const degreeDiff = (graph.hasNode(b.id) ? degree(b.id) : 0) - (graph.hasNode(a.id) ? degree(a.id) : 0);
|
|
3621
|
+
return degreeDiff || a.id.localeCompare(b.id);
|
|
3622
|
+
});
|
|
3623
|
+
const included = /* @__PURE__ */ new Set([...hops.keys()]);
|
|
3624
|
+
const edges = [];
|
|
3625
|
+
graph.forEachEdge((_key, attrs, source, target) => {
|
|
3626
|
+
if (!included.has(source) || !included.has(target)) return;
|
|
3627
|
+
const relation = String(attrs.relation);
|
|
3628
|
+
if (relations && !relations.has(relation)) return;
|
|
3629
|
+
edges.push({ source, target, relation, confidence: String(attrs.confidence) });
|
|
3630
|
+
});
|
|
3631
|
+
edges.sort(
|
|
3632
|
+
(a, b) => a.source.localeCompare(b.source) || a.target.localeCompare(b.target) || a.relation.localeCompare(b.relation)
|
|
3633
|
+
);
|
|
3634
|
+
const communities = /* @__PURE__ */ new Map();
|
|
3635
|
+
for (const id of included) {
|
|
3636
|
+
const attrs = graph.getNodeAttributes(id);
|
|
3637
|
+
const community = attrs.community;
|
|
3638
|
+
if (community === void 0) continue;
|
|
3639
|
+
const entry = communities.get(community) ?? {
|
|
3640
|
+
id: community,
|
|
3641
|
+
label: attrs.communityLabel ?? String(community),
|
|
3642
|
+
seedCount: 0
|
|
3643
|
+
};
|
|
3644
|
+
if (hops.get(id) === 0) entry.seedCount += 1;
|
|
3645
|
+
communities.set(community, entry);
|
|
3646
|
+
}
|
|
3647
|
+
return {
|
|
3648
|
+
nodes,
|
|
3649
|
+
edges,
|
|
3650
|
+
communities: [...communities.values()].sort((a, b) => b.seedCount - a.seedCount || a.id - b.id),
|
|
3651
|
+
truncated
|
|
3652
|
+
};
|
|
3653
|
+
}
|
|
3654
|
+
|
|
3203
3655
|
// src/impact.ts
|
|
3204
3656
|
var DEPENDENCY_RELATIONS = /* @__PURE__ */ new Set([
|
|
3205
3657
|
"calls",
|
|
@@ -3265,7 +3717,7 @@ function affectedBy(graph, nodeQuery, options = {}) {
|
|
|
3265
3717
|
}
|
|
3266
3718
|
|
|
3267
3719
|
// src/merge.ts
|
|
3268
|
-
var
|
|
3720
|
+
var import_graphology5 = __toESM(require("graphology"), 1);
|
|
3269
3721
|
var CONFIDENCE_RANK3 = { EXTRACTED: 3, INFERRED: 2, AMBIGUOUS: 1 };
|
|
3270
3722
|
function strongerConfidence2(a, b) {
|
|
3271
3723
|
return CONFIDENCE_RANK3[a] >= CONFIDENCE_RANK3[b] ? a : b;
|
|
@@ -3282,7 +3734,7 @@ function mergeGraphs(entries) {
|
|
|
3282
3734
|
if (duplicate !== void 0) {
|
|
3283
3735
|
throw new Error(`Duplicate project name in merge: "${duplicate}" \u2014 every entry needs a unique name.`);
|
|
3284
3736
|
}
|
|
3285
|
-
const merged = new
|
|
3737
|
+
const merged = new import_graphology5.default({ type: "directed", multi: true, allowSelfLoops: true });
|
|
3286
3738
|
const sorted = [...entries].sort((a, b) => a.name.localeCompare(b.name));
|
|
3287
3739
|
for (const { name, graph } of sorted) {
|
|
3288
3740
|
const nodeIds = [...graph.nodes()].sort((a, b) => a.localeCompare(b));
|
|
@@ -3468,7 +3920,7 @@ async function addViewEdges(query, schema, viewNames, tableNames, edges) {
|
|
|
3468
3920
|
}
|
|
3469
3921
|
|
|
3470
3922
|
// src/memory.ts
|
|
3471
|
-
var
|
|
3923
|
+
var import_node_crypto5 = require("crypto");
|
|
3472
3924
|
var fs15 = __toESM(require("fs/promises"), 1);
|
|
3473
3925
|
var path10 = __toESM(require("path"), 1);
|
|
3474
3926
|
var OUTCOMES = /* @__PURE__ */ new Set(["useful", "dead_end", "corrected"]);
|
|
@@ -3490,7 +3942,7 @@ async function saveResult(entry, memoryDir, now = /* @__PURE__ */ new Date()) {
|
|
|
3490
3942
|
validateResult(entry);
|
|
3491
3943
|
await fs15.mkdir(memoryDir, { recursive: true });
|
|
3492
3944
|
const saved = { ...entry, savedAt: now.toISOString() };
|
|
3493
|
-
const hash = (0,
|
|
3945
|
+
const hash = (0, import_node_crypto5.createHash)("sha256").update(JSON.stringify(saved)).digest("hex").slice(0, 8);
|
|
3494
3946
|
const stamp = saved.savedAt.replace(/[:.]/g, "-");
|
|
3495
3947
|
const filePath = path10.join(memoryDir, `result-${stamp}-${hash}.json`);
|
|
3496
3948
|
await fs15.writeFile(filePath, JSON.stringify(saved, null, 2), "utf-8");
|
|
@@ -4005,15 +4457,18 @@ function checkRules(graph, config) {
|
|
|
4005
4457
|
collectFiles,
|
|
4006
4458
|
deserializeGraph,
|
|
4007
4459
|
diffGraphs,
|
|
4460
|
+
expandRetrieval,
|
|
4008
4461
|
explainNode,
|
|
4009
4462
|
exportGraph,
|
|
4010
4463
|
extract,
|
|
4011
4464
|
extractMysql,
|
|
4012
4465
|
globToRegExp,
|
|
4013
4466
|
graphForDirectory,
|
|
4467
|
+
ingestDocument,
|
|
4014
4468
|
isTestFile,
|
|
4015
4469
|
loadGraph,
|
|
4016
4470
|
loadResults,
|
|
4471
|
+
mergeExtractionsInto,
|
|
4017
4472
|
mergeGraphs,
|
|
4018
4473
|
queryGraph,
|
|
4019
4474
|
rankForTask,
|
|
@@ -4031,6 +4486,7 @@ function checkRules(graph, config) {
|
|
|
4031
4486
|
shortestPath,
|
|
4032
4487
|
testsForChangedFiles,
|
|
4033
4488
|
testsForNode,
|
|
4489
|
+
updateCorpusGraph,
|
|
4034
4490
|
validateExtraction,
|
|
4035
4491
|
validateRules
|
|
4036
4492
|
});
|