@dreamtree-org/graphify 1.0.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/index.cjs ADDED
@@ -0,0 +1,2759 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ EXTRACTOR_REGISTRY: () => EXTRACTOR_REGISTRY,
34
+ ExtractionResultSchema: () => ExtractionResultSchema,
35
+ ExtractionValidationError: () => ExtractionValidationError,
36
+ analyze: () => analyze,
37
+ buildGraph: () => buildGraph,
38
+ cluster: () => cluster,
39
+ collectFiles: () => collectFiles,
40
+ explainNode: () => explainNode,
41
+ exportGraph: () => exportGraph,
42
+ extract: () => extract,
43
+ loadGraph: () => loadGraph,
44
+ queryGraph: () => queryGraph,
45
+ renderReport: () => renderReport,
46
+ resolveNode: () => resolveNode,
47
+ runPipeline: () => runPipeline,
48
+ scoreNodes: () => scoreNodes,
49
+ shortestPath: () => shortestPath,
50
+ validateExtraction: () => validateExtraction
51
+ });
52
+ module.exports = __toCommonJS(index_exports);
53
+
54
+ // node_modules/tsup/assets/cjs_shims.js
55
+ var getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${__filename}`).href : document.currentScript && document.currentScript.tagName.toUpperCase() === "SCRIPT" ? document.currentScript.src : new URL("main.js", document.baseURI).href;
56
+ var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
57
+
58
+ // src/detect.ts
59
+ var fs = __toESM(require("fs"), 1);
60
+ var path = __toESM(require("path"), 1);
61
+ var SKIP_DIRS = /* @__PURE__ */ new Set([
62
+ "node_modules",
63
+ ".git",
64
+ "dist",
65
+ "build",
66
+ "out",
67
+ "graphify-out",
68
+ ".next",
69
+ ".venv",
70
+ "venv",
71
+ "__pycache__",
72
+ ".cache",
73
+ "coverage",
74
+ ".turbo"
75
+ ]);
76
+ var CODE_EXTENSIONS = /* @__PURE__ */ new Set([
77
+ ".ts",
78
+ ".tsx",
79
+ ".js",
80
+ ".jsx",
81
+ ".mjs",
82
+ ".cjs",
83
+ ".mts",
84
+ ".cts",
85
+ ".py",
86
+ ".go",
87
+ ".rs",
88
+ ".java",
89
+ ".cs",
90
+ ".rb",
91
+ ".c",
92
+ ".h",
93
+ ".cpp",
94
+ ".hpp",
95
+ ".cc",
96
+ ".php",
97
+ ".kt",
98
+ ".swift",
99
+ ".scala",
100
+ ".lua",
101
+ ".sh",
102
+ ".sql"
103
+ ]);
104
+ var DOCUMENT_EXTENSIONS = /* @__PURE__ */ new Set([".md", ".mdx", ".txt", ".rst", ".adoc", ".org", ".json", ".yaml", ".yml", ".toml"]);
105
+ var PAPER_EXTENSIONS = /* @__PURE__ */ new Set([".pdf", ".tex", ".bib"]);
106
+ var IMAGE_EXTENSIONS = /* @__PURE__ */ new Set([".png", ".jpg", ".jpeg", ".gif", ".svg", ".webp", ".bmp", ".ico"]);
107
+ var VIDEO_EXTENSIONS = /* @__PURE__ */ new Set([".mp4", ".mov", ".avi", ".mkv", ".webm"]);
108
+ var SENSITIVE_NAME_PATTERNS = [
109
+ /^\.env(\..*)?$/i,
110
+ /^\.npmrc$/i,
111
+ /^\.pypirc$/i,
112
+ /credentials/i,
113
+ /secret/i,
114
+ /^id_rsa$/i,
115
+ /^id_ed25519$/i,
116
+ /\.pem$/i,
117
+ /\.key$/i,
118
+ /\.pfx$/i,
119
+ /\.p12$/i,
120
+ /^\.aws\//i,
121
+ /service[-_]?account.*\.json$/i
122
+ ];
123
+ function isSensitiveName(name) {
124
+ return SENSITIVE_NAME_PATTERNS.some((pattern) => pattern.test(name));
125
+ }
126
+ function categorize(ext) {
127
+ const lower = ext.toLowerCase();
128
+ if (CODE_EXTENSIONS.has(lower)) return "code";
129
+ if (DOCUMENT_EXTENSIONS.has(lower)) return "document";
130
+ if (PAPER_EXTENSIONS.has(lower)) return "paper";
131
+ if (IMAGE_EXTENSIONS.has(lower)) return "image";
132
+ if (VIDEO_EXTENSIONS.has(lower)) return "video";
133
+ return null;
134
+ }
135
+ function countWords(content) {
136
+ const trimmed = content.trim();
137
+ if (trimmed.length === 0) return 0;
138
+ return trimmed.split(/\s+/).length;
139
+ }
140
+ function readTextSafely(filePath) {
141
+ const buf = fs.readFileSync(filePath);
142
+ return buf.toString("utf-8");
143
+ }
144
+ function collectFiles(root) {
145
+ const scanRoot = path.resolve(root);
146
+ const stat = fs.statSync(scanRoot);
147
+ if (!stat.isDirectory()) {
148
+ throw new Error(`collectFiles: not a directory: ${scanRoot}`);
149
+ }
150
+ const files = {
151
+ code: [],
152
+ document: [],
153
+ paper: [],
154
+ image: [],
155
+ video: []
156
+ };
157
+ const skippedSensitive = [];
158
+ let totalWords = 0;
159
+ const stack = [scanRoot];
160
+ const textCategories = /* @__PURE__ */ new Set(["code", "document", "paper"]);
161
+ while (stack.length > 0) {
162
+ const dir = stack.pop();
163
+ let entries;
164
+ try {
165
+ entries = fs.readdirSync(dir, { withFileTypes: true });
166
+ } catch {
167
+ continue;
168
+ }
169
+ entries.sort((a, b) => a.name.localeCompare(b.name));
170
+ for (const entry of entries) {
171
+ if (entry.isSymbolicLink()) continue;
172
+ const fullPath = path.join(dir, entry.name);
173
+ const relPath = path.relative(scanRoot, fullPath);
174
+ if (entry.isDirectory()) {
175
+ if (SKIP_DIRS.has(entry.name) || entry.name.startsWith(".")) continue;
176
+ stack.push(fullPath);
177
+ continue;
178
+ }
179
+ if (!entry.isFile()) continue;
180
+ if (isSensitiveName(entry.name)) {
181
+ skippedSensitive.push(relPath);
182
+ continue;
183
+ }
184
+ const ext = path.extname(entry.name);
185
+ const category = categorize(ext);
186
+ if (category === null) continue;
187
+ files[category].push(relPath);
188
+ if (textCategories.has(category)) {
189
+ try {
190
+ const content = readTextSafely(fullPath);
191
+ totalWords += countWords(content);
192
+ } catch {
193
+ }
194
+ }
195
+ }
196
+ }
197
+ for (const category of Object.keys(files)) {
198
+ files[category].sort();
199
+ }
200
+ skippedSensitive.sort();
201
+ const totalFiles = Object.values(files).reduce((sum, list) => sum + list.length, 0);
202
+ return {
203
+ scanRoot,
204
+ files,
205
+ totalFiles,
206
+ totalWords,
207
+ skippedSensitive
208
+ };
209
+ }
210
+
211
+ // src/extract.ts
212
+ var path4 = __toESM(require("path"), 1);
213
+
214
+ // src/extractors/csharp.ts
215
+ var fs2 = __toESM(require("fs/promises"), 1);
216
+
217
+ // src/extractors/common.ts
218
+ var path2 = __toESM(require("path"), 1);
219
+ function namedChildren(node) {
220
+ return node.namedChildren.filter((c) => c !== null);
221
+ }
222
+ function descendantsOfType(node, types) {
223
+ return node.descendantsOfType(types).filter((c) => c !== null);
224
+ }
225
+ function toPosix(p) {
226
+ return p.split(path2.sep).join("/");
227
+ }
228
+ function lineOf(node) {
229
+ return `L${node.startPosition.row + 1}`;
230
+ }
231
+ function entityId(sourceFile, qualifiedName) {
232
+ return `${toPosix(sourceFile)}::${qualifiedName}`;
233
+ }
234
+ function externalId(name) {
235
+ return `external:${name}`;
236
+ }
237
+ function resolveModuleRef(sourceFile, specifier) {
238
+ if (specifier.startsWith(".") || specifier.startsWith("/")) {
239
+ const dir = path2.posix.dirname(toPosix(sourceFile));
240
+ const joined = specifier.startsWith("/") ? specifier : path2.posix.join(dir, specifier);
241
+ const normalized = path2.posix.normalize(joined);
242
+ return { id: normalized, label: specifier, external: false };
243
+ }
244
+ return { id: `module:${specifier}`, label: specifier, external: true };
245
+ }
246
+ var ExtractionBuilder = class {
247
+ nodeIds = /* @__PURE__ */ new Set();
248
+ nodes = [];
249
+ edges = [];
250
+ addNode(node) {
251
+ if (this.nodeIds.has(node.id)) return;
252
+ this.nodeIds.add(node.id);
253
+ this.nodes.push(node);
254
+ }
255
+ hasNode(id) {
256
+ return this.nodeIds.has(id);
257
+ }
258
+ addEdge(edge) {
259
+ this.edges.push(edge);
260
+ }
261
+ build() {
262
+ return { nodes: this.nodes, edges: this.edges };
263
+ }
264
+ };
265
+
266
+ // src/extractors/wasmLoader.ts
267
+ var import_node_module = require("module");
268
+ var import_web_tree_sitter = require("web-tree-sitter");
269
+ var require2 = (0, import_node_module.createRequire)(importMetaUrl);
270
+ var initPromise = null;
271
+ function ensureInitialized() {
272
+ if (!initPromise) {
273
+ initPromise = import_web_tree_sitter.Parser.init();
274
+ }
275
+ return initPromise;
276
+ }
277
+ var languageCache = /* @__PURE__ */ new Map();
278
+ async function loadLanguage(grammarName) {
279
+ await ensureInitialized();
280
+ let cached = languageCache.get(grammarName);
281
+ if (!cached) {
282
+ const wasmPath = require2.resolve(`tree-sitter-wasms/out/tree-sitter-${grammarName}.wasm`);
283
+ cached = import_web_tree_sitter.Language.load(wasmPath);
284
+ languageCache.set(grammarName, cached);
285
+ }
286
+ return cached;
287
+ }
288
+ async function createParser(grammarName) {
289
+ const language = await loadLanguage(grammarName);
290
+ const parser = new import_web_tree_sitter.Parser();
291
+ parser.setLanguage(language);
292
+ return parser;
293
+ }
294
+
295
+ // src/extractors/csharp.ts
296
+ async function extractCSharp(filePath) {
297
+ const parser = await createParser("c_sharp");
298
+ const raw = await fs2.readFile(filePath);
299
+ const content = raw.toString("utf-8");
300
+ const tree = parser.parse(content);
301
+ if (!tree) {
302
+ throw new Error(`extractCSharp: parser produced no tree for ${filePath}`);
303
+ }
304
+ const root = tree.rootNode;
305
+ const sourceFile = toPosix(filePath);
306
+ const builder = new ExtractionBuilder();
307
+ const fileId = sourceFile;
308
+ builder.addNode({ id: fileId, label: sourceFile, sourceFile, sourceLocation: "L1" });
309
+ const nameIndex = /* @__PURE__ */ new Map();
310
+ const functionNodeMap = /* @__PURE__ */ new Map();
311
+ const classMethods = /* @__PURE__ */ new Map();
312
+ const enclosingClassOf = /* @__PURE__ */ new Map();
313
+ const importIndex = /* @__PURE__ */ new Map();
314
+ function addModuleNode(ref) {
315
+ if (builder.hasNode(ref.id)) return;
316
+ builder.addNode({
317
+ id: ref.id,
318
+ label: ref.label,
319
+ sourceFile: ref.external ? "<external>" : ref.id,
320
+ sourceLocation: "L1"
321
+ });
322
+ }
323
+ function resolveHeritageTarget(name) {
324
+ const resolved = nameIndex.get(name);
325
+ if (resolved) return resolved;
326
+ const extId = externalId(name);
327
+ if (!builder.hasNode(extId)) {
328
+ builder.addNode({ id: extId, label: name, sourceFile: "<external>", sourceLocation: "L0" });
329
+ }
330
+ return extId;
331
+ }
332
+ function handleUsingDirective(node) {
333
+ const children = namedChildren(node);
334
+ const first = children[0];
335
+ if (!first) return;
336
+ let aliasName;
337
+ let targetNode;
338
+ if (first.type === "name_equals") {
339
+ aliasName = namedChildren(first)[0]?.text;
340
+ targetNode = children[1];
341
+ } else {
342
+ targetNode = first;
343
+ }
344
+ if (!targetNode) return;
345
+ const ref = resolveModuleRef(sourceFile, targetNode.text);
346
+ addModuleNode(ref);
347
+ if (aliasName) importIndex.set(aliasName, ref);
348
+ builder.addEdge({ source: fileId, target: ref.id, relation: "imports", confidence: "EXTRACTED" });
349
+ }
350
+ function handleMethodDeclaration(typeNode, typeId, typeName, member) {
351
+ const methodName = member.childForFieldName("name")?.text ?? "(anonymous)";
352
+ const methodId = entityId(sourceFile, `${typeName}.${methodName}`);
353
+ builder.addNode({
354
+ id: methodId,
355
+ label: `method ${typeName}.${methodName}`,
356
+ sourceFile,
357
+ sourceLocation: lineOf(member)
358
+ });
359
+ builder.addEdge({ source: typeId, target: methodId, relation: "method", confidence: "EXTRACTED" });
360
+ let methods = classMethods.get(typeNode.id);
361
+ if (!methods) {
362
+ methods = /* @__PURE__ */ new Map();
363
+ classMethods.set(typeNode.id, methods);
364
+ }
365
+ methods.set(methodName, methodId);
366
+ functionNodeMap.set(member.id, methodId);
367
+ enclosingClassOf.set(member.id, typeNode.id);
368
+ }
369
+ function handleBaseList(typeId, node) {
370
+ const baseList = node.childForFieldName("bases");
371
+ if (!baseList) return;
372
+ const entries = namedChildren(baseList);
373
+ entries.forEach((entry, index) => {
374
+ builder.addEdge({
375
+ source: typeId,
376
+ target: resolveHeritageTarget(entry.text),
377
+ relation: index === 0 ? "inherits" : "implements",
378
+ confidence: "EXTRACTED"
379
+ });
380
+ });
381
+ }
382
+ function handleClassDeclaration(node) {
383
+ const name = node.childForFieldName("name")?.text ?? "(anonymous)";
384
+ const classId = entityId(sourceFile, name);
385
+ builder.addNode({ id: classId, label: `class ${name}`, sourceFile, sourceLocation: lineOf(node) });
386
+ builder.addEdge({ source: fileId, target: classId, relation: "contains", confidence: "EXTRACTED" });
387
+ nameIndex.set(name, classId);
388
+ functionNodeMap.set(node.id, classId);
389
+ classMethods.set(node.id, /* @__PURE__ */ new Map());
390
+ handleBaseList(classId, node);
391
+ const body = node.childForFieldName("body");
392
+ if (body) {
393
+ for (const member of namedChildren(body)) {
394
+ if (member.type !== "method_declaration") continue;
395
+ handleMethodDeclaration(node, classId, name, member);
396
+ }
397
+ }
398
+ }
399
+ function handleInterfaceDeclaration(node) {
400
+ const name = node.childForFieldName("name")?.text ?? "(anonymous)";
401
+ const id = entityId(sourceFile, name);
402
+ builder.addNode({ id, label: `interface ${name}`, sourceFile, sourceLocation: lineOf(node) });
403
+ builder.addEdge({ source: fileId, target: id, relation: "contains", confidence: "EXTRACTED" });
404
+ nameIndex.set(name, id);
405
+ handleBaseList(id, node);
406
+ }
407
+ function walkTopLevel(node) {
408
+ for (const child of namedChildren(node)) {
409
+ switch (child.type) {
410
+ case "using_directive":
411
+ handleUsingDirective(child);
412
+ break;
413
+ case "class_declaration":
414
+ handleClassDeclaration(child);
415
+ break;
416
+ case "interface_declaration":
417
+ handleInterfaceDeclaration(child);
418
+ break;
419
+ case "namespace_declaration": {
420
+ const nsBody = child.childForFieldName("body");
421
+ if (nsBody) walkTopLevel(nsBody);
422
+ break;
423
+ }
424
+ case "file_scoped_namespace_declaration":
425
+ walkTopLevel(child);
426
+ break;
427
+ default:
428
+ break;
429
+ }
430
+ }
431
+ }
432
+ walkTopLevel(root);
433
+ function closestTrackedCaller(node) {
434
+ let current = node.parent;
435
+ while (current) {
436
+ const tracked = functionNodeMap.get(current.id);
437
+ if (tracked) return tracked;
438
+ current = current.parent;
439
+ }
440
+ return fileId;
441
+ }
442
+ function closestEnclosingClassMethods(node) {
443
+ let current = node.parent;
444
+ while (current) {
445
+ if (current.type === "method_declaration") {
446
+ const typeNodeId = enclosingClassOf.get(current.id);
447
+ if (typeNodeId !== void 0) return classMethods.get(typeNodeId);
448
+ }
449
+ current = current.parent;
450
+ }
451
+ return void 0;
452
+ }
453
+ for (const call of descendantsOfType(root, ["invocation_expression"])) {
454
+ const fn = call.childForFieldName("function");
455
+ if (!fn) continue;
456
+ let targetId = null;
457
+ let confidence = "EXTRACTED";
458
+ if (fn.type === "identifier") {
459
+ const name = fn.text;
460
+ const methodId = closestEnclosingClassMethods(call)?.get(name);
461
+ if (methodId) {
462
+ targetId = methodId;
463
+ } else {
464
+ const viaImport = importIndex.get(name);
465
+ if (viaImport) {
466
+ targetId = viaImport.id;
467
+ confidence = "INFERRED";
468
+ }
469
+ }
470
+ } else if (fn.type === "member_access_expression") {
471
+ const expression = fn.childForFieldName("expression");
472
+ const name = fn.childForFieldName("name")?.text;
473
+ if (expression && name) {
474
+ if (expression.type === "this_expression") {
475
+ const methodId = closestEnclosingClassMethods(call)?.get(name);
476
+ if (methodId) targetId = methodId;
477
+ } else if (expression.type === "identifier") {
478
+ const viaImport = importIndex.get(expression.text);
479
+ if (viaImport) {
480
+ targetId = viaImport.id;
481
+ confidence = "INFERRED";
482
+ }
483
+ }
484
+ }
485
+ }
486
+ if (!targetId) continue;
487
+ const callerId = closestTrackedCaller(call);
488
+ builder.addEdge({ source: callerId, target: targetId, relation: "calls", confidence });
489
+ }
490
+ return builder.build();
491
+ }
492
+
493
+ // src/extractors/go.ts
494
+ var fs3 = __toESM(require("fs/promises"), 1);
495
+ function stripQuotes(text) {
496
+ if (text.length >= 2) {
497
+ const first = text[0];
498
+ const last = text[text.length - 1];
499
+ if ((first === '"' || first === "`") && first === last) {
500
+ return text.slice(1, -1);
501
+ }
502
+ }
503
+ return text;
504
+ }
505
+ function defaultPackageQualifier(importPath) {
506
+ const segments = importPath.split("/");
507
+ return segments[segments.length - 1] ?? importPath;
508
+ }
509
+ async function extractGo(filePath) {
510
+ const parser = await createParser("go");
511
+ const raw = await fs3.readFile(filePath);
512
+ const content = raw.toString("utf-8");
513
+ const tree = parser.parse(content);
514
+ if (!tree) {
515
+ throw new Error(`extractGo: parser produced no tree for ${filePath}`);
516
+ }
517
+ const root = tree.rootNode;
518
+ const sourceFile = toPosix(filePath);
519
+ const builder = new ExtractionBuilder();
520
+ const fileId = sourceFile;
521
+ builder.addNode({ id: fileId, label: sourceFile, sourceFile, sourceLocation: "L1" });
522
+ const nameIndex = /* @__PURE__ */ new Map();
523
+ const functionNodeMap = /* @__PURE__ */ new Map();
524
+ const typeMethods = /* @__PURE__ */ new Map();
525
+ const receiverInfo = /* @__PURE__ */ new Map();
526
+ const importIndex = /* @__PURE__ */ new Map();
527
+ function addModuleNode(ref) {
528
+ if (builder.hasNode(ref.id)) return;
529
+ builder.addNode({
530
+ id: ref.id,
531
+ label: ref.label,
532
+ sourceFile: ref.external ? "<external>" : ref.id,
533
+ sourceLocation: "L1"
534
+ });
535
+ }
536
+ function resolveTypeTarget(name) {
537
+ const resolved = nameIndex.get(name);
538
+ if (resolved) return resolved;
539
+ const extId = externalId(name);
540
+ if (!builder.hasNode(extId)) {
541
+ builder.addNode({ id: extId, label: name, sourceFile: "<external>", sourceLocation: "L0" });
542
+ }
543
+ return extId;
544
+ }
545
+ function handleImportSpec(spec) {
546
+ const pathNode = spec.childForFieldName("path");
547
+ if (!pathNode) return;
548
+ const specifier = stripQuotes(pathNode.text);
549
+ const ref = resolveModuleRef(sourceFile, specifier);
550
+ addModuleNode(ref);
551
+ builder.addEdge({ source: fileId, target: ref.id, relation: "imports", confidence: "EXTRACTED" });
552
+ const aliasNode = spec.childForFieldName("name");
553
+ const alias = aliasNode?.text;
554
+ if (alias && alias !== "_" && alias !== ".") {
555
+ importIndex.set(alias, ref);
556
+ } else if (!alias) {
557
+ importIndex.set(defaultPackageQualifier(specifier), ref);
558
+ }
559
+ }
560
+ function handleImportDeclaration(node) {
561
+ for (const child of namedChildren(node)) {
562
+ if (child.type === "import_spec") {
563
+ handleImportSpec(child);
564
+ } else if (child.type === "import_spec_list") {
565
+ for (const spec of namedChildren(child)) {
566
+ if (spec.type === "import_spec") handleImportSpec(spec);
567
+ }
568
+ }
569
+ }
570
+ }
571
+ function handleFunctionDeclaration(node) {
572
+ const name = node.childForFieldName("name")?.text ?? "(anonymous)";
573
+ const id = entityId(sourceFile, name);
574
+ builder.addNode({ id, label: `function ${name}`, sourceFile, sourceLocation: lineOf(node) });
575
+ builder.addEdge({ source: fileId, target: id, relation: "contains", confidence: "EXTRACTED" });
576
+ nameIndex.set(name, id);
577
+ functionNodeMap.set(node.id, id);
578
+ }
579
+ function handleTypeDeclaration(node) {
580
+ for (const spec of namedChildren(node)) {
581
+ if (spec.type !== "type_spec") continue;
582
+ const name = spec.childForFieldName("name")?.text;
583
+ if (!name) continue;
584
+ const underlying = spec.childForFieldName("type");
585
+ const kind = underlying?.type === "struct_type" ? "struct" : underlying?.type === "interface_type" ? "interface" : "type";
586
+ const id = entityId(sourceFile, name);
587
+ builder.addNode({ id, label: `${kind} ${name}`, sourceFile, sourceLocation: lineOf(spec) });
588
+ builder.addEdge({ source: fileId, target: id, relation: "contains", confidence: "EXTRACTED" });
589
+ nameIndex.set(name, id);
590
+ typeMethods.set(id, /* @__PURE__ */ new Map());
591
+ if (underlying?.type === "struct_type") {
592
+ const fieldList = namedChildren(underlying).find((c) => c.type === "field_declaration_list");
593
+ if (fieldList) {
594
+ for (const field of namedChildren(fieldList)) {
595
+ if (field.type !== "field_declaration") continue;
596
+ if (field.childForFieldName("name")) continue;
597
+ const embeddedType = namedChildren(field)[0];
598
+ if (!embeddedType) continue;
599
+ const base = embeddedType.type === "pointer_type" ? namedChildren(embeddedType)[0] : embeddedType;
600
+ if (!base) continue;
601
+ builder.addEdge({
602
+ source: id,
603
+ target: resolveTypeTarget(base.text),
604
+ relation: "embeds",
605
+ confidence: "EXTRACTED"
606
+ });
607
+ }
608
+ }
609
+ }
610
+ }
611
+ }
612
+ function receiverTypeName(receiver) {
613
+ const paramDecl = namedChildren(receiver)[0];
614
+ if (!paramDecl) return null;
615
+ let typeNode = paramDecl.childForFieldName("type");
616
+ if (typeNode?.type === "pointer_type") {
617
+ typeNode = namedChildren(typeNode)[0] ?? null;
618
+ }
619
+ return typeNode?.type === "type_identifier" ? typeNode.text : null;
620
+ }
621
+ function receiverVarName(receiver) {
622
+ const paramDecl = namedChildren(receiver)[0];
623
+ return paramDecl?.childForFieldName("name")?.text ?? null;
624
+ }
625
+ function handleMethodDeclaration(node) {
626
+ const receiver = node.childForFieldName("receiver");
627
+ const methodName = node.childForFieldName("name")?.text ?? "(anonymous)";
628
+ if (!receiver) return;
629
+ const typeName = receiverTypeName(receiver);
630
+ if (!typeName) return;
631
+ const typeId = resolveTypeTarget(typeName);
632
+ const methodId = entityId(sourceFile, `${typeName}.${methodName}`);
633
+ builder.addNode({ id: methodId, label: `method ${typeName}.${methodName}`, sourceFile, sourceLocation: lineOf(node) });
634
+ builder.addEdge({ source: typeId, target: methodId, relation: "method", confidence: "EXTRACTED" });
635
+ let methods = typeMethods.get(typeId);
636
+ if (!methods) {
637
+ methods = /* @__PURE__ */ new Map();
638
+ typeMethods.set(typeId, methods);
639
+ }
640
+ methods.set(methodName, methodId);
641
+ functionNodeMap.set(node.id, methodId);
642
+ const varName = receiverVarName(receiver);
643
+ if (varName) receiverInfo.set(node.id, { varName, typeId });
644
+ }
645
+ for (const child of namedChildren(root)) {
646
+ switch (child.type) {
647
+ case "import_declaration":
648
+ handleImportDeclaration(child);
649
+ break;
650
+ case "type_declaration":
651
+ handleTypeDeclaration(child);
652
+ break;
653
+ case "function_declaration":
654
+ handleFunctionDeclaration(child);
655
+ break;
656
+ case "method_declaration":
657
+ handleMethodDeclaration(child);
658
+ break;
659
+ default:
660
+ break;
661
+ }
662
+ }
663
+ function closestTrackedCaller(node) {
664
+ let current = node.parent;
665
+ while (current) {
666
+ const tracked = functionNodeMap.get(current.id);
667
+ if (tracked) return tracked;
668
+ current = current.parent;
669
+ }
670
+ return fileId;
671
+ }
672
+ function closestEnclosingReceiver(node) {
673
+ let current = node.parent;
674
+ while (current) {
675
+ if (current.type === "method_declaration") {
676
+ return receiverInfo.get(current.id);
677
+ }
678
+ current = current.parent;
679
+ }
680
+ return void 0;
681
+ }
682
+ for (const call of descendantsOfType(root, ["call_expression"])) {
683
+ const fn = call.childForFieldName("function");
684
+ if (!fn) continue;
685
+ let targetId = null;
686
+ let confidence = "EXTRACTED";
687
+ if (fn.type === "identifier") {
688
+ const sameFile = nameIndex.get(fn.text);
689
+ if (sameFile) targetId = sameFile;
690
+ } else if (fn.type === "selector_expression") {
691
+ const operand = fn.childForFieldName("operand");
692
+ const property = fn.childForFieldName("field")?.text;
693
+ if (operand && property && operand.type === "identifier") {
694
+ const receiver = closestEnclosingReceiver(call);
695
+ if (receiver && receiver.varName === operand.text) {
696
+ const methodId = typeMethods.get(receiver.typeId)?.get(property);
697
+ if (methodId) targetId = methodId;
698
+ } else {
699
+ const viaImport = importIndex.get(operand.text);
700
+ if (viaImport) {
701
+ targetId = viaImport.id;
702
+ confidence = "INFERRED";
703
+ }
704
+ }
705
+ }
706
+ }
707
+ if (!targetId) continue;
708
+ const callerId = closestTrackedCaller(call);
709
+ builder.addEdge({ source: callerId, target: targetId, relation: "calls", confidence });
710
+ }
711
+ return builder.build();
712
+ }
713
+
714
+ // src/extractors/java.ts
715
+ var fs4 = __toESM(require("fs/promises"), 1);
716
+ async function extractJava(filePath) {
717
+ const parser = await createParser("java");
718
+ const raw = await fs4.readFile(filePath);
719
+ const content = raw.toString("utf-8");
720
+ const tree = parser.parse(content);
721
+ if (!tree) {
722
+ throw new Error(`extractJava: parser produced no tree for ${filePath}`);
723
+ }
724
+ const root = tree.rootNode;
725
+ const sourceFile = toPosix(filePath);
726
+ const builder = new ExtractionBuilder();
727
+ const fileId = sourceFile;
728
+ builder.addNode({ id: fileId, label: sourceFile, sourceFile, sourceLocation: "L1" });
729
+ const nameIndex = /* @__PURE__ */ new Map();
730
+ const functionNodeMap = /* @__PURE__ */ new Map();
731
+ const classMethods = /* @__PURE__ */ new Map();
732
+ const enclosingClassOf = /* @__PURE__ */ new Map();
733
+ const importIndex = /* @__PURE__ */ new Map();
734
+ function addModuleNode(ref) {
735
+ if (builder.hasNode(ref.id)) return;
736
+ builder.addNode({
737
+ id: ref.id,
738
+ label: ref.label,
739
+ sourceFile: ref.external ? "<external>" : ref.id,
740
+ sourceLocation: "L1"
741
+ });
742
+ }
743
+ function resolveHeritageTarget(name) {
744
+ const resolved = nameIndex.get(name);
745
+ if (resolved) return resolved;
746
+ const extId = externalId(name);
747
+ if (!builder.hasNode(extId)) {
748
+ builder.addNode({ id: extId, label: name, sourceFile: "<external>", sourceLocation: "L0" });
749
+ }
750
+ return extId;
751
+ }
752
+ function handleImportDeclaration(node) {
753
+ const hasWildcard = namedChildren(node).some((c) => c.type === "asterisk");
754
+ const scoped = namedChildren(node).find((c) => c.type === "scoped_identifier" || c.type === "identifier");
755
+ if (!scoped) return;
756
+ const fullPath = scoped.text;
757
+ const ref = resolveModuleRef(sourceFile, fullPath);
758
+ addModuleNode(ref);
759
+ if (hasWildcard) {
760
+ builder.addEdge({ source: fileId, target: ref.id, relation: "imports", confidence: "EXTRACTED" });
761
+ return;
762
+ }
763
+ const segments = fullPath.split(".");
764
+ const localName = segments[segments.length - 1];
765
+ if (localName) importIndex.set(localName, ref);
766
+ builder.addEdge({ source: fileId, target: ref.id, relation: "imports_from", confidence: "EXTRACTED" });
767
+ }
768
+ function handleMethodDeclaration(classNode, classId, className, member) {
769
+ const methodName = member.childForFieldName("name")?.text ?? "(anonymous)";
770
+ const methodId = entityId(sourceFile, `${className}.${methodName}`);
771
+ builder.addNode({
772
+ id: methodId,
773
+ label: `method ${className}.${methodName}`,
774
+ sourceFile,
775
+ sourceLocation: lineOf(member)
776
+ });
777
+ builder.addEdge({ source: classId, target: methodId, relation: "method", confidence: "EXTRACTED" });
778
+ let methods = classMethods.get(classNode.id);
779
+ if (!methods) {
780
+ methods = /* @__PURE__ */ new Map();
781
+ classMethods.set(classNode.id, methods);
782
+ }
783
+ methods.set(methodName, methodId);
784
+ functionNodeMap.set(member.id, methodId);
785
+ enclosingClassOf.set(member.id, classNode.id);
786
+ }
787
+ function handleClassDeclaration(node) {
788
+ const name = node.childForFieldName("name")?.text ?? "(anonymous)";
789
+ const classId = entityId(sourceFile, name);
790
+ builder.addNode({ id: classId, label: `class ${name}`, sourceFile, sourceLocation: lineOf(node) });
791
+ builder.addEdge({ source: fileId, target: classId, relation: "contains", confidence: "EXTRACTED" });
792
+ nameIndex.set(name, classId);
793
+ functionNodeMap.set(node.id, classId);
794
+ classMethods.set(node.id, /* @__PURE__ */ new Map());
795
+ const superclass = node.childForFieldName("superclass");
796
+ if (superclass) {
797
+ const base = namedChildren(superclass)[0];
798
+ if (base) {
799
+ builder.addEdge({
800
+ source: classId,
801
+ target: resolveHeritageTarget(base.text),
802
+ relation: "inherits",
803
+ confidence: "EXTRACTED"
804
+ });
805
+ }
806
+ }
807
+ const interfaces = node.childForFieldName("interfaces");
808
+ if (interfaces) {
809
+ const typeList = namedChildren(interfaces).find((c) => c.type === "type_list");
810
+ for (const iface of typeList ? namedChildren(typeList) : []) {
811
+ builder.addEdge({
812
+ source: classId,
813
+ target: resolveHeritageTarget(iface.text),
814
+ relation: "implements",
815
+ confidence: "EXTRACTED"
816
+ });
817
+ }
818
+ }
819
+ const body = node.childForFieldName("body");
820
+ if (body) {
821
+ for (const member of namedChildren(body)) {
822
+ if (member.type !== "method_declaration") continue;
823
+ handleMethodDeclaration(node, classId, name, member);
824
+ }
825
+ }
826
+ }
827
+ function handleInterfaceDeclaration(node) {
828
+ const name = node.childForFieldName("name")?.text ?? "(anonymous)";
829
+ const id = entityId(sourceFile, name);
830
+ builder.addNode({ id, label: `interface ${name}`, sourceFile, sourceLocation: lineOf(node) });
831
+ builder.addEdge({ source: fileId, target: id, relation: "contains", confidence: "EXTRACTED" });
832
+ nameIndex.set(name, id);
833
+ const extendsInterfaces = namedChildren(node).find((c) => c.type === "extends_interfaces");
834
+ if (extendsInterfaces) {
835
+ const typeList = namedChildren(extendsInterfaces).find((c) => c.type === "type_list");
836
+ for (const parent of typeList ? namedChildren(typeList) : []) {
837
+ builder.addEdge({
838
+ source: id,
839
+ target: resolveHeritageTarget(parent.text),
840
+ relation: "inherits",
841
+ confidence: "EXTRACTED"
842
+ });
843
+ }
844
+ }
845
+ }
846
+ for (const child of namedChildren(root)) {
847
+ switch (child.type) {
848
+ case "import_declaration":
849
+ handleImportDeclaration(child);
850
+ break;
851
+ case "class_declaration":
852
+ handleClassDeclaration(child);
853
+ break;
854
+ case "interface_declaration":
855
+ handleInterfaceDeclaration(child);
856
+ break;
857
+ default:
858
+ break;
859
+ }
860
+ }
861
+ function closestTrackedCaller(node) {
862
+ let current = node.parent;
863
+ while (current) {
864
+ const tracked = functionNodeMap.get(current.id);
865
+ if (tracked) return tracked;
866
+ current = current.parent;
867
+ }
868
+ return fileId;
869
+ }
870
+ function closestEnclosingClassMethods(node) {
871
+ let current = node.parent;
872
+ while (current) {
873
+ if (current.type === "method_declaration") {
874
+ const classNodeId = enclosingClassOf.get(current.id);
875
+ if (classNodeId !== void 0) return classMethods.get(classNodeId);
876
+ }
877
+ current = current.parent;
878
+ }
879
+ return void 0;
880
+ }
881
+ for (const call of descendantsOfType(root, ["method_invocation"])) {
882
+ const name = call.childForFieldName("name")?.text;
883
+ if (!name) continue;
884
+ const object = call.childForFieldName("object");
885
+ let targetId = null;
886
+ let confidence = "EXTRACTED";
887
+ if (!object) {
888
+ const methodId = closestEnclosingClassMethods(call)?.get(name);
889
+ if (methodId) {
890
+ targetId = methodId;
891
+ } else {
892
+ const viaImport = importIndex.get(name);
893
+ if (viaImport) {
894
+ targetId = viaImport.id;
895
+ confidence = "INFERRED";
896
+ }
897
+ }
898
+ } else if (object.type === "this") {
899
+ const methodId = closestEnclosingClassMethods(call)?.get(name);
900
+ if (methodId) targetId = methodId;
901
+ } else if (object.type === "identifier") {
902
+ const viaImport = importIndex.get(object.text);
903
+ if (viaImport) {
904
+ targetId = viaImport.id;
905
+ confidence = "INFERRED";
906
+ }
907
+ }
908
+ if (!targetId) continue;
909
+ const callerId = closestTrackedCaller(call);
910
+ builder.addEdge({ source: callerId, target: targetId, relation: "calls", confidence });
911
+ }
912
+ return builder.build();
913
+ }
914
+
915
+ // src/extractors/python.ts
916
+ var fs5 = __toESM(require("fs/promises"), 1);
917
+ async function extractPython(filePath) {
918
+ const parser = await createParser("python");
919
+ const raw = await fs5.readFile(filePath);
920
+ const content = raw.toString("utf-8");
921
+ const tree = parser.parse(content);
922
+ if (!tree) {
923
+ throw new Error(`extractPython: parser produced no tree for ${filePath}`);
924
+ }
925
+ const root = tree.rootNode;
926
+ const sourceFile = toPosix(filePath);
927
+ const builder = new ExtractionBuilder();
928
+ const fileId = sourceFile;
929
+ builder.addNode({ id: fileId, label: sourceFile, sourceFile, sourceLocation: "L1" });
930
+ const nameIndex = /* @__PURE__ */ new Map();
931
+ const functionNodeMap = /* @__PURE__ */ new Map();
932
+ const classMethods = /* @__PURE__ */ new Map();
933
+ const enclosingClassOf = /* @__PURE__ */ new Map();
934
+ const importIndex = /* @__PURE__ */ new Map();
935
+ function addModuleNode(ref) {
936
+ if (builder.hasNode(ref.id)) return;
937
+ builder.addNode({
938
+ id: ref.id,
939
+ label: ref.label,
940
+ sourceFile: ref.external ? "<external>" : ref.id,
941
+ sourceLocation: "L1"
942
+ });
943
+ }
944
+ function resolveHeritageTarget(name) {
945
+ const resolved = nameIndex.get(name);
946
+ if (resolved) return resolved;
947
+ const extId = externalId(name);
948
+ if (!builder.hasNode(extId)) {
949
+ builder.addNode({ id: extId, label: name, sourceFile: "<external>", sourceLocation: "L0" });
950
+ }
951
+ return extId;
952
+ }
953
+ function unwrapDecorated(node) {
954
+ if (node.type !== "decorated_definition") return node;
955
+ return namedChildren(node).find((c) => c.type === "function_definition" || c.type === "class_definition") ?? null;
956
+ }
957
+ function relativeSpecifier(relImport) {
958
+ let dots = 0;
959
+ for (const ch of relImport.text) {
960
+ if (ch !== ".") break;
961
+ dots++;
962
+ }
963
+ const dotted = namedChildren(relImport).find((c) => c.type === "dotted_name");
964
+ const up = dots - 1;
965
+ let base = up === 0 ? "." : Array.from({ length: up }, () => "..").join("/");
966
+ if (dotted) {
967
+ base = `${base}/${dotted.text.split(".").join("/")}`;
968
+ }
969
+ return base;
970
+ }
971
+ function moduleSpecifierOf(node) {
972
+ if (node.type === "relative_import") return relativeSpecifier(node);
973
+ return node.text;
974
+ }
975
+ function handleFunctionDefinition(node) {
976
+ const name = node.childForFieldName("name")?.text ?? "(anonymous)";
977
+ const id = entityId(sourceFile, name);
978
+ builder.addNode({ id, label: `function ${name}`, sourceFile, sourceLocation: lineOf(node) });
979
+ builder.addEdge({ source: fileId, target: id, relation: "contains", confidence: "EXTRACTED" });
980
+ nameIndex.set(name, id);
981
+ functionNodeMap.set(node.id, id);
982
+ }
983
+ function handleClassDefinition(node) {
984
+ const name = node.childForFieldName("name")?.text ?? "(anonymous)";
985
+ const classId = entityId(sourceFile, name);
986
+ builder.addNode({ id: classId, label: `class ${name}`, sourceFile, sourceLocation: lineOf(node) });
987
+ builder.addEdge({ source: fileId, target: classId, relation: "contains", confidence: "EXTRACTED" });
988
+ nameIndex.set(name, classId);
989
+ functionNodeMap.set(node.id, classId);
990
+ const superclasses = node.childForFieldName("superclasses");
991
+ if (superclasses) {
992
+ for (const base of namedChildren(superclasses)) {
993
+ if (base.type !== "identifier" && base.type !== "attribute") continue;
994
+ builder.addEdge({
995
+ source: classId,
996
+ target: resolveHeritageTarget(base.text),
997
+ relation: "inherits",
998
+ confidence: "EXTRACTED"
999
+ });
1000
+ }
1001
+ }
1002
+ const methods = /* @__PURE__ */ new Map();
1003
+ classMethods.set(node.id, methods);
1004
+ const body = node.childForFieldName("body");
1005
+ if (body) {
1006
+ for (const rawMember of namedChildren(body)) {
1007
+ const member = unwrapDecorated(rawMember);
1008
+ if (!member || member.type !== "function_definition") continue;
1009
+ const methodName = member.childForFieldName("name")?.text ?? "(anonymous)";
1010
+ const methodId = entityId(sourceFile, `${name}.${methodName}`);
1011
+ builder.addNode({
1012
+ id: methodId,
1013
+ label: `method ${name}.${methodName}`,
1014
+ sourceFile,
1015
+ sourceLocation: lineOf(member)
1016
+ });
1017
+ builder.addEdge({ source: classId, target: methodId, relation: "method", confidence: "EXTRACTED" });
1018
+ methods.set(methodName, methodId);
1019
+ functionNodeMap.set(member.id, methodId);
1020
+ enclosingClassOf.set(member.id, node.id);
1021
+ }
1022
+ }
1023
+ }
1024
+ function handleImportStatement(node) {
1025
+ for (const item of namedChildren(node)) {
1026
+ if (item.type === "dotted_name") {
1027
+ const ref = resolveModuleRef(sourceFile, item.text);
1028
+ addModuleNode(ref);
1029
+ const bindingName = namedChildren(item)[0]?.text ?? item.text;
1030
+ importIndex.set(bindingName, ref);
1031
+ builder.addEdge({ source: fileId, target: ref.id, relation: "imports", confidence: "EXTRACTED" });
1032
+ } else if (item.type === "aliased_import") {
1033
+ const dotted = item.childForFieldName("name");
1034
+ const alias = item.childForFieldName("alias")?.text;
1035
+ if (!dotted || !alias) continue;
1036
+ const ref = resolveModuleRef(sourceFile, dotted.text);
1037
+ addModuleNode(ref);
1038
+ importIndex.set(alias, ref);
1039
+ builder.addEdge({ source: fileId, target: ref.id, relation: "imports", confidence: "EXTRACTED" });
1040
+ }
1041
+ }
1042
+ }
1043
+ function handleImportFromStatement(node) {
1044
+ const moduleNode = node.childForFieldName("module_name");
1045
+ if (!moduleNode) return;
1046
+ const ref = resolveModuleRef(sourceFile, moduleSpecifierOf(moduleNode));
1047
+ addModuleNode(ref);
1048
+ let sawItem = false;
1049
+ for (const child of namedChildren(node)) {
1050
+ if (child.id === moduleNode.id) continue;
1051
+ if (child.type === "dotted_name") {
1052
+ sawItem = true;
1053
+ importIndex.set(child.text, ref);
1054
+ } else if (child.type === "aliased_import") {
1055
+ sawItem = true;
1056
+ const alias = child.childForFieldName("alias")?.text;
1057
+ const name = child.childForFieldName("name")?.text;
1058
+ if (alias) importIndex.set(alias, ref);
1059
+ else if (name) importIndex.set(name, ref);
1060
+ } else if (child.type === "wildcard_import") {
1061
+ sawItem = true;
1062
+ }
1063
+ }
1064
+ if (sawItem) {
1065
+ builder.addEdge({ source: fileId, target: ref.id, relation: "imports_from", confidence: "EXTRACTED" });
1066
+ }
1067
+ }
1068
+ for (const rawChild of namedChildren(root)) {
1069
+ if (rawChild.type === "import_statement") {
1070
+ handleImportStatement(rawChild);
1071
+ continue;
1072
+ }
1073
+ if (rawChild.type === "import_from_statement") {
1074
+ handleImportFromStatement(rawChild);
1075
+ continue;
1076
+ }
1077
+ const effective = unwrapDecorated(rawChild);
1078
+ if (!effective) continue;
1079
+ switch (effective.type) {
1080
+ case "function_definition":
1081
+ handleFunctionDefinition(effective);
1082
+ break;
1083
+ case "class_definition":
1084
+ handleClassDefinition(effective);
1085
+ break;
1086
+ default:
1087
+ break;
1088
+ }
1089
+ }
1090
+ function closestTrackedCaller(node) {
1091
+ let current = node.parent;
1092
+ while (current) {
1093
+ const tracked = functionNodeMap.get(current.id);
1094
+ if (tracked) return tracked;
1095
+ current = current.parent;
1096
+ }
1097
+ return fileId;
1098
+ }
1099
+ function closestEnclosingClassMethods(node) {
1100
+ let current = node.parent;
1101
+ while (current) {
1102
+ if (current.type === "function_definition") {
1103
+ const classNodeId = enclosingClassOf.get(current.id);
1104
+ if (classNodeId !== void 0) return classMethods.get(classNodeId);
1105
+ }
1106
+ current = current.parent;
1107
+ }
1108
+ return void 0;
1109
+ }
1110
+ for (const call of descendantsOfType(root, ["call"])) {
1111
+ const fn = call.childForFieldName("function");
1112
+ if (!fn) continue;
1113
+ let targetId = null;
1114
+ let confidence = "EXTRACTED";
1115
+ if (fn.type === "identifier") {
1116
+ const name = fn.text;
1117
+ const sameFile = nameIndex.get(name);
1118
+ if (sameFile) {
1119
+ targetId = sameFile;
1120
+ } else {
1121
+ const viaImport = importIndex.get(name);
1122
+ if (viaImport) {
1123
+ targetId = viaImport.id;
1124
+ confidence = "INFERRED";
1125
+ }
1126
+ }
1127
+ } else if (fn.type === "attribute") {
1128
+ const object = fn.childForFieldName("object");
1129
+ const property = fn.childForFieldName("attribute")?.text;
1130
+ if (object && property) {
1131
+ if (object.type === "identifier" && object.text === "self") {
1132
+ const methodId = closestEnclosingClassMethods(call)?.get(property);
1133
+ if (methodId) targetId = methodId;
1134
+ } else if (object.type === "identifier") {
1135
+ const viaImport = importIndex.get(object.text);
1136
+ if (viaImport) {
1137
+ targetId = viaImport.id;
1138
+ confidence = "INFERRED";
1139
+ }
1140
+ }
1141
+ }
1142
+ }
1143
+ if (!targetId) continue;
1144
+ const callerId = closestTrackedCaller(call);
1145
+ builder.addEdge({ source: callerId, target: targetId, relation: "calls", confidence });
1146
+ }
1147
+ return builder.build();
1148
+ }
1149
+
1150
+ // src/extractors/ruby.ts
1151
+ var fs6 = __toESM(require("fs/promises"), 1);
1152
+ function conventionalConstantName(specifier) {
1153
+ const lastSegment = specifier.split("/").filter(Boolean).pop() ?? specifier;
1154
+ return lastSegment.split("_").filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join("");
1155
+ }
1156
+ function stringLiteralContent(node) {
1157
+ if (node.type !== "string") return null;
1158
+ const content = namedChildren(node).find((c) => c.type === "string_content");
1159
+ return content ? content.text : "";
1160
+ }
1161
+ async function extractRuby(filePath) {
1162
+ const parser = await createParser("ruby");
1163
+ const raw = await fs6.readFile(filePath);
1164
+ const content = raw.toString("utf-8");
1165
+ const tree = parser.parse(content);
1166
+ if (!tree) {
1167
+ throw new Error(`extractRuby: parser produced no tree for ${filePath}`);
1168
+ }
1169
+ const root = tree.rootNode;
1170
+ const sourceFile = toPosix(filePath);
1171
+ const builder = new ExtractionBuilder();
1172
+ const fileId = sourceFile;
1173
+ builder.addNode({ id: fileId, label: sourceFile, sourceFile, sourceLocation: "L1" });
1174
+ const nameIndex = /* @__PURE__ */ new Map();
1175
+ const functionNodeMap = /* @__PURE__ */ new Map();
1176
+ const typeMethods = /* @__PURE__ */ new Map();
1177
+ const enclosingTypeOf = /* @__PURE__ */ new Map();
1178
+ const importIndex = /* @__PURE__ */ new Map();
1179
+ function addModuleNode(ref) {
1180
+ if (builder.hasNode(ref.id)) return;
1181
+ builder.addNode({
1182
+ id: ref.id,
1183
+ label: ref.label,
1184
+ sourceFile: ref.external ? "<external>" : ref.id,
1185
+ sourceLocation: "L1"
1186
+ });
1187
+ }
1188
+ function resolveHeritageTarget(name) {
1189
+ const resolved = nameIndex.get(name);
1190
+ if (resolved) return resolved;
1191
+ const extId = externalId(name);
1192
+ if (!builder.hasNode(extId)) {
1193
+ builder.addNode({ id: extId, label: name, sourceFile: "<external>", sourceLocation: "L0" });
1194
+ }
1195
+ return extId;
1196
+ }
1197
+ function handleTopLevelMethod(node) {
1198
+ const name = node.childForFieldName("name")?.text ?? "(anonymous)";
1199
+ const id = entityId(sourceFile, name);
1200
+ builder.addNode({ id, label: `function ${name}`, sourceFile, sourceLocation: lineOf(node) });
1201
+ builder.addEdge({ source: fileId, target: id, relation: "contains", confidence: "EXTRACTED" });
1202
+ nameIndex.set(name, id);
1203
+ functionNodeMap.set(node.id, id);
1204
+ }
1205
+ function handleClassOrModule(node, kind) {
1206
+ const name = node.childForFieldName("name")?.text ?? "(anonymous)";
1207
+ const id = entityId(sourceFile, name);
1208
+ builder.addNode({ id, label: `${kind} ${name}`, sourceFile, sourceLocation: lineOf(node) });
1209
+ builder.addEdge({ source: fileId, target: id, relation: "contains", confidence: "EXTRACTED" });
1210
+ nameIndex.set(name, id);
1211
+ typeMethods.set(id, /* @__PURE__ */ new Map());
1212
+ if (kind === "class") {
1213
+ const superclass = node.childForFieldName("superclass");
1214
+ const base = superclass ? namedChildren(superclass)[0] : void 0;
1215
+ if (base) {
1216
+ builder.addEdge({
1217
+ source: id,
1218
+ target: resolveHeritageTarget(base.text),
1219
+ relation: "inherits",
1220
+ confidence: "EXTRACTED"
1221
+ });
1222
+ }
1223
+ }
1224
+ const body = node.childForFieldName("body");
1225
+ if (!body) return;
1226
+ const methods = typeMethods.get(id);
1227
+ for (const member of namedChildren(body)) {
1228
+ if (member.type === "method") {
1229
+ const methodName = member.childForFieldName("name")?.text ?? "(anonymous)";
1230
+ const methodId = entityId(sourceFile, `${name}.${methodName}`);
1231
+ builder.addNode({
1232
+ id: methodId,
1233
+ label: `method ${name}.${methodName}`,
1234
+ sourceFile,
1235
+ sourceLocation: lineOf(member)
1236
+ });
1237
+ builder.addEdge({ source: id, target: methodId, relation: "method", confidence: "EXTRACTED" });
1238
+ methods?.set(methodName, methodId);
1239
+ functionNodeMap.set(member.id, methodId);
1240
+ enclosingTypeOf.set(member.id, id);
1241
+ } else if (member.type === "call") {
1242
+ const methodField = member.childForFieldName("method")?.text;
1243
+ if (methodField !== "include" && methodField !== "extend" && methodField !== "prepend") continue;
1244
+ const args = member.childForFieldName("arguments");
1245
+ for (const arg of args ? namedChildren(args) : []) {
1246
+ if (arg.type !== "constant" && arg.type !== "scoped_constant") continue;
1247
+ builder.addEdge({
1248
+ source: id,
1249
+ target: resolveHeritageTarget(arg.text),
1250
+ relation: "mixes_in",
1251
+ confidence: "EXTRACTED"
1252
+ });
1253
+ }
1254
+ }
1255
+ }
1256
+ }
1257
+ for (const child of namedChildren(root)) {
1258
+ switch (child.type) {
1259
+ case "method":
1260
+ handleTopLevelMethod(child);
1261
+ break;
1262
+ case "class":
1263
+ handleClassOrModule(child, "class");
1264
+ break;
1265
+ case "module":
1266
+ handleClassOrModule(child, "module");
1267
+ break;
1268
+ default:
1269
+ break;
1270
+ }
1271
+ }
1272
+ function closestTrackedCaller(node) {
1273
+ let current = node.parent;
1274
+ while (current) {
1275
+ const tracked = functionNodeMap.get(current.id);
1276
+ if (tracked) return tracked;
1277
+ current = current.parent;
1278
+ }
1279
+ return fileId;
1280
+ }
1281
+ function closestEnclosingTypeMethods(node) {
1282
+ let current = node.parent;
1283
+ while (current) {
1284
+ if (current.type === "method") {
1285
+ const typeId = enclosingTypeOf.get(current.id);
1286
+ if (typeId !== void 0) return typeMethods.get(typeId);
1287
+ }
1288
+ current = current.parent;
1289
+ }
1290
+ return void 0;
1291
+ }
1292
+ const allCalls = descendantsOfType(root, ["call"]);
1293
+ const requireCallIds = /* @__PURE__ */ new Set();
1294
+ for (const call of allCalls) {
1295
+ const methodName = call.childForFieldName("method")?.text;
1296
+ if (methodName !== "require" && methodName !== "require_relative") continue;
1297
+ const args = call.childForFieldName("arguments");
1298
+ const soleArg = args ? namedChildren(args) : [];
1299
+ if (soleArg.length !== 1) continue;
1300
+ const literal = stringLiteralContent(soleArg[0]);
1301
+ if (literal === null) continue;
1302
+ requireCallIds.add(call.id);
1303
+ const ref = resolveModuleRef(sourceFile, literal);
1304
+ addModuleNode(ref);
1305
+ builder.addEdge({ source: fileId, target: ref.id, relation: "imports", confidence: "EXTRACTED" });
1306
+ importIndex.set(conventionalConstantName(literal), ref);
1307
+ }
1308
+ for (const call of allCalls) {
1309
+ if (requireCallIds.has(call.id)) continue;
1310
+ const methodName = call.childForFieldName("method")?.text;
1311
+ if (!methodName) continue;
1312
+ const receiver = call.childForFieldName("receiver");
1313
+ let targetId = null;
1314
+ let confidence = "EXTRACTED";
1315
+ if (!receiver) {
1316
+ const viaEnclosingType = closestEnclosingTypeMethods(call)?.get(methodName);
1317
+ if (viaEnclosingType) {
1318
+ targetId = viaEnclosingType;
1319
+ } else {
1320
+ const sameFile = nameIndex.get(methodName);
1321
+ if (sameFile) targetId = sameFile;
1322
+ }
1323
+ } else if (receiver.type === "self") {
1324
+ const methodId = closestEnclosingTypeMethods(call)?.get(methodName);
1325
+ if (methodId) targetId = methodId;
1326
+ } else if (receiver.type === "constant" || receiver.type === "scoped_constant") {
1327
+ const sameFileTypeId = nameIndex.get(receiver.text);
1328
+ const methodId = sameFileTypeId ? typeMethods.get(sameFileTypeId)?.get(methodName) : void 0;
1329
+ if (methodId) {
1330
+ targetId = methodId;
1331
+ } else {
1332
+ const viaImport = importIndex.get(receiver.text);
1333
+ if (viaImport) {
1334
+ targetId = viaImport.id;
1335
+ confidence = "INFERRED";
1336
+ }
1337
+ }
1338
+ }
1339
+ if (!targetId) continue;
1340
+ const callerId = closestTrackedCaller(call);
1341
+ builder.addEdge({ source: callerId, target: targetId, relation: "calls", confidence });
1342
+ }
1343
+ return builder.build();
1344
+ }
1345
+
1346
+ // src/extractors/rust.ts
1347
+ var fs7 = __toESM(require("fs/promises"), 1);
1348
+ function convertUsePath(segments) {
1349
+ const first = segments[0];
1350
+ if (first === "crate" || first === "self") {
1351
+ const rest = segments.slice(1);
1352
+ return rest.length ? `./${rest.join("/")}` : ".";
1353
+ }
1354
+ if (first === "super") {
1355
+ let i = 0;
1356
+ while (segments[i] === "super") i++;
1357
+ const ups = Array.from({ length: i }, () => "..").join("/");
1358
+ const rest = segments.slice(i);
1359
+ return rest.length ? `${ups}/${rest.join("/")}` : ups;
1360
+ }
1361
+ return segments.join("::");
1362
+ }
1363
+ async function extractRust(filePath) {
1364
+ const parser = await createParser("rust");
1365
+ const raw = await fs7.readFile(filePath);
1366
+ const content = raw.toString("utf-8");
1367
+ const tree = parser.parse(content);
1368
+ if (!tree) {
1369
+ throw new Error(`extractRust: parser produced no tree for ${filePath}`);
1370
+ }
1371
+ const root = tree.rootNode;
1372
+ const sourceFile = toPosix(filePath);
1373
+ const builder = new ExtractionBuilder();
1374
+ const fileId = sourceFile;
1375
+ builder.addNode({ id: fileId, label: sourceFile, sourceFile, sourceLocation: "L1" });
1376
+ const nameIndex = /* @__PURE__ */ new Map();
1377
+ const functionNodeMap = /* @__PURE__ */ new Map();
1378
+ const typeMethods = /* @__PURE__ */ new Map();
1379
+ const enclosingTypeOf = /* @__PURE__ */ new Map();
1380
+ const importIndex = /* @__PURE__ */ new Map();
1381
+ function addModuleNode(ref) {
1382
+ if (builder.hasNode(ref.id)) return;
1383
+ builder.addNode({
1384
+ id: ref.id,
1385
+ label: ref.label,
1386
+ sourceFile: ref.external ? "<external>" : ref.id,
1387
+ sourceLocation: "L1"
1388
+ });
1389
+ }
1390
+ function resolveTypeTarget(name) {
1391
+ const resolved = nameIndex.get(name);
1392
+ if (resolved) return resolved;
1393
+ const extId = externalId(name);
1394
+ if (!builder.hasNode(extId)) {
1395
+ builder.addNode({ id: extId, label: name, sourceFile: "<external>", sourceLocation: "L0" });
1396
+ }
1397
+ return extId;
1398
+ }
1399
+ function processUseArgument(node) {
1400
+ switch (node.type) {
1401
+ case "identifier": {
1402
+ const ref = resolveModuleRef(sourceFile, convertUsePath([node.text]));
1403
+ addModuleNode(ref);
1404
+ importIndex.set(node.text, ref);
1405
+ builder.addEdge({ source: fileId, target: ref.id, relation: "imports", confidence: "EXTRACTED" });
1406
+ break;
1407
+ }
1408
+ case "scoped_identifier": {
1409
+ const pathNode = node.childForFieldName("path");
1410
+ const nameNode = node.childForFieldName("name");
1411
+ if (!pathNode || !nameNode) break;
1412
+ const ref = resolveModuleRef(sourceFile, convertUsePath(pathNode.text.split("::")));
1413
+ addModuleNode(ref);
1414
+ importIndex.set(nameNode.text, ref);
1415
+ builder.addEdge({ source: fileId, target: ref.id, relation: "imports_from", confidence: "EXTRACTED" });
1416
+ break;
1417
+ }
1418
+ case "use_as_clause": {
1419
+ const pathNode = node.childForFieldName("path");
1420
+ const aliasNode = node.childForFieldName("alias");
1421
+ if (!pathNode || !aliasNode) break;
1422
+ const ref = resolveModuleRef(sourceFile, convertUsePath(pathNode.text.split("::")));
1423
+ addModuleNode(ref);
1424
+ importIndex.set(aliasNode.text, ref);
1425
+ builder.addEdge({ source: fileId, target: ref.id, relation: "imports", confidence: "EXTRACTED" });
1426
+ break;
1427
+ }
1428
+ case "scoped_use_list": {
1429
+ const pathNode = node.childForFieldName("path");
1430
+ const listNode = node.childForFieldName("list");
1431
+ if (!listNode) break;
1432
+ const prefixSegments = pathNode ? pathNode.text.split("::") : [];
1433
+ const ref = resolveModuleRef(sourceFile, convertUsePath(prefixSegments));
1434
+ addModuleNode(ref);
1435
+ let sawItem = false;
1436
+ for (const item of namedChildren(listNode)) {
1437
+ sawItem = true;
1438
+ if (item.type === "identifier") {
1439
+ importIndex.set(item.text, ref);
1440
+ } else if (item.type === "use_as_clause") {
1441
+ const alias = item.childForFieldName("alias")?.text;
1442
+ if (alias) importIndex.set(alias, ref);
1443
+ }
1444
+ }
1445
+ if (sawItem) {
1446
+ builder.addEdge({ source: fileId, target: ref.id, relation: "imports_from", confidence: "EXTRACTED" });
1447
+ }
1448
+ break;
1449
+ }
1450
+ default:
1451
+ break;
1452
+ }
1453
+ }
1454
+ function handleStructItem(node) {
1455
+ const name = node.childForFieldName("name")?.text ?? "(anonymous)";
1456
+ const id = entityId(sourceFile, name);
1457
+ builder.addNode({ id, label: `struct ${name}`, sourceFile, sourceLocation: lineOf(node) });
1458
+ builder.addEdge({ source: fileId, target: id, relation: "contains", confidence: "EXTRACTED" });
1459
+ nameIndex.set(name, id);
1460
+ typeMethods.set(id, /* @__PURE__ */ new Map());
1461
+ }
1462
+ function handleTraitItem(node) {
1463
+ const name = node.childForFieldName("name")?.text ?? "(anonymous)";
1464
+ const id = entityId(sourceFile, name);
1465
+ builder.addNode({ id, label: `trait ${name}`, sourceFile, sourceLocation: lineOf(node) });
1466
+ builder.addEdge({ source: fileId, target: id, relation: "contains", confidence: "EXTRACTED" });
1467
+ nameIndex.set(name, id);
1468
+ }
1469
+ function handleFunctionItem(node) {
1470
+ const name = node.childForFieldName("name")?.text ?? "(anonymous)";
1471
+ const id = entityId(sourceFile, name);
1472
+ builder.addNode({ id, label: `function ${name}`, sourceFile, sourceLocation: lineOf(node) });
1473
+ builder.addEdge({ source: fileId, target: id, relation: "contains", confidence: "EXTRACTED" });
1474
+ nameIndex.set(name, id);
1475
+ functionNodeMap.set(node.id, id);
1476
+ }
1477
+ function handleImplItem(node) {
1478
+ const typeName = node.childForFieldName("type")?.text;
1479
+ if (!typeName) return;
1480
+ const typeId = resolveTypeTarget(typeName);
1481
+ const traitName = node.childForFieldName("trait")?.text;
1482
+ if (traitName) {
1483
+ builder.addEdge({
1484
+ source: typeId,
1485
+ target: resolveTypeTarget(traitName),
1486
+ relation: "implements",
1487
+ confidence: "EXTRACTED"
1488
+ });
1489
+ }
1490
+ let methods = typeMethods.get(typeId);
1491
+ if (!methods) {
1492
+ methods = /* @__PURE__ */ new Map();
1493
+ typeMethods.set(typeId, methods);
1494
+ }
1495
+ const body = node.childForFieldName("body");
1496
+ if (!body) return;
1497
+ for (const member of namedChildren(body)) {
1498
+ if (member.type !== "function_item") continue;
1499
+ const methodName = member.childForFieldName("name")?.text ?? "(anonymous)";
1500
+ const methodId = entityId(sourceFile, `${typeName}.${methodName}`);
1501
+ builder.addNode({
1502
+ id: methodId,
1503
+ label: `method ${typeName}.${methodName}`,
1504
+ sourceFile,
1505
+ sourceLocation: lineOf(member)
1506
+ });
1507
+ builder.addEdge({ source: typeId, target: methodId, relation: "method", confidence: "EXTRACTED" });
1508
+ methods.set(methodName, methodId);
1509
+ functionNodeMap.set(member.id, methodId);
1510
+ enclosingTypeOf.set(member.id, typeId);
1511
+ }
1512
+ }
1513
+ const implNodes = [];
1514
+ for (const child of namedChildren(root)) {
1515
+ switch (child.type) {
1516
+ case "use_declaration": {
1517
+ const arg = child.childForFieldName("argument");
1518
+ if (arg) processUseArgument(arg);
1519
+ break;
1520
+ }
1521
+ case "struct_item":
1522
+ handleStructItem(child);
1523
+ break;
1524
+ case "trait_item":
1525
+ handleTraitItem(child);
1526
+ break;
1527
+ case "function_item":
1528
+ handleFunctionItem(child);
1529
+ break;
1530
+ case "impl_item":
1531
+ implNodes.push(child);
1532
+ break;
1533
+ default:
1534
+ break;
1535
+ }
1536
+ }
1537
+ for (const impl of implNodes) handleImplItem(impl);
1538
+ function closestTrackedCaller(node) {
1539
+ let current = node.parent;
1540
+ while (current) {
1541
+ const tracked = functionNodeMap.get(current.id);
1542
+ if (tracked) return tracked;
1543
+ current = current.parent;
1544
+ }
1545
+ return fileId;
1546
+ }
1547
+ function closestEnclosingTypeMethods(node) {
1548
+ let current = node.parent;
1549
+ while (current) {
1550
+ if (current.type === "function_item") {
1551
+ const typeId = enclosingTypeOf.get(current.id);
1552
+ if (typeId !== void 0) return typeMethods.get(typeId);
1553
+ }
1554
+ current = current.parent;
1555
+ }
1556
+ return void 0;
1557
+ }
1558
+ for (const call of descendantsOfType(root, ["call_expression"])) {
1559
+ const fn = call.childForFieldName("function");
1560
+ if (!fn) continue;
1561
+ let targetId = null;
1562
+ let confidence = "EXTRACTED";
1563
+ if (fn.type === "identifier") {
1564
+ const name = fn.text;
1565
+ const sameFile = nameIndex.get(name);
1566
+ if (sameFile) {
1567
+ targetId = sameFile;
1568
+ } else {
1569
+ const viaImport = importIndex.get(name);
1570
+ if (viaImport) {
1571
+ targetId = viaImport.id;
1572
+ confidence = "INFERRED";
1573
+ }
1574
+ }
1575
+ } else if (fn.type === "field_expression") {
1576
+ const value = fn.childForFieldName("value");
1577
+ const field = fn.childForFieldName("field")?.text;
1578
+ if (value && field) {
1579
+ if (value.type === "self") {
1580
+ const methodId = closestEnclosingTypeMethods(call)?.get(field);
1581
+ if (methodId) targetId = methodId;
1582
+ }
1583
+ }
1584
+ } else if (fn.type === "scoped_identifier") {
1585
+ const path8 = fn.childForFieldName("path")?.text;
1586
+ const name = fn.childForFieldName("name")?.text;
1587
+ if (path8 && name) {
1588
+ const viaImport = importIndex.get(path8);
1589
+ if (viaImport) {
1590
+ targetId = viaImport.id;
1591
+ confidence = "INFERRED";
1592
+ }
1593
+ }
1594
+ }
1595
+ if (!targetId) continue;
1596
+ const callerId = closestTrackedCaller(call);
1597
+ builder.addEdge({ source: callerId, target: targetId, relation: "calls", confidence });
1598
+ }
1599
+ return builder.build();
1600
+ }
1601
+
1602
+ // src/extractors/typescript.ts
1603
+ var fs8 = __toESM(require("fs/promises"), 1);
1604
+ var path3 = __toESM(require("path"), 1);
1605
+ function grammarForExtension(ext) {
1606
+ const lower = ext.toLowerCase();
1607
+ if (lower === ".tsx") return "tsx";
1608
+ if (lower === ".jsx") return "javascript";
1609
+ if (lower === ".ts" || lower === ".mts" || lower === ".cts") return "typescript";
1610
+ return "javascript";
1611
+ }
1612
+ function stripQuotes2(text) {
1613
+ if (text.length >= 2) {
1614
+ const first = text[0];
1615
+ const last = text[text.length - 1];
1616
+ if ((first === '"' || first === "'" || first === "`") && first === last) {
1617
+ return text.slice(1, -1);
1618
+ }
1619
+ }
1620
+ return text;
1621
+ }
1622
+ function firstStringArgument(call) {
1623
+ const args = call.childForFieldName("arguments");
1624
+ if (!args) return null;
1625
+ const first = namedChildren(args)[0];
1626
+ if (!first || first.type !== "string") return null;
1627
+ return stripQuotes2(first.text);
1628
+ }
1629
+ async function extractTypeScript(filePath) {
1630
+ const ext = path3.extname(filePath);
1631
+ const grammar = grammarForExtension(ext);
1632
+ const parser = await createParser(grammar);
1633
+ const raw = await fs8.readFile(filePath);
1634
+ const content = raw.toString("utf-8");
1635
+ const tree = parser.parse(content);
1636
+ if (!tree) {
1637
+ throw new Error(`extractTypeScript: parser produced no tree for ${filePath}`);
1638
+ }
1639
+ const root = tree.rootNode;
1640
+ const sourceFile = toPosix(filePath);
1641
+ const builder = new ExtractionBuilder();
1642
+ const fileId = sourceFile;
1643
+ builder.addNode({ id: fileId, label: sourceFile, sourceFile, sourceLocation: "L1" });
1644
+ const nameIndex = /* @__PURE__ */ new Map();
1645
+ const functionNodeMap = /* @__PURE__ */ new Map();
1646
+ const classMethods = /* @__PURE__ */ new Map();
1647
+ const enclosingClassOf = /* @__PURE__ */ new Map();
1648
+ const importIndex = /* @__PURE__ */ new Map();
1649
+ function addModuleNode(ref) {
1650
+ if (builder.hasNode(ref.id)) return;
1651
+ builder.addNode({
1652
+ id: ref.id,
1653
+ label: ref.label,
1654
+ sourceFile: ref.external ? "<external>" : ref.id,
1655
+ sourceLocation: "L1"
1656
+ });
1657
+ }
1658
+ function importTargetId(binding, property) {
1659
+ if (!binding.ref.external) {
1660
+ if (binding.kind === "named" && binding.importedName) {
1661
+ return entityId(binding.ref.id, binding.importedName);
1662
+ }
1663
+ if (binding.kind === "namespace" && property) {
1664
+ return entityId(binding.ref.id, property);
1665
+ }
1666
+ }
1667
+ return binding.ref.id;
1668
+ }
1669
+ function resolveHeritageTarget(name) {
1670
+ const resolved = nameIndex.get(name);
1671
+ if (resolved) return resolved;
1672
+ const extId = externalId(name);
1673
+ if (!builder.hasNode(extId)) {
1674
+ builder.addNode({ id: extId, label: name, sourceFile: "<external>", sourceLocation: "L0" });
1675
+ }
1676
+ return extId;
1677
+ }
1678
+ function handleFunctionDeclaration(node) {
1679
+ const name = node.childForFieldName("name")?.text ?? "(anonymous)";
1680
+ const id = entityId(sourceFile, name);
1681
+ builder.addNode({ id, label: `function ${name}`, sourceFile, sourceLocation: lineOf(node) });
1682
+ builder.addEdge({ source: fileId, target: id, relation: "contains", confidence: "EXTRACTED" });
1683
+ nameIndex.set(name, id);
1684
+ functionNodeMap.set(node.id, id);
1685
+ }
1686
+ function registerRequireBinding(nameNode, ref) {
1687
+ if (nameNode.type === "identifier") {
1688
+ importIndex.set(nameNode.text, { ref, kind: "namespace" });
1689
+ return;
1690
+ }
1691
+ if (nameNode.type === "object_pattern") {
1692
+ for (const prop of namedChildren(nameNode)) {
1693
+ if (prop.type === "shorthand_property_identifier_pattern") {
1694
+ importIndex.set(prop.text, { ref, importedName: prop.text, kind: "named" });
1695
+ } else if (prop.type === "pair_pattern") {
1696
+ const key = prop.childForFieldName("key")?.text;
1697
+ const value = prop.childForFieldName("value")?.text;
1698
+ if (key && value) importIndex.set(value, { ref, importedName: key, kind: "named" });
1699
+ }
1700
+ }
1701
+ }
1702
+ }
1703
+ function handleTopLevelVariableFunctions(node) {
1704
+ for (const declarator of namedChildren(node)) {
1705
+ if (declarator.type !== "variable_declarator") continue;
1706
+ const value = declarator.childForFieldName("value");
1707
+ if (!value) continue;
1708
+ if (value.type === "arrow_function" || value.type === "function_expression") {
1709
+ const name = declarator.childForFieldName("name")?.text;
1710
+ if (!name) continue;
1711
+ const id = entityId(sourceFile, name);
1712
+ builder.addNode({ id, label: `function ${name}`, sourceFile, sourceLocation: lineOf(declarator) });
1713
+ builder.addEdge({ source: fileId, target: id, relation: "contains", confidence: "EXTRACTED" });
1714
+ nameIndex.set(name, id);
1715
+ functionNodeMap.set(value.id, id);
1716
+ continue;
1717
+ }
1718
+ if (value.type === "call_expression") {
1719
+ const callee = value.childForFieldName("function");
1720
+ const specifier = callee?.type === "identifier" && callee.text === "require" ? firstStringArgument(value) : null;
1721
+ if (specifier) {
1722
+ const nameNode = declarator.childForFieldName("name");
1723
+ if (nameNode) {
1724
+ const ref = resolveModuleRef(sourceFile, specifier);
1725
+ addModuleNode(ref);
1726
+ registerRequireBinding(nameNode, ref);
1727
+ }
1728
+ }
1729
+ }
1730
+ }
1731
+ }
1732
+ function handleInterfaceDeclaration(node) {
1733
+ const name = node.childForFieldName("name")?.text ?? "(anonymous)";
1734
+ const id = entityId(sourceFile, name);
1735
+ builder.addNode({ id, label: `interface ${name}`, sourceFile, sourceLocation: lineOf(node) });
1736
+ builder.addEdge({ source: fileId, target: id, relation: "contains", confidence: "EXTRACTED" });
1737
+ nameIndex.set(name, id);
1738
+ }
1739
+ function handleClassDeclaration(node) {
1740
+ const name = node.childForFieldName("name")?.text ?? "(anonymous)";
1741
+ const classId = entityId(sourceFile, name);
1742
+ builder.addNode({ id: classId, label: `class ${name}`, sourceFile, sourceLocation: lineOf(node) });
1743
+ builder.addEdge({ source: fileId, target: classId, relation: "contains", confidence: "EXTRACTED" });
1744
+ nameIndex.set(name, classId);
1745
+ functionNodeMap.set(node.id, classId);
1746
+ const heritage = namedChildren(node).find((c) => c.type === "class_heritage");
1747
+ if (heritage) {
1748
+ for (const clause of namedChildren(heritage)) {
1749
+ if (clause.type === "extends_clause") {
1750
+ const base = clause.childForFieldName("value")?.text;
1751
+ if (base) {
1752
+ builder.addEdge({
1753
+ source: classId,
1754
+ target: resolveHeritageTarget(base),
1755
+ relation: "inherits",
1756
+ confidence: "EXTRACTED"
1757
+ });
1758
+ }
1759
+ } else if (clause.type === "implements_clause") {
1760
+ for (const iface of namedChildren(clause)) {
1761
+ builder.addEdge({
1762
+ source: classId,
1763
+ target: resolveHeritageTarget(iface.text),
1764
+ relation: "implements",
1765
+ confidence: "EXTRACTED"
1766
+ });
1767
+ }
1768
+ }
1769
+ }
1770
+ }
1771
+ const methods = /* @__PURE__ */ new Map();
1772
+ classMethods.set(node.id, methods);
1773
+ const body = node.childForFieldName("body");
1774
+ if (body) {
1775
+ for (const member of namedChildren(body)) {
1776
+ if (member.type !== "method_definition") continue;
1777
+ const methodName = member.childForFieldName("name")?.text ?? "(anonymous)";
1778
+ const methodId = entityId(sourceFile, `${name}.${methodName}`);
1779
+ builder.addNode({
1780
+ id: methodId,
1781
+ label: `method ${name}.${methodName}`,
1782
+ sourceFile,
1783
+ sourceLocation: lineOf(member)
1784
+ });
1785
+ builder.addEdge({ source: classId, target: methodId, relation: "method", confidence: "EXTRACTED" });
1786
+ methods.set(methodName, methodId);
1787
+ functionNodeMap.set(member.id, methodId);
1788
+ enclosingClassOf.set(member.id, node.id);
1789
+ }
1790
+ }
1791
+ }
1792
+ function handleImport(node) {
1793
+ const sourceNode = node.childForFieldName("source");
1794
+ if (!sourceNode) return;
1795
+ const ref = resolveModuleRef(sourceFile, stripQuotes2(sourceNode.text));
1796
+ addModuleNode(ref);
1797
+ const clause = namedChildren(node).find((c) => c.type === "import_clause");
1798
+ if (!clause) return;
1799
+ let sawNamed = false;
1800
+ let sawWhole = false;
1801
+ for (const child of namedChildren(clause)) {
1802
+ if (child.type === "named_imports") {
1803
+ sawNamed = true;
1804
+ for (const specifierNode of namedChildren(child)) {
1805
+ const importedName = specifierNode.childForFieldName("name")?.text;
1806
+ const localName = specifierNode.childForFieldName("alias")?.text ?? importedName;
1807
+ if (localName) importIndex.set(localName, { ref, importedName, kind: "named" });
1808
+ }
1809
+ } else if (child.type === "namespace_import") {
1810
+ sawWhole = true;
1811
+ const localName = namedChildren(child)[0]?.text;
1812
+ if (localName) importIndex.set(localName, { ref, kind: "namespace" });
1813
+ } else if (child.type === "identifier") {
1814
+ sawWhole = true;
1815
+ importIndex.set(child.text, { ref, kind: "default" });
1816
+ }
1817
+ }
1818
+ if (sawNamed) {
1819
+ builder.addEdge({ source: fileId, target: ref.id, relation: "imports_from", confidence: "EXTRACTED" });
1820
+ }
1821
+ if (sawWhole) {
1822
+ builder.addEdge({ source: fileId, target: ref.id, relation: "imports", confidence: "EXTRACTED" });
1823
+ }
1824
+ }
1825
+ function handleReExport(node) {
1826
+ const sourceNode = node.childForFieldName("source");
1827
+ if (!sourceNode) return;
1828
+ const ref = resolveModuleRef(sourceFile, stripQuotes2(sourceNode.text));
1829
+ addModuleNode(ref);
1830
+ builder.addEdge({ source: fileId, target: ref.id, relation: "re_exports", confidence: "EXTRACTED" });
1831
+ }
1832
+ function unwrapExport(node) {
1833
+ if (node.type !== "export_statement") return node;
1834
+ return node.childForFieldName("declaration");
1835
+ }
1836
+ for (const child of namedChildren(root)) {
1837
+ if (child.type === "import_statement") {
1838
+ handleImport(child);
1839
+ continue;
1840
+ }
1841
+ if (child.type === "export_statement" && child.childForFieldName("source")) {
1842
+ handleReExport(child);
1843
+ continue;
1844
+ }
1845
+ const effective = unwrapExport(child);
1846
+ if (!effective) continue;
1847
+ switch (effective.type) {
1848
+ case "function_declaration":
1849
+ case "generator_function_declaration":
1850
+ handleFunctionDeclaration(effective);
1851
+ break;
1852
+ case "class_declaration":
1853
+ handleClassDeclaration(effective);
1854
+ break;
1855
+ case "interface_declaration":
1856
+ handleInterfaceDeclaration(effective);
1857
+ break;
1858
+ case "lexical_declaration":
1859
+ case "variable_declaration":
1860
+ handleTopLevelVariableFunctions(effective);
1861
+ break;
1862
+ default:
1863
+ break;
1864
+ }
1865
+ }
1866
+ function closestTrackedCaller(node) {
1867
+ let current = node.parent;
1868
+ while (current) {
1869
+ const tracked = functionNodeMap.get(current.id);
1870
+ if (tracked) return tracked;
1871
+ current = current.parent;
1872
+ }
1873
+ return fileId;
1874
+ }
1875
+ function closestEnclosingClassMethods(node) {
1876
+ let current = node.parent;
1877
+ while (current) {
1878
+ if (current.type === "method_definition") {
1879
+ const classNodeId = enclosingClassOf.get(current.id);
1880
+ if (classNodeId !== void 0) return classMethods.get(classNodeId);
1881
+ }
1882
+ current = current.parent;
1883
+ }
1884
+ return void 0;
1885
+ }
1886
+ for (const call of descendantsOfType(root, ["call_expression"])) {
1887
+ const fn = call.childForFieldName("function");
1888
+ if (!fn) continue;
1889
+ if (fn.type === "identifier" && fn.text === "require" || fn.type === "import") {
1890
+ const specifier = firstStringArgument(call);
1891
+ if (specifier) {
1892
+ const ref = resolveModuleRef(sourceFile, specifier);
1893
+ addModuleNode(ref);
1894
+ builder.addEdge({
1895
+ source: closestTrackedCaller(call),
1896
+ target: ref.id,
1897
+ relation: "imports",
1898
+ confidence: "EXTRACTED"
1899
+ });
1900
+ continue;
1901
+ }
1902
+ }
1903
+ let targetId = null;
1904
+ let confidence = "EXTRACTED";
1905
+ if (fn.type === "identifier") {
1906
+ const name = fn.text;
1907
+ const sameFile = nameIndex.get(name);
1908
+ if (sameFile) {
1909
+ targetId = sameFile;
1910
+ } else {
1911
+ const binding = importIndex.get(name);
1912
+ if (binding) {
1913
+ targetId = importTargetId(binding);
1914
+ confidence = "INFERRED";
1915
+ }
1916
+ }
1917
+ } else if (fn.type === "member_expression") {
1918
+ const object = fn.childForFieldName("object");
1919
+ const property = fn.childForFieldName("property")?.text;
1920
+ if (object && property) {
1921
+ if (object.type === "this") {
1922
+ const methodId = closestEnclosingClassMethods(call)?.get(property);
1923
+ if (methodId) targetId = methodId;
1924
+ } else if (object.type === "identifier") {
1925
+ const binding = importIndex.get(object.text);
1926
+ if (binding) {
1927
+ targetId = importTargetId(binding, property);
1928
+ confidence = "INFERRED";
1929
+ }
1930
+ }
1931
+ }
1932
+ }
1933
+ if (!targetId) continue;
1934
+ const callerId = closestTrackedCaller(call);
1935
+ builder.addEdge({ source: callerId, target: targetId, relation: "calls", confidence });
1936
+ }
1937
+ return builder.build();
1938
+ }
1939
+
1940
+ // src/extract.ts
1941
+ var EXTRACTOR_REGISTRY = {
1942
+ ".ts": extractTypeScript,
1943
+ ".tsx": extractTypeScript,
1944
+ ".js": extractTypeScript,
1945
+ ".jsx": extractTypeScript,
1946
+ ".mjs": extractTypeScript,
1947
+ ".cjs": extractTypeScript,
1948
+ ".mts": extractTypeScript,
1949
+ ".cts": extractTypeScript,
1950
+ ".py": extractPython,
1951
+ ".go": extractGo,
1952
+ ".rs": extractRust,
1953
+ ".java": extractJava,
1954
+ ".cs": extractCSharp,
1955
+ ".rb": extractRuby
1956
+ };
1957
+ async function extract(filePath) {
1958
+ const ext = path4.extname(filePath);
1959
+ const extractor = EXTRACTOR_REGISTRY[ext];
1960
+ if (!extractor) {
1961
+ return { nodes: [], edges: [] };
1962
+ }
1963
+ return extractor(filePath);
1964
+ }
1965
+
1966
+ // src/build.ts
1967
+ var import_graphology = __toESM(require("graphology"), 1);
1968
+
1969
+ // src/schema.ts
1970
+ var import_zod = require("zod");
1971
+ var ConfidenceSchema = import_zod.z.enum(["EXTRACTED", "INFERRED", "AMBIGUOUS"]);
1972
+ var RelationSchema = import_zod.z.enum([
1973
+ "calls",
1974
+ "imports",
1975
+ "imports_from",
1976
+ "inherits",
1977
+ "implements",
1978
+ "mixes_in",
1979
+ "embeds",
1980
+ "references",
1981
+ "contains",
1982
+ "method",
1983
+ "re_exports"
1984
+ ]);
1985
+ var GraphNodeSchema = import_zod.z.object({
1986
+ id: import_zod.z.string().min(1, "node id must be non-empty"),
1987
+ label: import_zod.z.string(),
1988
+ sourceFile: import_zod.z.string(),
1989
+ sourceLocation: import_zod.z.string()
1990
+ });
1991
+ var GraphEdgeSchema = import_zod.z.object({
1992
+ source: import_zod.z.string().min(1, "edge source must be non-empty"),
1993
+ target: import_zod.z.string().min(1, "edge target must be non-empty"),
1994
+ relation: RelationSchema,
1995
+ confidence: ConfidenceSchema
1996
+ });
1997
+ var ExtractionResultSchema = import_zod.z.object({
1998
+ nodes: import_zod.z.array(GraphNodeSchema),
1999
+ edges: import_zod.z.array(GraphEdgeSchema)
2000
+ });
2001
+ var ExtractionValidationError = class extends Error {
2002
+ issues;
2003
+ constructor(message, issues) {
2004
+ super(message);
2005
+ this.name = "ExtractionValidationError";
2006
+ this.issues = issues;
2007
+ }
2008
+ };
2009
+ function validateExtraction(value, context) {
2010
+ const result = ExtractionResultSchema.safeParse(value);
2011
+ if (!result.success) {
2012
+ const label = context ? ` (${context})` : "";
2013
+ const issues = result.error.issues.map((issue) => ({
2014
+ path: issue.path,
2015
+ message: issue.message
2016
+ }));
2017
+ const details = issues.map((issue) => `${issue.path.join(".") || "<root>"}: ${issue.message}`).join("; ");
2018
+ throw new ExtractionValidationError(`Invalid ExtractionResult${label}: ${details}`, issues);
2019
+ }
2020
+ return result.data;
2021
+ }
2022
+
2023
+ // src/build.ts
2024
+ var CONFIDENCE_RANK = { EXTRACTED: 3, INFERRED: 2, AMBIGUOUS: 1 };
2025
+ function strongerConfidence(a, b) {
2026
+ return CONFIDENCE_RANK[a] >= CONFIDENCE_RANK[b] ? a : b;
2027
+ }
2028
+ function ensureNode(graph, id) {
2029
+ if (graph.hasNode(id)) return;
2030
+ graph.addNode(id, { label: id, sourceFile: "<unknown>", sourceLocation: "L0" });
2031
+ }
2032
+ function buildGraph(extractions) {
2033
+ const graph = new import_graphology.default({ type: "directed", multi: true, allowSelfLoops: true });
2034
+ const validated = extractions.map(
2035
+ (extraction, index) => validateExtraction(extraction, `extraction #${index}`)
2036
+ );
2037
+ const allNodes = validated.flatMap((extraction) => extraction.nodes);
2038
+ const allEdges = validated.flatMap((extraction) => extraction.edges);
2039
+ const sortedNodes = [...allNodes].sort((a, b) => a.id.localeCompare(b.id));
2040
+ for (const node of sortedNodes) {
2041
+ if (graph.hasNode(node.id)) continue;
2042
+ graph.addNode(node.id, {
2043
+ label: node.label,
2044
+ sourceFile: node.sourceFile,
2045
+ sourceLocation: node.sourceLocation
2046
+ });
2047
+ }
2048
+ const sortedEdges = [...allEdges].sort(
2049
+ (a, b) => a.source.localeCompare(b.source) || a.target.localeCompare(b.target) || a.relation.localeCompare(b.relation)
2050
+ );
2051
+ for (const edge of sortedEdges) {
2052
+ ensureNode(graph, edge.source);
2053
+ ensureNode(graph, edge.target);
2054
+ const key = `${edge.source}|${edge.relation}|${edge.target}`;
2055
+ if (graph.hasEdge(key)) {
2056
+ const existing = graph.getEdgeAttribute(key, "confidence");
2057
+ graph.setEdgeAttribute(key, "confidence", strongerConfidence(existing, edge.confidence));
2058
+ continue;
2059
+ }
2060
+ graph.addEdgeWithKey(key, edge.source, edge.target, {
2061
+ relation: edge.relation,
2062
+ confidence: edge.confidence
2063
+ });
2064
+ }
2065
+ return graph;
2066
+ }
2067
+
2068
+ // src/cluster.ts
2069
+ var import_node_crypto = require("crypto");
2070
+ var import_graphology2 = __toESM(require("graphology"), 1);
2071
+ var import_graphology_communities_leiden = __toESM(require("@aflsolutions/graphology-communities-leiden"), 1);
2072
+ var import_graphology_communities_louvain = __toESM(require("graphology-communities-louvain"), 1);
2073
+ var FIXED_SEED = 1592594996;
2074
+ function mulberry32(seed) {
2075
+ let a = seed >>> 0;
2076
+ return function next() {
2077
+ a = a + 1831565813 | 0;
2078
+ let t = Math.imul(a ^ a >>> 15, 1 | a);
2079
+ t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
2080
+ return ((t ^ t >>> 14) >>> 0) / 4294967296;
2081
+ };
2082
+ }
2083
+ function sortedWorkingCopy(graph) {
2084
+ const copy = new import_graphology2.default({ type: graph.type, multi: graph.multi, allowSelfLoops: graph.allowSelfLoops });
2085
+ const nodeIds = graph.nodes().sort((a, b) => a.localeCompare(b));
2086
+ for (const id of nodeIds) {
2087
+ copy.addNode(id, { ...graph.getNodeAttributes(id) });
2088
+ }
2089
+ const edgeEntries = [];
2090
+ graph.forEachEdge((edge, attributes, source, target) => {
2091
+ edgeEntries.push({ key: edge, source, target, attributes });
2092
+ });
2093
+ edgeEntries.sort(
2094
+ (a, b) => a.source.localeCompare(b.source) || a.target.localeCompare(b.target) || String(a.attributes.relation).localeCompare(String(b.attributes.relation))
2095
+ );
2096
+ for (const entry of edgeEntries) {
2097
+ copy.addEdgeWithKey(entry.key, entry.source, entry.target, { ...entry.attributes });
2098
+ }
2099
+ return copy;
2100
+ }
2101
+ function toUndirectedCopy(graph) {
2102
+ const copy = new import_graphology2.default({ type: "undirected", multi: true, allowSelfLoops: graph.allowSelfLoops });
2103
+ graph.forEachNode((nodeId, attributes) => copy.addNode(nodeId, { ...attributes }));
2104
+ graph.forEachEdge((edgeKey, attributes, source, target) => {
2105
+ copy.addEdgeWithKey(edgeKey, source, target, { ...attributes });
2106
+ });
2107
+ return copy;
2108
+ }
2109
+ function runAlgorithm(working, options) {
2110
+ const rng = mulberry32(FIXED_SEED);
2111
+ if (options.algorithm === "leiden") {
2112
+ const undirected = toUndirectedCopy(working);
2113
+ import_graphology_communities_leiden.default.assign(undirected, { rng, maxIterations: options.maxIterations ?? 0 });
2114
+ undirected.forEachNode((nodeId, attributes) => {
2115
+ working.setNodeAttribute(nodeId, "community", attributes.community);
2116
+ });
2117
+ return;
2118
+ }
2119
+ import_graphology_communities_louvain.default.assign(working, { rng });
2120
+ }
2121
+ function cluster(graph, options = {}) {
2122
+ if (graph.order === 0) return graph;
2123
+ const working = sortedWorkingCopy(graph);
2124
+ runAlgorithm(working, options);
2125
+ const hubs = /* @__PURE__ */ new Map();
2126
+ working.forEachNode((nodeId) => {
2127
+ const community = working.getNodeAttribute(nodeId, "community");
2128
+ const degree = working.degree(nodeId);
2129
+ const current = hubs.get(community);
2130
+ if (!current || degree > current.degree) {
2131
+ hubs.set(community, { id: nodeId, degree });
2132
+ }
2133
+ });
2134
+ const membersByCommunity = /* @__PURE__ */ new Map();
2135
+ working.forEachNode((nodeId) => {
2136
+ const community = working.getNodeAttribute(nodeId, "community");
2137
+ const list = membersByCommunity.get(community);
2138
+ if (list) list.push(nodeId);
2139
+ else membersByCommunity.set(community, [nodeId]);
2140
+ });
2141
+ const labelByCommunity = /* @__PURE__ */ new Map();
2142
+ const hashByCommunity = /* @__PURE__ */ new Map();
2143
+ for (const [community, members] of membersByCommunity) {
2144
+ members.sort((a, b) => a.localeCompare(b));
2145
+ hashByCommunity.set(community, (0, import_node_crypto.createHash)("sha256").update(members.join("\n")).digest("hex"));
2146
+ const hub = hubs.get(community);
2147
+ const hubLabel = hub ? working.getNodeAttribute(hub.id, "label") : void 0;
2148
+ labelByCommunity.set(community, hubLabel && hubLabel.length > 0 ? hubLabel : `community-${community}`);
2149
+ }
2150
+ working.forEachNode((nodeId) => {
2151
+ const community = working.getNodeAttribute(nodeId, "community");
2152
+ graph.mergeNodeAttributes(nodeId, {
2153
+ community,
2154
+ communityLabel: labelByCommunity.get(community),
2155
+ communityHash: hashByCommunity.get(community)
2156
+ });
2157
+ });
2158
+ return graph;
2159
+ }
2160
+
2161
+ // src/analyze.ts
2162
+ var ABSOLUTE_DEGREE_FLOOR = 10;
2163
+ var MEAN_DEGREE_MULTIPLIER = 3;
2164
+ var MAX_LISTED = 10;
2165
+ function truncatedList(items) {
2166
+ const shown = items.slice(0, MAX_LISTED).join(", ");
2167
+ return items.length > MAX_LISTED ? `${shown}, ...` : shown;
2168
+ }
2169
+ function connectedComponents(graph) {
2170
+ const visited = /* @__PURE__ */ new Set();
2171
+ const components = [];
2172
+ const sortedNodes = [...graph.nodes()].sort((a, b) => a.localeCompare(b));
2173
+ for (const start of sortedNodes) {
2174
+ if (visited.has(start)) continue;
2175
+ const component = [];
2176
+ const queue = [start];
2177
+ visited.add(start);
2178
+ while (queue.length > 0) {
2179
+ const current = queue.shift();
2180
+ component.push(current);
2181
+ const neighbors = [...graph.neighbors(current)].sort((a, b) => a.localeCompare(b));
2182
+ for (const neighbor of neighbors) {
2183
+ if (!visited.has(neighbor)) {
2184
+ visited.add(neighbor);
2185
+ queue.push(neighbor);
2186
+ }
2187
+ }
2188
+ }
2189
+ component.sort((a, b) => a.localeCompare(b));
2190
+ components.push(component);
2191
+ }
2192
+ components.sort((a, b) => b.length - a.length || (a[0] ?? "").localeCompare(b[0] ?? ""));
2193
+ return components;
2194
+ }
2195
+ function labelOf(graph, id) {
2196
+ return graph.getNodeAttribute(id, "label") ?? id;
2197
+ }
2198
+ function analyze(graph) {
2199
+ const godNodes = [];
2200
+ const surprises = [];
2201
+ const openQuestions = [];
2202
+ if (graph.order === 0) {
2203
+ openQuestions.push(
2204
+ "The graph is empty \u2014 no nodes were extracted. Check detect()/extract() coverage for this codebase."
2205
+ );
2206
+ return { godNodes, surprises, openQuestions };
2207
+ }
2208
+ const degreeEntries = [...graph.nodes()].map((id) => ({ id, degree: graph.degree(id) })).sort((a, b) => a.id.localeCompare(b.id));
2209
+ const meanDegree = degreeEntries.reduce((sum, e) => sum + e.degree, 0) / degreeEntries.length;
2210
+ const threshold = Math.max(ABSOLUTE_DEGREE_FLOOR, meanDegree * MEAN_DEGREE_MULTIPLIER);
2211
+ for (const { id, degree } of degreeEntries) {
2212
+ if (degree > 0 && degree >= threshold) {
2213
+ godNodes.push(id);
2214
+ surprises.push(
2215
+ `${labelOf(graph, id)} has ${degree} connections (fan-in + fan-out) \u2014 well above the graph average of ${meanDegree.toFixed(1)}. Likely a god node / hotspot worth reviewing for excessive coupling.`
2216
+ );
2217
+ }
2218
+ }
2219
+ graph.forEachEdge((_edge, attributes, source, target) => {
2220
+ if (source === target) {
2221
+ surprises.push(`${labelOf(graph, source)} has a self-referential "${String(attributes.relation)}" edge.`);
2222
+ }
2223
+ });
2224
+ const ambiguousEdges = [];
2225
+ graph.forEachEdge((_edge, attributes, source, target) => {
2226
+ if (attributes.confidence === "AMBIGUOUS") {
2227
+ ambiguousEdges.push(
2228
+ `${labelOf(graph, source)} --${String(attributes.relation)}--> ${labelOf(graph, target)}`
2229
+ );
2230
+ }
2231
+ });
2232
+ if (ambiguousEdges.length > 0) {
2233
+ ambiguousEdges.sort((a, b) => a.localeCompare(b));
2234
+ openQuestions.push(
2235
+ `${ambiguousEdges.length} edge(s) are marked AMBIGUOUS and need human review: ` + truncatedList(ambiguousEdges)
2236
+ );
2237
+ }
2238
+ const isolated = degreeEntries.filter((e) => e.degree === 0).map((e) => e.id);
2239
+ if (isolated.length > 0) {
2240
+ openQuestions.push(
2241
+ `${isolated.length} node(s) have no incoming or outgoing edges (${truncatedList(isolated)}) \u2014 dead code, an entry point, or a gap in extraction?`
2242
+ );
2243
+ }
2244
+ const components = connectedComponents(graph);
2245
+ if (components.length > 1) {
2246
+ const largest = components[0];
2247
+ openQuestions.push(
2248
+ `The graph has ${components.length} disconnected components \u2014 the largest has ${largest.length} node(s). Confirm this reflects real module boundaries rather than missed cross-file edges.`
2249
+ );
2250
+ }
2251
+ return { godNodes, surprises, openQuestions };
2252
+ }
2253
+
2254
+ // src/report.ts
2255
+ function labelOf2(graph, id) {
2256
+ return graph.getNodeAttribute(id, "label") ?? id;
2257
+ }
2258
+ function bulletList(items) {
2259
+ if (items.length === 0) return "_None found._";
2260
+ return items.map((item) => `- ${item}`).join("\n");
2261
+ }
2262
+ function summarizeCommunities(graph) {
2263
+ const byCommunity = /* @__PURE__ */ new Map();
2264
+ graph.forEachNode((nodeId, attributes) => {
2265
+ const community = attributes.community;
2266
+ if (community === void 0) return;
2267
+ const label = attributes.communityLabel ?? `community-${community}`;
2268
+ const existing = byCommunity.get(community);
2269
+ if (existing) {
2270
+ existing.members.push(nodeId);
2271
+ } else {
2272
+ byCommunity.set(community, { label, members: [nodeId] });
2273
+ }
2274
+ });
2275
+ const summaries = [...byCommunity.values()];
2276
+ for (const summary of summaries) {
2277
+ summary.members.sort((a, b) => a.localeCompare(b));
2278
+ }
2279
+ summaries.sort((a, b) => b.members.length - a.members.length || a.label.localeCompare(b.label));
2280
+ return summaries;
2281
+ }
2282
+ function renderReport(graph, analysis, now = /* @__PURE__ */ new Date()) {
2283
+ const generatedAt = now.toISOString();
2284
+ const communities = summarizeCommunities(graph);
2285
+ const lines = [];
2286
+ lines.push("# Graph Report");
2287
+ lines.push("");
2288
+ lines.push(`Generated: ${generatedAt}`);
2289
+ lines.push("");
2290
+ lines.push("## Summary");
2291
+ lines.push("");
2292
+ lines.push(`- **Nodes:** ${graph.order}`);
2293
+ lines.push(`- **Edges:** ${graph.size}`);
2294
+ lines.push(`- **Communities:** ${communities.length}`);
2295
+ lines.push(`- **God nodes:** ${analysis.godNodes.length}`);
2296
+ lines.push("");
2297
+ lines.push("## Communities");
2298
+ lines.push("");
2299
+ if (communities.length === 0) {
2300
+ lines.push("_No communities detected (graph may be empty, or cluster() has not run yet)._");
2301
+ } else {
2302
+ for (const community of communities) {
2303
+ lines.push(`### ${community.label} (${community.members.length} member(s))`);
2304
+ lines.push("");
2305
+ const shown = community.members.slice(0, 25).map((id) => labelOf2(graph, id));
2306
+ lines.push(bulletList(shown));
2307
+ if (community.members.length > 25) {
2308
+ lines.push(`- ...and ${community.members.length - 25} more`);
2309
+ }
2310
+ lines.push("");
2311
+ }
2312
+ }
2313
+ lines.push("## God Nodes");
2314
+ lines.push("");
2315
+ lines.push(
2316
+ "Nodes with unusually high fan-in + fan-out relative to the rest of this graph \u2014 often a sign of a shared utility, a bottleneck, or a module that has taken on too many responsibilities."
2317
+ );
2318
+ lines.push("");
2319
+ lines.push(bulletList(analysis.godNodes.map((id) => labelOf2(graph, id))));
2320
+ lines.push("");
2321
+ lines.push("## Structural Surprises");
2322
+ lines.push("");
2323
+ lines.push(bulletList(analysis.surprises));
2324
+ lines.push("");
2325
+ lines.push("## Open Questions");
2326
+ lines.push("");
2327
+ lines.push(bulletList(analysis.openQuestions));
2328
+ lines.push("");
2329
+ return lines.join("\n");
2330
+ }
2331
+
2332
+ // src/export.ts
2333
+ var import_node_module2 = require("module");
2334
+ var fs10 = __toESM(require("fs/promises"), 1);
2335
+ var path5 = __toESM(require("path"), 1);
2336
+
2337
+ // src/security.ts
2338
+ var import_node_crypto2 = require("crypto");
2339
+ var dns = __toESM(require("dns"), 1);
2340
+ var http = __toESM(require("http"), 1);
2341
+ var https = __toESM(require("https"), 1);
2342
+ var fs9 = __toESM(require("fs"), 1);
2343
+ var net = __toESM(require("net"), 1);
2344
+ var nodePath = __toESM(require("path"), 1);
2345
+ var MAX_FETCH_BYTES = 50 * 1024 * 1024;
2346
+ var MAX_TEXT_BYTES = 10 * 1024 * 1024;
2347
+ var MAX_LABEL_LEN = 256;
2348
+ function validateGraphPath(path8, base) {
2349
+ const baseDir = base ?? nodePath.join(process.cwd(), "graphify-out");
2350
+ let resolvedBase;
2351
+ try {
2352
+ resolvedBase = fs9.realpathSync(baseDir);
2353
+ } catch {
2354
+ throw new Error(`Base directory does not exist: ${baseDir}`);
2355
+ }
2356
+ const candidate = nodePath.isAbsolute(path8) ? path8 : nodePath.join(resolvedBase, path8);
2357
+ const resolvedCandidate = nodePath.resolve(candidate);
2358
+ let realCandidate = resolvedCandidate;
2359
+ try {
2360
+ realCandidate = fs9.realpathSync(resolvedCandidate);
2361
+ } catch {
2362
+ }
2363
+ const relative4 = nodePath.relative(resolvedBase, realCandidate);
2364
+ const escapes = relative4 === ".." || relative4.startsWith(`..${nodePath.sep}`) || nodePath.isAbsolute(relative4);
2365
+ if (escapes) {
2366
+ throw new Error(`Path escapes graphify-out/: ${path8}`);
2367
+ }
2368
+ return realCandidate;
2369
+ }
2370
+ function sanitizeLabel(text) {
2371
+ if (text == null) return "";
2372
+ const stripped = String(text).replace(/[\x00-\x1f\x7f]/g, "");
2373
+ return stripped.slice(0, MAX_LABEL_LEN);
2374
+ }
2375
+ function escapeHtml(text) {
2376
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
2377
+ }
2378
+
2379
+ // src/export.ts
2380
+ var require3 = (0, import_node_module2.createRequire)(importMetaUrl);
2381
+ function resolveVisNetworkAssets() {
2382
+ const packageJsonPath = require3.resolve("vis-network/package.json");
2383
+ const root = path5.dirname(packageJsonPath);
2384
+ return {
2385
+ jsPath: path5.join(root, "standalone", "umd", "vis-network.min.js"),
2386
+ cssPath: path5.join(root, "styles", "vis-network.min.css")
2387
+ };
2388
+ }
2389
+ var COMMUNITY_PALETTE = [
2390
+ "#4e79a7",
2391
+ "#f28e2b",
2392
+ "#e15759",
2393
+ "#76b7b2",
2394
+ "#59a14f",
2395
+ "#edc948",
2396
+ "#b07aa1",
2397
+ "#ff9da7",
2398
+ "#9c755f",
2399
+ "#bab0ac"
2400
+ ];
2401
+ function colorForCommunity(community) {
2402
+ if (community === void 0) return "#8ab4f8";
2403
+ const index = (community % COMMUNITY_PALETTE.length + COMMUNITY_PALETTE.length) % COMMUNITY_PALETTE.length;
2404
+ return COMMUNITY_PALETTE[index];
2405
+ }
2406
+ function safeInlineJson(value) {
2407
+ return JSON.stringify(value, null, 2).replace(/<\/(script)/gi, "<\\/$1");
2408
+ }
2409
+ function buildVisNodesAndEdges(graph) {
2410
+ const nodes = graph.nodes().map((id) => {
2411
+ const attributes = graph.getNodeAttributes(id);
2412
+ const rawLabel = attributes.label ?? id;
2413
+ const label = escapeHtml(sanitizeLabel(rawLabel));
2414
+ const sourceFile = escapeHtml(sanitizeLabel(attributes.sourceFile ?? ""));
2415
+ const sourceLocation = escapeHtml(sanitizeLabel(attributes.sourceLocation ?? ""));
2416
+ const communityLabel = escapeHtml(sanitizeLabel(attributes.communityLabel ?? ""));
2417
+ return {
2418
+ id,
2419
+ label,
2420
+ title: `${label}
2421
+ ${sourceFile}${sourceLocation ? `:${sourceLocation}` : ""}${communityLabel ? `
2422
+ community: ${communityLabel}` : ""}`,
2423
+ color: colorForCommunity(attributes.community)
2424
+ };
2425
+ });
2426
+ const edges = [];
2427
+ graph.forEachEdge((edgeKey, attributes, source, target) => {
2428
+ const relation = escapeHtml(sanitizeLabel(String(attributes.relation ?? "")));
2429
+ const confidence = escapeHtml(sanitizeLabel(String(attributes.confidence ?? "")));
2430
+ edges.push({
2431
+ id: edgeKey,
2432
+ from: source,
2433
+ to: target,
2434
+ label: relation,
2435
+ title: `${relation} (${confidence})`,
2436
+ dashes: confidence === "INFERRED" || confidence === "AMBIGUOUS",
2437
+ arrows: "to"
2438
+ });
2439
+ });
2440
+ return { nodes, edges };
2441
+ }
2442
+ async function renderHtml(graph) {
2443
+ const { jsPath, cssPath } = resolveVisNetworkAssets();
2444
+ const [visJs, visCss] = await Promise.all([
2445
+ fs10.readFile(jsPath, "utf-8"),
2446
+ fs10.readFile(cssPath, "utf-8")
2447
+ ]);
2448
+ const { nodes, edges } = buildVisNodesAndEdges(graph);
2449
+ const dataJson = safeInlineJson({ nodes, edges });
2450
+ return `<!doctype html>
2451
+ <html lang="en">
2452
+ <head>
2453
+ <meta charset="utf-8">
2454
+ <title>graphify \u2014 graph.html</title>
2455
+ <style>
2456
+ html, body { margin: 0; padding: 0; height: 100%; font-family: system-ui, sans-serif; }
2457
+ #graph { width: 100%; height: 100vh; }
2458
+ ${visCss}
2459
+ </style>
2460
+ </head>
2461
+ <body>
2462
+ <div id="graph"></div>
2463
+ <script>
2464
+ ${visJs}
2465
+ </script>
2466
+ <script>
2467
+ (function () {
2468
+ var data = ${dataJson};
2469
+ var nodes = new vis.DataSet(data.nodes);
2470
+ var edges = new vis.DataSet(data.edges);
2471
+ var container = document.getElementById('graph');
2472
+ var network = new vis.Network(container, { nodes: nodes, edges: edges }, {
2473
+ nodes: { shape: 'dot', size: 12, font: { size: 14 } },
2474
+ edges: { smooth: { type: 'continuous' }, font: { size: 10, align: 'middle' } },
2475
+ physics: { stabilization: true },
2476
+ interaction: { hover: true, tooltipDelay: 150 },
2477
+ });
2478
+ window.__graphifyNetwork = network;
2479
+ })();
2480
+ </script>
2481
+ </body>
2482
+ </html>
2483
+ `;
2484
+ }
2485
+ async function exportGraph(graph, options, report) {
2486
+ const outDir = path5.resolve(options.outDir);
2487
+ await fs10.mkdir(outDir, { recursive: true });
2488
+ const jsonPath = validateGraphPath("graph.json", outDir);
2489
+ await fs10.writeFile(jsonPath, JSON.stringify(graph.toJSON(), null, 2), "utf-8");
2490
+ if (options.html !== false) {
2491
+ const htmlPath = validateGraphPath("graph.html", outDir);
2492
+ await fs10.writeFile(htmlPath, await renderHtml(graph), "utf-8");
2493
+ }
2494
+ if (report !== void 0) {
2495
+ const reportPath = validateGraphPath("GRAPH_REPORT.md", outDir);
2496
+ await fs10.writeFile(reportPath, report, "utf-8");
2497
+ }
2498
+ const notImplemented = [];
2499
+ if (options.svg) notImplemented.push("--svg");
2500
+ if (options.graphml) notImplemented.push("--graphml");
2501
+ if (options.neo4j) notImplemented.push("--neo4j");
2502
+ if (options.obsidian) notImplemented.push("--obsidian");
2503
+ for (const flag of notImplemented) {
2504
+ console.warn(`graphify: ${flag} export is not implemented yet in v1 \u2014 skipping.`);
2505
+ }
2506
+ }
2507
+
2508
+ // src/pipeline.ts
2509
+ var path6 = __toESM(require("path"), 1);
2510
+ async function runPipeline(root, options = {}) {
2511
+ const progress = options.onProgress ?? (() => {
2512
+ });
2513
+ const resolvedRoot = path6.resolve(root);
2514
+ progress(`Scanning ${resolvedRoot} ...`);
2515
+ const manifest = collectFiles(resolvedRoot);
2516
+ progress(
2517
+ `Found ${manifest.totalFiles} file(s) (${manifest.files.code.length} code, ${manifest.skippedSensitive.length} skipped as sensitive).`
2518
+ );
2519
+ progress("Extracting...");
2520
+ const extractions = [];
2521
+ for (const relFile of manifest.files.code.sort((a, b) => a.localeCompare(b))) {
2522
+ const filePath = path6.relative(process.cwd(), path6.join(resolvedRoot, relFile)) || relFile;
2523
+ try {
2524
+ const extraction = await extract(filePath);
2525
+ extractions.push(extraction);
2526
+ } catch (error) {
2527
+ progress(` extraction failed for ${relFile}: ${error.message}`);
2528
+ throw error;
2529
+ }
2530
+ }
2531
+ progress("Building graph...");
2532
+ const graph = buildGraph(extractions);
2533
+ progress(`Clustering (${options.algorithm ?? "louvain"})...`);
2534
+ cluster(graph, { algorithm: options.algorithm, maxIterations: options.maxIterations });
2535
+ progress("Analyzing...");
2536
+ const analysis = analyze(graph);
2537
+ progress("Rendering report...");
2538
+ const report = renderReport(graph, analysis);
2539
+ const outDir = options.outDir ?? path6.join(resolvedRoot, "graphify-out");
2540
+ progress(`Exporting to ${outDir} ...`);
2541
+ await exportGraph(
2542
+ graph,
2543
+ {
2544
+ outDir,
2545
+ html: options.html,
2546
+ svg: options.svg,
2547
+ graphml: options.graphml,
2548
+ neo4j: options.neo4j,
2549
+ obsidian: options.obsidian
2550
+ },
2551
+ report
2552
+ );
2553
+ progress("Done.");
2554
+ return { manifest, graph, analysis, report };
2555
+ }
2556
+
2557
+ // src/graphStore.ts
2558
+ var fs11 = __toESM(require("fs/promises"), 1);
2559
+ var path7 = __toESM(require("path"), 1);
2560
+ var import_graphology3 = __toESM(require("graphology"), 1);
2561
+ async function loadGraph(outDir = path7.join(process.cwd(), "graphify-out")) {
2562
+ const jsonPath = validateGraphPath("graph.json", outDir);
2563
+ const raw = await fs11.readFile(jsonPath, "utf-8");
2564
+ const data = JSON.parse(raw);
2565
+ return import_graphology3.default.from(data);
2566
+ }
2567
+
2568
+ // src/query.ts
2569
+ var STOPWORDS = /* @__PURE__ */ new Set([
2570
+ "a",
2571
+ "an",
2572
+ "the",
2573
+ "is",
2574
+ "are",
2575
+ "was",
2576
+ "were",
2577
+ "do",
2578
+ "does",
2579
+ "did",
2580
+ "how",
2581
+ "what",
2582
+ "where",
2583
+ "when",
2584
+ "why",
2585
+ "who",
2586
+ "which",
2587
+ "to",
2588
+ "of",
2589
+ "in",
2590
+ "on",
2591
+ "for",
2592
+ "and",
2593
+ "or",
2594
+ "this",
2595
+ "that",
2596
+ "it",
2597
+ "does",
2598
+ "that's"
2599
+ ]);
2600
+ function tokenize(text) {
2601
+ return text.toLowerCase().split(/[^a-z0-9_.]+/).filter((token) => token.length > 1 && !STOPWORDS.has(token));
2602
+ }
2603
+ function scoreNodes(graph, query) {
2604
+ const tokens = tokenize(query);
2605
+ const matches = [];
2606
+ graph.forEachNode((id, attributes) => {
2607
+ const label = attributes.label ?? id;
2608
+ const haystack = `${label} ${id}`.toLowerCase();
2609
+ let score = 0;
2610
+ for (const token of tokens) {
2611
+ if (haystack.includes(token)) score += 1;
2612
+ }
2613
+ if (score > 0) matches.push({ id, label, score });
2614
+ });
2615
+ matches.sort((a, b) => b.score - a.score || a.id.localeCompare(b.id));
2616
+ return matches;
2617
+ }
2618
+ function resolveNode(graph, query) {
2619
+ const matches = scoreNodes(graph, query);
2620
+ return matches[0] ?? null;
2621
+ }
2622
+ var DEFAULT_MAX_DEPTH = 2;
2623
+ var DEFAULT_MAX_SEEDS = 5;
2624
+ var DEFAULT_BUDGET = 40;
2625
+ function queryGraph(graph, question, options = {}) {
2626
+ const maxDepth = options.maxDepth ?? DEFAULT_MAX_DEPTH;
2627
+ const maxSeeds = options.maxSeeds ?? DEFAULT_MAX_SEEDS;
2628
+ const budget = options.budget ?? DEFAULT_BUDGET;
2629
+ const allMatches = scoreNodes(graph, question);
2630
+ const seeds = allMatches.length > 0 ? allMatches.slice(0, maxSeeds) : [];
2631
+ const visited = /* @__PURE__ */ new Map();
2632
+ const frontier = seeds.map((s) => ({ id: s.id, depth: 0 }));
2633
+ for (const seed of seeds) visited.set(seed.id, 0);
2634
+ while (frontier.length > 0 && visited.size < budget) {
2635
+ const current = options.dfs ? frontier.pop() : frontier.shift();
2636
+ if (current.depth >= maxDepth) continue;
2637
+ const neighbors = [...graph.neighbors(current.id)].sort((a, b) => a.localeCompare(b));
2638
+ for (const neighbor of neighbors) {
2639
+ if (visited.has(neighbor) || visited.size >= budget) continue;
2640
+ visited.set(neighbor, current.depth + 1);
2641
+ frontier.push({ id: neighbor, depth: current.depth + 1 });
2642
+ }
2643
+ }
2644
+ const visitedNodes = [...visited.entries()].map(([id, depth]) => {
2645
+ const attrs = graph.getNodeAttributes(id);
2646
+ return {
2647
+ id,
2648
+ label: attrs.label ?? id,
2649
+ sourceFile: attrs.sourceFile ?? "",
2650
+ sourceLocation: attrs.sourceLocation ?? "",
2651
+ depth
2652
+ };
2653
+ }).sort((a, b) => a.depth - b.depth || a.id.localeCompare(b.id));
2654
+ const edges = [];
2655
+ graph.forEachEdge((_edge, attrs, source, target) => {
2656
+ if (visited.has(source) && visited.has(target)) {
2657
+ edges.push({
2658
+ source,
2659
+ target,
2660
+ relation: String(attrs.relation),
2661
+ confidence: String(attrs.confidence)
2662
+ });
2663
+ }
2664
+ });
2665
+ edges.sort(
2666
+ (a, b) => a.source.localeCompare(b.source) || a.target.localeCompare(b.target) || a.relation.localeCompare(b.relation)
2667
+ );
2668
+ return { seeds, visited: visitedNodes, edges };
2669
+ }
2670
+ function shortestPath(graph, fromQuery, toQuery) {
2671
+ const from = resolveNode(graph, fromQuery);
2672
+ const to = resolveNode(graph, toQuery);
2673
+ if (!from || !to) return null;
2674
+ if (from.id === to.id) {
2675
+ return { from, to, found: true, path: [from.id], edges: [] };
2676
+ }
2677
+ const predecessor = /* @__PURE__ */ new Map();
2678
+ const visited = /* @__PURE__ */ new Set([from.id]);
2679
+ const queue = [from.id];
2680
+ while (queue.length > 0) {
2681
+ const current = queue.shift();
2682
+ if (current === to.id) break;
2683
+ const neighbors = [...graph.neighbors(current)].sort((a, b) => a.localeCompare(b));
2684
+ for (const neighbor of neighbors) {
2685
+ if (visited.has(neighbor)) continue;
2686
+ visited.add(neighbor);
2687
+ predecessor.set(neighbor, current);
2688
+ queue.push(neighbor);
2689
+ }
2690
+ }
2691
+ if (!visited.has(to.id)) {
2692
+ return { from, to, found: false, path: [], edges: [] };
2693
+ }
2694
+ const path8 = [to.id];
2695
+ let cursor = to.id;
2696
+ while (cursor !== from.id) {
2697
+ const prev = predecessor.get(cursor);
2698
+ if (!prev) break;
2699
+ path8.unshift(prev);
2700
+ cursor = prev;
2701
+ }
2702
+ const edges = [];
2703
+ for (let i = 0; i < path8.length - 1; i++) {
2704
+ const a = path8[i];
2705
+ const b = path8[i + 1];
2706
+ const key = graph.edges(a, b)[0] ?? graph.edges(b, a)[0];
2707
+ if (key) {
2708
+ const attrs = graph.getEdgeAttributes(key);
2709
+ edges.push({ source: a, target: b, relation: String(attrs.relation), confidence: String(attrs.confidence) });
2710
+ }
2711
+ }
2712
+ return { from, to, found: true, path: path8, edges };
2713
+ }
2714
+ function explainNode(graph, query) {
2715
+ const match = resolveNode(graph, query);
2716
+ if (!match) return null;
2717
+ const attrs = graph.getNodeAttributes(match.id);
2718
+ const outgoing = [];
2719
+ const incoming = [];
2720
+ graph.forEachOutEdge(match.id, (_edge, edgeAttrs, source, target) => {
2721
+ outgoing.push({ source, target, relation: String(edgeAttrs.relation), confidence: String(edgeAttrs.confidence) });
2722
+ });
2723
+ graph.forEachInEdge(match.id, (_edge, edgeAttrs, source, target) => {
2724
+ incoming.push({ source, target, relation: String(edgeAttrs.relation), confidence: String(edgeAttrs.confidence) });
2725
+ });
2726
+ outgoing.sort((a, b) => a.target.localeCompare(b.target) || a.relation.localeCompare(b.relation));
2727
+ incoming.sort((a, b) => a.source.localeCompare(b.source) || a.relation.localeCompare(b.relation));
2728
+ return {
2729
+ id: match.id,
2730
+ label: match.label,
2731
+ sourceFile: attrs.sourceFile ?? "",
2732
+ sourceLocation: attrs.sourceLocation ?? "",
2733
+ communityLabel: attrs.communityLabel,
2734
+ outgoing,
2735
+ incoming
2736
+ };
2737
+ }
2738
+ // Annotate the CommonJS export names for ESM import in node:
2739
+ 0 && (module.exports = {
2740
+ EXTRACTOR_REGISTRY,
2741
+ ExtractionResultSchema,
2742
+ ExtractionValidationError,
2743
+ analyze,
2744
+ buildGraph,
2745
+ cluster,
2746
+ collectFiles,
2747
+ explainNode,
2748
+ exportGraph,
2749
+ extract,
2750
+ loadGraph,
2751
+ queryGraph,
2752
+ renderReport,
2753
+ resolveNode,
2754
+ runPipeline,
2755
+ scoreNodes,
2756
+ shortestPath,
2757
+ validateExtraction
2758
+ });
2759
+ //# sourceMappingURL=index.cjs.map