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