@neat.is/core 0.7.3 → 0.7.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/server.cjs CHANGED
@@ -1083,7 +1083,7 @@ async function rollbackExtension(ctx, args) {
1083
1083
 
1084
1084
  // src/divergences.ts
1085
1085
  init_cjs_shims();
1086
- var import_types8 = require("@neat.is/types");
1086
+ var import_types9 = require("@neat.is/types");
1087
1087
 
1088
1088
  // src/columns.ts
1089
1089
  init_cjs_shims();
@@ -1439,8 +1439,8 @@ init_cjs_shims();
1439
1439
 
1440
1440
  // src/ingest.ts
1441
1441
  init_cjs_shims();
1442
- var import_node_fs8 = require("fs");
1443
- var import_node_path9 = __toESM(require("path"), 1);
1442
+ var import_node_fs9 = require("fs");
1443
+ var import_node_path10 = __toESM(require("path"), 1);
1444
1444
  var sourceMapJs = __toESM(require("source-map-js"), 1);
1445
1445
 
1446
1446
  // src/policy.ts
@@ -1903,16 +1903,16 @@ var PolicyViolationsLog = class {
1903
1903
  };
1904
1904
 
1905
1905
  // src/ingest.ts
1906
- var import_types6 = require("@neat.is/types");
1906
+ var import_types7 = require("@neat.is/types");
1907
1907
 
1908
1908
  // src/extract/routes.ts
1909
1909
  init_cjs_shims();
1910
- var import_node_path8 = __toESM(require("path"), 1);
1911
- var import_tree_sitter = __toESM(require("tree-sitter"), 1);
1912
- var import_tree_sitter_javascript = __toESM(require("tree-sitter-javascript"), 1);
1913
- var import_tree_sitter_python = __toESM(require("tree-sitter-python"), 1);
1914
- var import_tree_sitter_go = __toESM(require("tree-sitter-go"), 1);
1915
- var import_types5 = require("@neat.is/types");
1910
+ var import_node_path9 = __toESM(require("path"), 1);
1911
+ var import_tree_sitter2 = __toESM(require("tree-sitter"), 1);
1912
+ var import_tree_sitter_javascript2 = __toESM(require("tree-sitter-javascript"), 1);
1913
+ var import_tree_sitter_python2 = __toESM(require("tree-sitter-python"), 1);
1914
+ var import_tree_sitter_go2 = __toESM(require("tree-sitter-go"), 1);
1915
+ var import_types6 = require("@neat.is/types");
1916
1916
 
1917
1917
  // src/extract/shared.ts
1918
1918
  init_cjs_shims();
@@ -2283,7 +2283,15 @@ function ensureFileNode(graph, serviceName, serviceNodeId, relPath) {
2283
2283
  return { fileNodeId, nodesAdded, edgesAdded };
2284
2284
  }
2285
2285
 
2286
- // src/extract/routes.ts
2286
+ // src/extract/imports.ts
2287
+ init_cjs_shims();
2288
+ var import_node_path8 = __toESM(require("path"), 1);
2289
+ var import_node_fs8 = require("fs");
2290
+ var import_tree_sitter = __toESM(require("tree-sitter"), 1);
2291
+ var import_tree_sitter_javascript = __toESM(require("tree-sitter-javascript"), 1);
2292
+ var import_tree_sitter_python = __toESM(require("tree-sitter-python"), 1);
2293
+ var import_tree_sitter_go = __toESM(require("tree-sitter-go"), 1);
2294
+ var import_types5 = require("@neat.is/types");
2287
2295
  var PARSE_CHUNK = 16384;
2288
2296
  function parseSource(parser, source) {
2289
2297
  return parser.parse(
@@ -2305,153 +2313,530 @@ function makeGoParser() {
2305
2313
  p.setLanguage(import_tree_sitter_go.default);
2306
2314
  return p;
2307
2315
  }
2308
- var ROUTER_METHODS = /* @__PURE__ */ new Set([
2309
- "get",
2310
- "post",
2311
- "put",
2312
- "patch",
2313
- "delete",
2314
- "options",
2315
- "head",
2316
- "all"
2317
- ]);
2318
- var NEXT_APP_METHODS = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]);
2319
- var JS_ROUTE_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx"]);
2320
- function ginRoutesFromSource(source, parser) {
2321
- const tree = parseSource(parser, source);
2322
- const prefixes = /* @__PURE__ */ new Map();
2323
- const out = [];
2324
- walk(tree.rootNode, (node) => {
2325
- if (node.type === "short_var_declaration" || node.type === "var_spec") {
2326
- const name = node.childForFieldName("left")?.namedChild(0)?.text ?? node.childForFieldName("name")?.text;
2327
- const value = node.childForFieldName("right")?.namedChild(0) ?? node.childForFieldName("value");
2328
- if (name && value?.type === "call_expression") {
2329
- const fn2 = value.childForFieldName("function");
2330
- const field = fn2?.childForFieldName("field")?.text;
2331
- const first2 = value.childForFieldName("arguments")?.namedChild(0);
2332
- if (field === "Group" && first2?.type === "interpreted_string_literal") {
2333
- prefixes.set(name, first2.text.slice(1, -1));
2334
- }
2335
- }
2336
- return;
2337
- }
2338
- if (node.type !== "call_expression") return;
2339
- const fn = node.childForFieldName("function");
2340
- if (fn?.type !== "selector_expression") return;
2341
- const method = fn.childForFieldName("field")?.text?.toUpperCase();
2342
- if (!method || !ROUTER_METHODS.has(method.toLowerCase())) return;
2343
- const receiver = fn.childForFieldName("operand")?.text ?? "";
2344
- const first = node.childForFieldName("arguments")?.namedChild(0);
2345
- if (first?.type !== "interpreted_string_literal") return;
2346
- const leaf = first.text.slice(1, -1);
2347
- out.push({
2348
- method: method === "ALL" ? "ALL" : method,
2349
- pathTemplate: canonicalizeTemplate((prefixes.get(receiver) ?? "") + leaf),
2350
- line: node.startPosition.row + 1,
2351
- framework: "gin"
2352
- });
2353
- });
2354
- return out;
2355
- }
2356
- var FASTAPI_METHODS = /* @__PURE__ */ new Set(["get", "post", "put", "patch", "delete", "options", "head", "trace"]);
2357
- var NESTJS_METHODS = /* @__PURE__ */ new Map([
2358
- ["Get", "GET"],
2359
- ["Post", "POST"],
2360
- ["Put", "PUT"],
2361
- ["Patch", "PATCH"],
2362
- ["Delete", "DELETE"],
2363
- ["Options", "OPTIONS"],
2364
- ["Head", "HEAD"],
2365
- ["All", "ALL"]
2366
- ]);
2367
- function canonicalizeTemplate(raw) {
2368
- let p = raw.split("?")[0].split("#")[0];
2369
- if (!p.startsWith("/")) p = "/" + p;
2370
- if (p.length > 1 && p.endsWith("/")) p = p.slice(0, -1);
2371
- return p;
2372
- }
2373
- function isDynamicSegment(seg) {
2374
- if (seg.length === 0) return false;
2375
- if (seg.includes(":")) return true;
2376
- if (seg.startsWith("{") || seg.startsWith("[")) return true;
2377
- if (/^\d+$/.test(seg)) return true;
2378
- if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(seg)) return true;
2379
- if (/^[0-9a-f]{24,}$/i.test(seg)) return true;
2380
- return false;
2316
+ function stringLiteralText(node) {
2317
+ for (let i = 0; i < node.childCount; i++) {
2318
+ const child = node.child(i);
2319
+ if (child?.type === "string_fragment") return child.text;
2320
+ }
2321
+ const raw = node.text;
2322
+ if (raw.length >= 2) return raw.slice(1, -1);
2323
+ return raw.length === 0 ? null : "";
2381
2324
  }
2382
- function normalizePathTemplate(raw) {
2383
- const canonical = canonicalizeTemplate(raw);
2384
- const segments = canonical.split("/").filter((s) => s.length > 0);
2385
- const normalised = segments.map((seg) => isDynamicSegment(seg) ? ":param" : seg.toLowerCase());
2386
- return "/" + normalised.join("/");
2325
+ function clipSnippet(text) {
2326
+ const oneLine = text.split("\n")[0] ?? text;
2327
+ return oneLine.length > 120 ? oneLine.slice(0, 120) : oneLine;
2387
2328
  }
2388
- function walk(node, visit) {
2389
- visit(node);
2329
+ function collectGoImports(node, out) {
2330
+ if (node.type === "import_spec") {
2331
+ const pathNode = node.childForFieldName("path");
2332
+ if (pathNode) {
2333
+ const specifier = pathNode.text.replace(/^`|`$/g, "").replace(/^"|"$/g, "");
2334
+ if (specifier) out.push({ specifier, line: node.startPosition.row + 1, snippet: clipSnippet(node.text) });
2335
+ }
2336
+ return;
2337
+ }
2390
2338
  for (let i = 0; i < node.namedChildCount; i++) {
2391
2339
  const child = node.namedChild(i);
2392
- if (child) walk(child, visit);
2340
+ if (child) collectGoImports(child, out);
2393
2341
  }
2394
2342
  }
2395
- function staticStringText(node) {
2396
- if (node.type === "string") {
2397
- for (let i = 0; i < node.namedChildCount; i++) {
2398
- const child = node.namedChild(i);
2399
- if (child?.type === "string_fragment") return child.text;
2343
+ function collectJsImports(node, out) {
2344
+ if (node.type === "import_statement") {
2345
+ const source = node.childForFieldName("source");
2346
+ if (source) {
2347
+ const specifier = stringLiteralText(source);
2348
+ if (specifier) {
2349
+ out.push({ specifier, line: node.startPosition.row + 1, snippet: clipSnippet(node.text) });
2350
+ }
2400
2351
  }
2401
- return "";
2352
+ return;
2402
2353
  }
2403
- if (node.type === "template_string") {
2404
- for (let i = 0; i < node.namedChildCount; i++) {
2405
- if (node.namedChild(i)?.type === "template_substitution") return null;
2354
+ if (node.type === "call_expression") {
2355
+ const fn = node.childForFieldName("function");
2356
+ if (fn?.type === "identifier" && fn.text === "require") {
2357
+ const args = node.childForFieldName("arguments");
2358
+ const firstArg = args?.namedChild(0);
2359
+ if (firstArg?.type === "string") {
2360
+ const specifier = stringLiteralText(firstArg);
2361
+ if (specifier) {
2362
+ out.push({ specifier, line: node.startPosition.row + 1, snippet: clipSnippet(node.text) });
2363
+ }
2364
+ }
2406
2365
  }
2407
- const raw = node.text;
2408
- return raw.length >= 2 ? raw.slice(1, -1) : "";
2409
2366
  }
2410
- return null;
2367
+ for (let i = 0; i < node.namedChildCount; i++) {
2368
+ const child = node.namedChild(i);
2369
+ if (child) collectJsImports(child, out);
2370
+ }
2411
2371
  }
2412
- function objectStringProp(objNode, key) {
2413
- for (let i = 0; i < objNode.namedChildCount; i++) {
2414
- const pair = objNode.namedChild(i);
2415
- if (!pair || pair.type !== "pair") continue;
2416
- const k = pair.childForFieldName("key");
2417
- if (!k) continue;
2418
- const kText = k.type === "string" ? staticStringText(k) : k.text;
2419
- if (kText !== key) continue;
2420
- const v = pair.childForFieldName("value");
2421
- if (v) return staticStringText(v);
2372
+ function collectImportedNames(node, out) {
2373
+ if (node.type === "aliased_import") {
2374
+ const nameNode = node.childForFieldName("name");
2375
+ if (nameNode) out.push(nameNode.text);
2376
+ return;
2377
+ }
2378
+ if (node.type === "dotted_name") {
2379
+ out.push(node.text);
2380
+ return;
2381
+ }
2382
+ for (let i = 0; i < node.namedChildCount; i++) {
2383
+ const child = node.namedChild(i);
2384
+ if (child) collectImportedNames(child, out);
2422
2385
  }
2423
- return null;
2424
2386
  }
2425
- function fastifyRouteMethods(objNode) {
2426
- for (let i = 0; i < objNode.namedChildCount; i++) {
2427
- const pair = objNode.namedChild(i);
2428
- if (!pair || pair.type !== "pair") continue;
2429
- const k = pair.childForFieldName("key");
2430
- const kText = k ? k.type === "string" ? staticStringText(k) : k.text : null;
2431
- if (kText !== "method") continue;
2432
- const v = pair.childForFieldName("value");
2433
- if (!v) return [];
2434
- if (v.type === "string" || v.type === "template_string") {
2435
- const s = staticStringText(v);
2436
- return s ? [s.toUpperCase()] : [];
2437
- }
2438
- if (v.type === "array") {
2439
- const out = [];
2440
- for (let j = 0; j < v.namedChildCount; j++) {
2441
- const el = v.namedChild(j);
2442
- if (el && (el.type === "string" || el.type === "template_string")) {
2443
- const s = staticStringText(el);
2444
- if (s) out.push(s.toUpperCase());
2387
+ function collectPyImports(node, out) {
2388
+ if (node.type === "import_from_statement") {
2389
+ let level = 0;
2390
+ let modulePath = "";
2391
+ const names = [];
2392
+ let pastFrom = false;
2393
+ let pastImport = false;
2394
+ for (let i = 0; i < node.childCount; i++) {
2395
+ const child = node.child(i);
2396
+ if (!child) continue;
2397
+ if (!pastFrom) {
2398
+ if (child.type === "from") pastFrom = true;
2399
+ continue;
2400
+ }
2401
+ if (!pastImport) {
2402
+ if (child.type === "import") {
2403
+ pastImport = true;
2404
+ continue;
2405
+ }
2406
+ if (child.type === "relative_import") {
2407
+ for (let j = 0; j < child.childCount; j++) {
2408
+ const rc = child.child(j);
2409
+ if (!rc) continue;
2410
+ if (rc.type === "import_prefix") {
2411
+ for (let k = 0; k < rc.childCount; k++) {
2412
+ if (rc.child(k)?.type === ".") level++;
2413
+ }
2414
+ } else if (rc.type === "dotted_name") modulePath = rc.text;
2415
+ }
2416
+ } else if (child.type === "dotted_name") {
2417
+ modulePath = child.text;
2445
2418
  }
2419
+ continue;
2446
2420
  }
2447
- return out;
2421
+ collectImportedNames(child, names);
2422
+ }
2423
+ if (level > 0 || modulePath) {
2424
+ out.push({ modulePath, level, names, line: node.startPosition.row + 1, snippet: clipSnippet(node.text) });
2448
2425
  }
2449
2426
  }
2450
- return [];
2427
+ for (let i = 0; i < node.namedChildCount; i++) {
2428
+ const child = node.namedChild(i);
2429
+ if (child) collectPyImports(child, out);
2430
+ }
2451
2431
  }
2452
- function nestDecoratorImports(root) {
2453
- const imports = /* @__PURE__ */ new Map();
2454
- walk(root, (node) => {
2432
+ async function fileExists2(p) {
2433
+ try {
2434
+ await import_node_fs8.promises.access(p);
2435
+ return true;
2436
+ } catch {
2437
+ return false;
2438
+ }
2439
+ }
2440
+ function isWithinServiceDir(candidate, serviceDir) {
2441
+ const rel = import_node_path8.default.relative(serviceDir, candidate);
2442
+ return rel !== "" && !rel.startsWith("..") && !import_node_path8.default.isAbsolute(rel);
2443
+ }
2444
+ var JS_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
2445
+ var JS_INDEX_FILES = JS_EXTENSIONS.map((ext) => `index${ext}`);
2446
+ async function firstExistingCandidate(base, serviceDir) {
2447
+ for (const ext of JS_EXTENSIONS) {
2448
+ const candidate = base + ext;
2449
+ if (isWithinServiceDir(candidate, serviceDir) && await fileExists2(candidate)) {
2450
+ return toPosix(import_node_path8.default.relative(serviceDir, candidate));
2451
+ }
2452
+ }
2453
+ for (const indexFile of JS_INDEX_FILES) {
2454
+ const candidate = import_node_path8.default.join(base, indexFile);
2455
+ if (isWithinServiceDir(candidate, serviceDir) && await fileExists2(candidate)) {
2456
+ return toPosix(import_node_path8.default.relative(serviceDir, candidate));
2457
+ }
2458
+ }
2459
+ return null;
2460
+ }
2461
+ async function loadTsPathConfig(serviceDir) {
2462
+ const tsconfigPath = import_node_path8.default.join(serviceDir, "tsconfig.json");
2463
+ let raw;
2464
+ try {
2465
+ raw = await import_node_fs8.promises.readFile(tsconfigPath, "utf8");
2466
+ } catch {
2467
+ return null;
2468
+ }
2469
+ try {
2470
+ const parsed = JSON.parse(raw);
2471
+ const paths = parsed.compilerOptions?.paths;
2472
+ if (!paths || Object.keys(paths).length === 0) return null;
2473
+ const baseUrl = parsed.compilerOptions?.baseUrl;
2474
+ return { paths, baseDir: baseUrl ? import_node_path8.default.resolve(serviceDir, baseUrl) : serviceDir };
2475
+ } catch (err) {
2476
+ recordExtractionError("import alias resolution", tsconfigPath, err);
2477
+ return null;
2478
+ }
2479
+ }
2480
+ async function resolveTsAlias(specifier, config, serviceDir) {
2481
+ for (const [pattern, targets] of Object.entries(config.paths)) {
2482
+ let suffix = null;
2483
+ if (pattern === specifier) {
2484
+ suffix = "";
2485
+ } else if (pattern.endsWith("/*")) {
2486
+ const prefix = pattern.slice(0, -1);
2487
+ if (specifier.startsWith(prefix)) suffix = specifier.slice(prefix.length);
2488
+ }
2489
+ if (suffix === null) continue;
2490
+ for (const target of targets) {
2491
+ const targetBase = target.endsWith("/*") ? target.slice(0, -2) : target.replace(/\*$/, "");
2492
+ const resolvedBase = import_node_path8.default.resolve(config.baseDir, targetBase, suffix);
2493
+ const hit = await firstExistingCandidate(resolvedBase, serviceDir);
2494
+ if (hit) return hit;
2495
+ if (isWithinServiceDir(resolvedBase, serviceDir) && await fileExists2(resolvedBase)) {
2496
+ return toPosix(import_node_path8.default.relative(serviceDir, resolvedBase));
2497
+ }
2498
+ }
2499
+ }
2500
+ return null;
2501
+ }
2502
+ async function resolveJsImport(specifier, importerDir, serviceDir, tsPaths) {
2503
+ if (!specifier) return null;
2504
+ if (specifier.startsWith("./") || specifier.startsWith("../")) {
2505
+ const base = import_node_path8.default.resolve(importerDir, specifier);
2506
+ const ext = import_node_path8.default.extname(specifier);
2507
+ if (ext) {
2508
+ if (ext === ".js" || ext === ".jsx") {
2509
+ const tsExt = ext === ".jsx" ? ".tsx" : ".ts";
2510
+ const tsSibling = base.slice(0, -ext.length) + tsExt;
2511
+ if (isWithinServiceDir(tsSibling, serviceDir) && await fileExists2(tsSibling)) {
2512
+ return toPosix(import_node_path8.default.relative(serviceDir, tsSibling));
2513
+ }
2514
+ }
2515
+ if (isWithinServiceDir(base, serviceDir) && await fileExists2(base)) {
2516
+ return toPosix(import_node_path8.default.relative(serviceDir, base));
2517
+ }
2518
+ if (!JS_EXTENSIONS.includes(ext)) {
2519
+ return firstExistingCandidate(base, serviceDir);
2520
+ }
2521
+ return null;
2522
+ }
2523
+ return firstExistingCandidate(base, serviceDir);
2524
+ }
2525
+ if (tsPaths) return resolveTsAlias(specifier, tsPaths, serviceDir);
2526
+ return null;
2527
+ }
2528
+ async function resolvePyImport(imp, importerPath, serviceDir) {
2529
+ let baseDir;
2530
+ if (imp.level > 0) {
2531
+ baseDir = import_node_path8.default.dirname(importerPath);
2532
+ for (let i = 1; i < imp.level; i++) baseDir = import_node_path8.default.dirname(baseDir);
2533
+ } else {
2534
+ baseDir = serviceDir;
2535
+ }
2536
+ const moduleBase = imp.modulePath ? import_node_path8.default.join(baseDir, imp.modulePath.split(".").join("/")) : baseDir;
2537
+ const resolved = /* @__PURE__ */ new Set();
2538
+ let needModuleFile = imp.names.length === 0;
2539
+ for (const name of imp.names) {
2540
+ const submoduleFile = import_node_path8.default.join(moduleBase, `${name}.py`);
2541
+ const subpackageInit = import_node_path8.default.join(moduleBase, name, "__init__.py");
2542
+ if (isWithinServiceDir(submoduleFile, serviceDir) && await fileExists2(submoduleFile)) {
2543
+ resolved.add(toPosix(import_node_path8.default.relative(serviceDir, submoduleFile)));
2544
+ } else if (isWithinServiceDir(subpackageInit, serviceDir) && await fileExists2(subpackageInit)) {
2545
+ resolved.add(toPosix(import_node_path8.default.relative(serviceDir, subpackageInit)));
2546
+ } else {
2547
+ needModuleFile = true;
2548
+ }
2549
+ }
2550
+ if (needModuleFile) {
2551
+ const moduleFileCandidates = imp.modulePath ? [`${moduleBase}.py`, import_node_path8.default.join(moduleBase, "__init__.py")] : [import_node_path8.default.join(moduleBase, "__init__.py")];
2552
+ for (const candidate of moduleFileCandidates) {
2553
+ if (isWithinServiceDir(candidate, serviceDir) && await fileExists2(candidate)) {
2554
+ resolved.add(toPosix(import_node_path8.default.relative(serviceDir, candidate)));
2555
+ break;
2556
+ }
2557
+ }
2558
+ }
2559
+ return [...resolved];
2560
+ }
2561
+ async function resolveGoImport(specifier, modulePath, serviceDir) {
2562
+ if (specifier !== modulePath && !specifier.startsWith(`${modulePath}/`)) return null;
2563
+ const suffix = specifier === modulePath ? "" : specifier.slice(modulePath.length + 1);
2564
+ const dir = import_node_path8.default.join(serviceDir, suffix);
2565
+ const entries = await import_node_fs8.promises.readdir(dir, { withFileTypes: true }).catch(() => []);
2566
+ const candidates = entries.filter((entry) => entry.isFile() && entry.name.endsWith(".go") && !entry.name.endsWith("_test.go")).map((entry) => import_node_path8.default.join(dir, entry.name));
2567
+ if (candidates.length !== 1) return null;
2568
+ return toPosix(import_node_path8.default.relative(serviceDir, candidates[0]));
2569
+ }
2570
+ function emitImportEdge(graph, serviceName, importerFileId, importerRelPath, importeeRelPath, line, snippet2) {
2571
+ const importeeFileId = (0, import_types5.fileId)(serviceName, importeeRelPath);
2572
+ if (!graph.hasNode(importeeFileId)) return 0;
2573
+ const edgeId = (0, import_types5.extractedEdgeId)(importerFileId, importeeFileId, import_types5.EdgeType.IMPORTS);
2574
+ if (graph.hasEdge(edgeId)) return 0;
2575
+ const edge = {
2576
+ id: edgeId,
2577
+ source: importerFileId,
2578
+ target: importeeFileId,
2579
+ type: import_types5.EdgeType.IMPORTS,
2580
+ provenance: import_types5.Provenance.EXTRACTED,
2581
+ confidence: (0, import_types5.confidenceForExtracted)("structural"),
2582
+ evidence: { file: importerRelPath, line, snippet: snippet2 }
2583
+ };
2584
+ graph.addEdgeWithKey(edgeId, importerFileId, importeeFileId, edge);
2585
+ return 1;
2586
+ }
2587
+ async function addImports(graph, services) {
2588
+ const jsParser = makeJsParser();
2589
+ const pyParser = makePyParser();
2590
+ const goParser = makeGoParser();
2591
+ let edgesAdded = 0;
2592
+ for (const service of services) {
2593
+ const tsPaths = await loadTsPathConfig(service.dir);
2594
+ const files = await loadSourceFiles(service.dir);
2595
+ for (const file of files) {
2596
+ if (isTestPath(file.path)) continue;
2597
+ const relFile = toPosix(import_node_path8.default.relative(service.dir, file.path));
2598
+ const importerFileId = (0, import_types5.fileId)(service.pkg.name, relFile);
2599
+ const isPython = import_node_path8.default.extname(file.path) === ".py";
2600
+ const isGo = import_node_path8.default.extname(file.path) === ".go";
2601
+ if (isGo) {
2602
+ let goImports = [];
2603
+ try {
2604
+ const tree = parseSource(goParser, file.content);
2605
+ collectGoImports(tree.rootNode, goImports);
2606
+ } catch (err) {
2607
+ recordExtractionError("import extraction", file.path, err);
2608
+ continue;
2609
+ }
2610
+ const goMod = await import_node_fs8.promises.readFile(import_node_path8.default.join(service.dir, "go.mod"), "utf8").catch(() => "");
2611
+ const modulePath = goMod.match(/^\s*module\s+(\S+)\s*$/m)?.[1];
2612
+ if (!modulePath) continue;
2613
+ for (const imp of goImports) {
2614
+ const resolved = await resolveGoImport(imp.specifier, modulePath, service.dir);
2615
+ if (!resolved) continue;
2616
+ edgesAdded += emitImportEdge(graph, service.pkg.name, importerFileId, relFile, resolved, imp.line, imp.snippet);
2617
+ }
2618
+ continue;
2619
+ }
2620
+ if (isPython) {
2621
+ let pyImports = [];
2622
+ try {
2623
+ const tree = parseSource(pyParser, file.content);
2624
+ collectPyImports(tree.rootNode, pyImports);
2625
+ } catch (err) {
2626
+ recordExtractionError("import extraction", file.path, err);
2627
+ continue;
2628
+ }
2629
+ for (const imp of pyImports) {
2630
+ const resolvedPaths = await resolvePyImport(imp, file.path, service.dir);
2631
+ for (const resolved of resolvedPaths) {
2632
+ edgesAdded += emitImportEdge(
2633
+ graph,
2634
+ service.pkg.name,
2635
+ importerFileId,
2636
+ relFile,
2637
+ resolved,
2638
+ imp.line,
2639
+ imp.snippet
2640
+ );
2641
+ }
2642
+ }
2643
+ continue;
2644
+ }
2645
+ let jsImports = [];
2646
+ try {
2647
+ const tree = parseSource(jsParser, file.content);
2648
+ collectJsImports(tree.rootNode, jsImports);
2649
+ } catch (err) {
2650
+ recordExtractionError("import extraction", file.path, err);
2651
+ continue;
2652
+ }
2653
+ for (const imp of jsImports) {
2654
+ const resolved = await resolveJsImport(imp.specifier, import_node_path8.default.dirname(file.path), service.dir, tsPaths);
2655
+ if (!resolved) continue;
2656
+ edgesAdded += emitImportEdge(
2657
+ graph,
2658
+ service.pkg.name,
2659
+ importerFileId,
2660
+ relFile,
2661
+ resolved,
2662
+ imp.line,
2663
+ imp.snippet
2664
+ );
2665
+ }
2666
+ }
2667
+ }
2668
+ return { nodesAdded: 0, edgesAdded };
2669
+ }
2670
+
2671
+ // src/extract/routes.ts
2672
+ var PARSE_CHUNK2 = 16384;
2673
+ function parseSource2(parser, source) {
2674
+ return parser.parse(
2675
+ (index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK2)
2676
+ );
2677
+ }
2678
+ function makeJsParser2() {
2679
+ const p = new import_tree_sitter2.default();
2680
+ p.setLanguage(import_tree_sitter_javascript2.default);
2681
+ return p;
2682
+ }
2683
+ function makePyParser2() {
2684
+ const p = new import_tree_sitter2.default();
2685
+ p.setLanguage(import_tree_sitter_python2.default);
2686
+ return p;
2687
+ }
2688
+ function makeGoParser2() {
2689
+ const p = new import_tree_sitter2.default();
2690
+ p.setLanguage(import_tree_sitter_go2.default);
2691
+ return p;
2692
+ }
2693
+ var ROUTER_METHODS = /* @__PURE__ */ new Set([
2694
+ "get",
2695
+ "post",
2696
+ "put",
2697
+ "patch",
2698
+ "delete",
2699
+ "options",
2700
+ "head",
2701
+ "all"
2702
+ ]);
2703
+ var NEXT_APP_METHODS = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]);
2704
+ var JS_ROUTE_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx"]);
2705
+ function ginRoutesFromSource(source, parser) {
2706
+ const tree = parseSource2(parser, source);
2707
+ const prefixes = /* @__PURE__ */ new Map();
2708
+ const out = [];
2709
+ walk(tree.rootNode, (node) => {
2710
+ if (node.type === "short_var_declaration" || node.type === "var_spec") {
2711
+ const name = node.childForFieldName("left")?.namedChild(0)?.text ?? node.childForFieldName("name")?.text;
2712
+ const value = node.childForFieldName("right")?.namedChild(0) ?? node.childForFieldName("value");
2713
+ if (name && value?.type === "call_expression") {
2714
+ const fn2 = value.childForFieldName("function");
2715
+ const field = fn2?.childForFieldName("field")?.text;
2716
+ const first2 = value.childForFieldName("arguments")?.namedChild(0);
2717
+ if (field === "Group" && first2?.type === "interpreted_string_literal") {
2718
+ prefixes.set(name, first2.text.slice(1, -1));
2719
+ }
2720
+ }
2721
+ return;
2722
+ }
2723
+ if (node.type !== "call_expression") return;
2724
+ const fn = node.childForFieldName("function");
2725
+ if (fn?.type !== "selector_expression") return;
2726
+ const method = fn.childForFieldName("field")?.text?.toUpperCase();
2727
+ if (!method || !ROUTER_METHODS.has(method.toLowerCase())) return;
2728
+ const receiver = fn.childForFieldName("operand")?.text ?? "";
2729
+ const first = node.childForFieldName("arguments")?.namedChild(0);
2730
+ if (first?.type !== "interpreted_string_literal") return;
2731
+ const leaf = first.text.slice(1, -1);
2732
+ out.push({
2733
+ method: method === "ALL" ? "ALL" : method,
2734
+ pathTemplate: canonicalizeTemplate((prefixes.get(receiver) ?? "") + leaf),
2735
+ line: node.startPosition.row + 1,
2736
+ framework: "gin"
2737
+ });
2738
+ });
2739
+ return out;
2740
+ }
2741
+ var FASTAPI_METHODS = /* @__PURE__ */ new Set(["get", "post", "put", "patch", "delete", "options", "head", "trace"]);
2742
+ var NESTJS_METHODS = /* @__PURE__ */ new Map([
2743
+ ["Get", "GET"],
2744
+ ["Post", "POST"],
2745
+ ["Put", "PUT"],
2746
+ ["Patch", "PATCH"],
2747
+ ["Delete", "DELETE"],
2748
+ ["Options", "OPTIONS"],
2749
+ ["Head", "HEAD"],
2750
+ ["All", "ALL"]
2751
+ ]);
2752
+ function canonicalizeTemplate(raw) {
2753
+ let p = raw.split("?")[0].split("#")[0];
2754
+ if (!p.startsWith("/")) p = "/" + p;
2755
+ if (p.length > 1 && p.endsWith("/")) p = p.slice(0, -1);
2756
+ return p;
2757
+ }
2758
+ function isDynamicSegment(seg) {
2759
+ if (seg.length === 0) return false;
2760
+ if (seg.includes(":")) return true;
2761
+ if (seg.startsWith("{") || seg.startsWith("[")) return true;
2762
+ if (/^\d+$/.test(seg)) return true;
2763
+ if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(seg)) return true;
2764
+ if (/^[0-9a-f]{24,}$/i.test(seg)) return true;
2765
+ return false;
2766
+ }
2767
+ function normalizePathTemplate(raw) {
2768
+ const canonical = canonicalizeTemplate(raw);
2769
+ const segments = canonical.split("/").filter((s) => s.length > 0);
2770
+ const normalised = segments.map((seg) => isDynamicSegment(seg) ? ":param" : seg.toLowerCase());
2771
+ return "/" + normalised.join("/");
2772
+ }
2773
+ function walk(node, visit) {
2774
+ visit(node);
2775
+ for (let i = 0; i < node.namedChildCount; i++) {
2776
+ const child = node.namedChild(i);
2777
+ if (child) walk(child, visit);
2778
+ }
2779
+ }
2780
+ function staticStringText(node) {
2781
+ if (node.type === "string") {
2782
+ for (let i = 0; i < node.namedChildCount; i++) {
2783
+ const child = node.namedChild(i);
2784
+ if (child?.type === "string_fragment") return child.text;
2785
+ }
2786
+ return "";
2787
+ }
2788
+ if (node.type === "template_string") {
2789
+ for (let i = 0; i < node.namedChildCount; i++) {
2790
+ if (node.namedChild(i)?.type === "template_substitution") return null;
2791
+ }
2792
+ const raw = node.text;
2793
+ return raw.length >= 2 ? raw.slice(1, -1) : "";
2794
+ }
2795
+ return null;
2796
+ }
2797
+ function objectStringProp(objNode, key) {
2798
+ for (let i = 0; i < objNode.namedChildCount; i++) {
2799
+ const pair = objNode.namedChild(i);
2800
+ if (!pair || pair.type !== "pair") continue;
2801
+ const k = pair.childForFieldName("key");
2802
+ if (!k) continue;
2803
+ const kText = k.type === "string" ? staticStringText(k) : k.text;
2804
+ if (kText !== key) continue;
2805
+ const v = pair.childForFieldName("value");
2806
+ if (v) return staticStringText(v);
2807
+ }
2808
+ return null;
2809
+ }
2810
+ function fastifyRouteMethods(objNode) {
2811
+ for (let i = 0; i < objNode.namedChildCount; i++) {
2812
+ const pair = objNode.namedChild(i);
2813
+ if (!pair || pair.type !== "pair") continue;
2814
+ const k = pair.childForFieldName("key");
2815
+ const kText = k ? k.type === "string" ? staticStringText(k) : k.text : null;
2816
+ if (kText !== "method") continue;
2817
+ const v = pair.childForFieldName("value");
2818
+ if (!v) return [];
2819
+ if (v.type === "string" || v.type === "template_string") {
2820
+ const s = staticStringText(v);
2821
+ return s ? [s.toUpperCase()] : [];
2822
+ }
2823
+ if (v.type === "array") {
2824
+ const out = [];
2825
+ for (let j = 0; j < v.namedChildCount; j++) {
2826
+ const el = v.namedChild(j);
2827
+ if (el && (el.type === "string" || el.type === "template_string")) {
2828
+ const s = staticStringText(el);
2829
+ if (s) out.push(s.toUpperCase());
2830
+ }
2831
+ }
2832
+ return out;
2833
+ }
2834
+ }
2835
+ return [];
2836
+ }
2837
+ function nestDecoratorImports(root) {
2838
+ const imports = /* @__PURE__ */ new Map();
2839
+ walk(root, (node) => {
2455
2840
  if (node.type !== "import_statement") return;
2456
2841
  const source = node.childForFieldName("source");
2457
2842
  if (!source || staticStringText(source) !== "@nestjs/common") return;
@@ -2499,7 +2884,7 @@ function nestJoinedPath(prefix, leaf) {
2499
2884
  return canonicalizeTemplate(segments.join("/"));
2500
2885
  }
2501
2886
  function nestjsRoutesFromSource(source, parser) {
2502
- const tree = parseSource(parser, source);
2887
+ const tree = parseSource2(parser, source);
2503
2888
  const imports = nestDecoratorImports(tree.rootNode);
2504
2889
  if (![...imports.values()].includes("Controller")) return [];
2505
2890
  const out = [];
@@ -2547,7 +2932,7 @@ function nestjsRoutesFromSource(source, parser) {
2547
2932
  return out;
2548
2933
  }
2549
2934
  function serverRoutesFromSource(source, parser, hasExpress, hasFastify, hasHono = false) {
2550
- const tree = parseSource(parser, source);
2935
+ const tree = parseSource2(parser, source);
2551
2936
  const out = [];
2552
2937
  const framework = hasExpress ? "express" : hasFastify ? "fastify" : hasHono ? "hono" : "unknown";
2553
2938
  walk(tree.rootNode, (node) => {
@@ -2605,7 +2990,7 @@ function isNextPagesApiFile(relFile) {
2605
2990
  if (pagesIdx === -1 || segs[pagesIdx + 1] !== "api") return false;
2606
2991
  const base = segs[segs.length - 1] ?? "";
2607
2992
  if (/^_(app|document|middleware)\./.test(base)) return false;
2608
- return JS_ROUTE_EXTENSIONS.has(import_node_path8.default.extname(base));
2993
+ return JS_ROUTE_EXTENSIONS.has(import_node_path9.default.extname(base));
2609
2994
  }
2610
2995
  function nextSegment(seg) {
2611
2996
  if (seg.startsWith("(") && seg.endsWith(")")) return null;
@@ -2667,7 +3052,7 @@ function nextAppMethods(root) {
2667
3052
  }
2668
3053
  function nextRoutesFromFile(source, relFile, parser) {
2669
3054
  if (isNextAppRouteFile(relFile)) {
2670
- const tree = parseSource(parser, source);
3055
+ const tree = parseSource2(parser, source);
2671
3056
  const template = nextAppPathTemplate(relFile);
2672
3057
  return nextAppMethods(tree.rootNode).map(({ method, line }) => ({
2673
3058
  method,
@@ -2788,7 +3173,7 @@ function collectMountPrefixes(root, consts) {
2788
3173
  return mounts;
2789
3174
  }
2790
3175
  function pythonRoutesFromSource(source, parser, framework) {
2791
- const tree = parseSource(parser, source);
3176
+ const tree = parseSource2(parser, source);
2792
3177
  const prefixes = collectPythonRouterPrefixes(tree.rootNode);
2793
3178
  const consts = collectStringConstants(tree.rootNode);
2794
3179
  const mounts = collectMountPrefixes(tree.rootNode, consts);
@@ -2828,7 +3213,7 @@ function pythonRoutesFromSource(source, parser, framework) {
2828
3213
  return out;
2829
3214
  }
2830
3215
  function djangoRoutesFromSource(source, parser) {
2831
- const tree = parseSource(parser, source);
3216
+ const tree = parseSource2(parser, source);
2832
3217
  const out = [];
2833
3218
  walk(tree.rootNode, (node) => {
2834
3219
  if (node.type !== "assignment") return;
@@ -2856,10 +3241,320 @@ function djangoRoutesFromSource(source, parser) {
2856
3241
  });
2857
3242
  return out;
2858
3243
  }
3244
+ function namedArgs(argsNode) {
3245
+ const out = [];
3246
+ if (!argsNode) return out;
3247
+ for (let i = 0; i < argsNode.namedChildCount; i++) {
3248
+ const c = argsNode.namedChild(i);
3249
+ if (c && c.type !== "comment") out.push(c);
3250
+ }
3251
+ return out;
3252
+ }
3253
+ function parseUseMount(callNode) {
3254
+ const args = namedArgs(callNode.childForFieldName("arguments"));
3255
+ if (args.length === 0) return null;
3256
+ const first = args[0];
3257
+ const firstStr = first.type === "string" || first.type === "template_string" ? staticStringText(first) : null;
3258
+ if (firstStr !== null && firstStr.startsWith("/")) {
3259
+ const prefix = canonicalizeTemplate(firstStr);
3260
+ const second = args[1];
3261
+ const target = second && second.type === "identifier" ? second.text : null;
3262
+ return { prefix: prefix === "/" ? "" : prefix, target };
3263
+ }
3264
+ if (args.length === 1 && first.type === "identifier") return { prefix: "", target: first.text };
3265
+ return null;
3266
+ }
3267
+ function unwrapRouterExpr(node, expressLocals, routerCtors) {
3268
+ if (node.type === "identifier") return { base: { alias: node.text }, mounts: [] };
3269
+ if (node.type !== "call_expression") return null;
3270
+ const fn = node.childForFieldName("function");
3271
+ if (!fn) return null;
3272
+ if (fn.type === "member_expression") {
3273
+ const prop = fn.childForFieldName("property")?.text;
3274
+ const obj = fn.childForFieldName("object");
3275
+ if (!prop || !obj) return null;
3276
+ if (prop === "use") {
3277
+ const inner = unwrapRouterExpr(obj, expressLocals, routerCtors);
3278
+ if (!inner) return null;
3279
+ const mount = parseUseMount(node);
3280
+ return { base: inner.base, mounts: mount ? [...inner.mounts, mount] : inner.mounts };
3281
+ }
3282
+ if (prop === "Router" && obj.type === "identifier" && expressLocals.has(obj.text)) {
3283
+ return { base: "newRouter", mounts: [] };
3284
+ }
3285
+ return null;
3286
+ }
3287
+ if (fn.type === "identifier") {
3288
+ if (expressLocals.has(fn.text)) return { base: "app", mounts: [] };
3289
+ if (routerCtors.has(fn.text)) return { base: "newRouter", mounts: [] };
3290
+ }
3291
+ return null;
3292
+ }
3293
+ function collectExpressImports(root) {
3294
+ const expressLocals = /* @__PURE__ */ new Set();
3295
+ const routerCtors = /* @__PURE__ */ new Set();
3296
+ const bindings = [];
3297
+ const addFromExpress = (local, sel, exported) => {
3298
+ if (sel === "default" || sel === "namespace") expressLocals.add(local);
3299
+ else if (exported === "Router") routerCtors.add(local);
3300
+ };
3301
+ walk(root, (node) => {
3302
+ if (node.type === "import_statement") {
3303
+ const source = node.childForFieldName("source");
3304
+ const spec = source ? staticStringText(source) : null;
3305
+ if (!spec) return;
3306
+ let clause = null;
3307
+ for (let i = 0; i < node.namedChildCount; i++) {
3308
+ const c = node.namedChild(i);
3309
+ if (c?.type === "import_clause") clause = c;
3310
+ }
3311
+ if (!clause) return;
3312
+ for (let i = 0; i < clause.namedChildCount; i++) {
3313
+ const c = clause.namedChild(i);
3314
+ if (!c) continue;
3315
+ if (c.type === "identifier") {
3316
+ if (spec === "express") addFromExpress(c.text, "default", "default");
3317
+ else bindings.push({ local: c.text, specifier: spec, sel: "default" });
3318
+ } else if (c.type === "namespace_import") {
3319
+ const id = c.namedChild(0);
3320
+ if (id?.type === "identifier") {
3321
+ if (spec === "express") addFromExpress(id.text, "namespace", "namespace");
3322
+ else bindings.push({ local: id.text, specifier: spec, sel: "namespace" });
3323
+ }
3324
+ } else if (c.type === "named_imports") {
3325
+ for (let j = 0; j < c.namedChildCount; j++) {
3326
+ const s = c.namedChild(j);
3327
+ if (s?.type !== "import_specifier") continue;
3328
+ const name = s.childForFieldName("name")?.text;
3329
+ if (!name) continue;
3330
+ const local = s.childForFieldName("alias")?.text ?? name;
3331
+ if (spec === "express") addFromExpress(local, name, name);
3332
+ else bindings.push({ local, specifier: spec, sel: name });
3333
+ }
3334
+ }
3335
+ }
3336
+ return;
3337
+ }
3338
+ if (node.type === "variable_declarator") {
3339
+ const value = node.childForFieldName("value");
3340
+ if (value?.type !== "call_expression") return;
3341
+ const fn = value.childForFieldName("function");
3342
+ if (fn?.type !== "identifier" || fn.text !== "require") return;
3343
+ const arg = namedArgs(value.childForFieldName("arguments"))[0];
3344
+ const spec = arg ? staticStringText(arg) : null;
3345
+ if (!spec) return;
3346
+ const name = node.childForFieldName("name");
3347
+ if (name?.type === "identifier") {
3348
+ if (spec === "express") expressLocals.add(name.text);
3349
+ else bindings.push({ local: name.text, specifier: spec, sel: "default" });
3350
+ } else if (name?.type === "object_pattern") {
3351
+ for (let i = 0; i < name.namedChildCount; i++) {
3352
+ const el = name.namedChild(i);
3353
+ if (!el) continue;
3354
+ let local;
3355
+ let exported;
3356
+ if (el.type === "shorthand_property_identifier_pattern") {
3357
+ local = el.text;
3358
+ exported = el.text;
3359
+ } else if (el.type === "pair_pattern") {
3360
+ exported = el.childForFieldName("key")?.text;
3361
+ local = el.childForFieldName("value")?.text ?? exported;
3362
+ }
3363
+ if (!local || !exported) continue;
3364
+ if (spec === "express") addFromExpress(local, exported, exported);
3365
+ else bindings.push({ local, specifier: spec, sel: exported });
3366
+ }
3367
+ }
3368
+ }
3369
+ });
3370
+ return { expressLocals, routerCtors, bindings };
3371
+ }
3372
+ function analyzeExpressFile(root, dir) {
3373
+ const { expressLocals, routerCtors, bindings } = collectExpressImports(root);
3374
+ const routerVars = /* @__PURE__ */ new Map();
3375
+ const appVars = /* @__PURE__ */ new Set();
3376
+ const exportNamed = /* @__PURE__ */ new Map();
3377
+ let exportDefaultName = null;
3378
+ const getVar = (name) => {
3379
+ let rv = routerVars.get(name);
3380
+ if (!rv) {
3381
+ rv = { declares: false, mounts: [] };
3382
+ routerVars.set(name, rv);
3383
+ }
3384
+ return rv;
3385
+ };
3386
+ const refFromExpr = (expr, key) => {
3387
+ if (expr.type === "identifier") return expr.text;
3388
+ const u = unwrapRouterExpr(expr, expressLocals, routerCtors);
3389
+ if (!u) return null;
3390
+ const rv = getVar(key);
3391
+ for (const m of u.mounts) rv.mounts.push(m);
3392
+ if (typeof u.base === "object") rv.aliasOf = u.base.alias;
3393
+ return key;
3394
+ };
3395
+ const isExported = (declarator) => {
3396
+ const decl = declarator.parent;
3397
+ return decl?.parent?.type === "export_statement";
3398
+ };
3399
+ walk(root, (node) => {
3400
+ if (node.type === "variable_declarator") {
3401
+ const value = node.childForFieldName("value");
3402
+ const name = node.childForFieldName("name");
3403
+ if (name?.type !== "identifier" || !value) return;
3404
+ if (value.type === "call_expression") {
3405
+ const fn = value.childForFieldName("function");
3406
+ if (fn?.type === "identifier" && fn.text === "require") return;
3407
+ }
3408
+ const u = unwrapRouterExpr(value, expressLocals, routerCtors);
3409
+ if (!u) return;
3410
+ const rv = getVar(name.text);
3411
+ for (const m of u.mounts) rv.mounts.push(m);
3412
+ if (u.base === "app") appVars.add(name.text);
3413
+ else if (typeof u.base === "object") rv.aliasOf = u.base.alias;
3414
+ if (isExported(node)) exportNamed.set(name.text, name.text);
3415
+ return;
3416
+ }
3417
+ if (node.type === "call_expression") {
3418
+ const fn = node.childForFieldName("function");
3419
+ if (fn?.type !== "member_expression") return;
3420
+ const obj = fn.childForFieldName("object");
3421
+ const prop = fn.childForFieldName("property")?.text;
3422
+ if (obj?.type !== "identifier" || !prop) return;
3423
+ if (prop === "use") {
3424
+ const m = parseUseMount(node);
3425
+ if (m) getVar(obj.text).mounts.push(m);
3426
+ } else if (ROUTER_METHODS.has(prop.toLowerCase())) {
3427
+ const first = namedArgs(node.childForFieldName("arguments"))[0];
3428
+ const p = first ? staticStringText(first) : null;
3429
+ if (p !== null && p.startsWith("/")) getVar(obj.text).declares = true;
3430
+ }
3431
+ return;
3432
+ }
3433
+ if (node.type === "export_statement") {
3434
+ let clause = null;
3435
+ for (let i = 0; i < node.namedChildCount; i++) {
3436
+ const c = node.namedChild(i);
3437
+ if (c?.type === "export_clause") clause = c;
3438
+ }
3439
+ if (clause) {
3440
+ for (let i = 0; i < clause.namedChildCount; i++) {
3441
+ const spec = clause.namedChild(i);
3442
+ if (spec?.type !== "export_specifier") continue;
3443
+ const local = spec.childForFieldName("name")?.text;
3444
+ if (!local) continue;
3445
+ const exportedAs = spec.childForFieldName("alias")?.text ?? local;
3446
+ if (exportedAs === "default") exportDefaultName = local;
3447
+ else exportNamed.set(exportedAs, local);
3448
+ }
3449
+ return;
3450
+ }
3451
+ if (node.childForFieldName("declaration")) return;
3452
+ for (let i = 0; i < node.namedChildCount; i++) {
3453
+ const c = node.namedChild(i);
3454
+ if (c && c.type !== "export_clause") {
3455
+ exportDefaultName = refFromExpr(c, "#default");
3456
+ break;
3457
+ }
3458
+ }
3459
+ return;
3460
+ }
3461
+ if (node.type === "assignment_expression") {
3462
+ const left = node.childForFieldName("left");
3463
+ const right = node.childForFieldName("right");
3464
+ if (left?.type !== "member_expression" || !right) return;
3465
+ const lobj = left.childForFieldName("object")?.text;
3466
+ const lprop = left.childForFieldName("property")?.text;
3467
+ if (lobj === "module" && lprop === "exports") exportDefaultName = refFromExpr(right, "#default");
3468
+ else if (lobj === "exports" && lprop) {
3469
+ const key = refFromExpr(right, `#exp:${lprop}`);
3470
+ if (key) exportNamed.set(lprop, key);
3471
+ }
3472
+ }
3473
+ });
3474
+ return {
3475
+ dir,
3476
+ routerVars,
3477
+ appVars,
3478
+ exportDefaultName,
3479
+ exportNamed,
3480
+ rawBindings: bindings,
3481
+ importedRouters: /* @__PURE__ */ new Map()
3482
+ };
3483
+ }
3484
+ async function expressMountPrefixes(files, serviceDir, tsPaths) {
3485
+ const jsParser = makeJsParser2();
3486
+ const fileInfo = /* @__PURE__ */ new Map();
3487
+ for (const f of files) {
3488
+ if (!JS_ROUTE_EXTENSIONS.has(import_node_path9.default.extname(f.path))) continue;
3489
+ if (isTestPath(f.path)) continue;
3490
+ const rel = toPosix(import_node_path9.default.relative(serviceDir, f.path));
3491
+ try {
3492
+ const tree = parseSource2(jsParser, f.content);
3493
+ fileInfo.set(rel, analyzeExpressFile(tree.rootNode, import_node_path9.default.dirname(f.path)));
3494
+ } catch {
3495
+ }
3496
+ }
3497
+ if (fileInfo.size === 0) return /* @__PURE__ */ new Map();
3498
+ for (const info of fileInfo.values()) {
3499
+ for (const b of info.rawBindings) {
3500
+ const resolved = await resolveJsImport(b.specifier, info.dir, serviceDir, tsPaths);
3501
+ if (!resolved || !fileInfo.has(resolved)) continue;
3502
+ info.importedRouters.set(b.local, { file: resolved, sel: b.sel === "namespace" ? "default" : b.sel });
3503
+ }
3504
+ }
3505
+ const resolveTarget = (name, file) => {
3506
+ const info = fileInfo.get(file);
3507
+ if (!info) return null;
3508
+ if (info.routerVars.has(name)) return { file, name };
3509
+ const imp = info.importedRouters.get(name);
3510
+ if (!imp) return null;
3511
+ const target = fileInfo.get(imp.file);
3512
+ if (!target) return null;
3513
+ const key = imp.sel === "default" ? target.exportDefaultName : target.exportNamed.get(imp.sel);
3514
+ if (!key) return null;
3515
+ return { file: imp.file, name: key };
3516
+ };
3517
+ const filePrefix = /* @__PURE__ */ new Map();
3518
+ const conflicted = /* @__PURE__ */ new Set();
3519
+ const apply = (file, prefix) => {
3520
+ if (conflicted.has(file)) return;
3521
+ const existing = filePrefix.get(file);
3522
+ if (existing === void 0) filePrefix.set(file, prefix);
3523
+ else if (existing !== prefix) {
3524
+ filePrefix.delete(file);
3525
+ conflicted.add(file);
3526
+ }
3527
+ };
3528
+ const visited = /* @__PURE__ */ new Set();
3529
+ const collect = (file, name, accPrefix) => {
3530
+ const key = `${file}|${name}|${accPrefix}`;
3531
+ if (visited.has(key)) return;
3532
+ visited.add(key);
3533
+ const info = fileInfo.get(file);
3534
+ const rv = info?.routerVars.get(name);
3535
+ if (!info || !rv) return;
3536
+ if (rv.declares && info.appVars.size === 0) apply(file, accPrefix);
3537
+ for (const m of rv.mounts) {
3538
+ if (!m.target) continue;
3539
+ const t = resolveTarget(m.target, file);
3540
+ if (t) collect(t.file, t.name, accPrefix + m.prefix);
3541
+ }
3542
+ if (rv.aliasOf) {
3543
+ const t = resolveTarget(rv.aliasOf, file);
3544
+ if (t) collect(t.file, t.name, accPrefix);
3545
+ }
3546
+ };
3547
+ for (const [rel, info] of fileInfo) {
3548
+ for (const appVar of info.appVars) collect(rel, appVar, "");
3549
+ }
3550
+ const out = /* @__PURE__ */ new Map();
3551
+ for (const [file, prefix] of filePrefix) if (prefix && prefix !== "/") out.set(file, prefix);
3552
+ return out;
3553
+ }
2859
3554
  async function addRoutes(graph, services) {
2860
- const jsParser = makeJsParser();
2861
- const pyParser = makePyParser();
2862
- const goParser = makeGoParser();
3555
+ const jsParser = makeJsParser2();
3556
+ const pyParser = makePyParser2();
3557
+ const goParser = makeGoParser2();
2863
3558
  let nodesAdded = 0;
2864
3559
  let edgesAdded = 0;
2865
3560
  for (const service of services) {
@@ -2879,13 +3574,14 @@ async function addRoutes(graph, services) {
2879
3574
  if (!hasExpress && !hasFastify && !hasHono && !hasNext && !hasNestjs && !hasFastapi && !hasFlask && !hasDjango && !hasGin)
2880
3575
  continue;
2881
3576
  const files = await loadSourceFiles(service.dir);
3577
+ const mountPrefixes = hasExpress ? await expressMountPrefixes(files, service.dir, await loadTsPathConfig(service.dir)) : /* @__PURE__ */ new Map();
2882
3578
  for (const file of files) {
2883
3579
  if (isTestPath(file.path)) continue;
2884
- const ext = import_node_path8.default.extname(file.path);
3580
+ const ext = import_node_path9.default.extname(file.path);
2885
3581
  const isPy = ext === ".py";
2886
3582
  const isGo = ext === ".go";
2887
3583
  if (!JS_ROUTE_EXTENSIONS.has(ext) && !isPy && !isGo) continue;
2888
- const relFile = toPosix(import_node_path8.default.relative(service.dir, file.path));
3584
+ const relFile = toPosix(import_node_path9.default.relative(service.dir, file.path));
2889
3585
  let routes;
2890
3586
  try {
2891
3587
  if (isGo) {
@@ -2907,16 +3603,18 @@ async function addRoutes(graph, services) {
2907
3603
  continue;
2908
3604
  }
2909
3605
  if (routes.length === 0) continue;
3606
+ const mountPrefix = mountPrefixes.get(relFile);
2910
3607
  for (const route of routes) {
2911
- const rid = (0, import_types5.routeId)(service.pkg.name, route.method, route.pathTemplate);
3608
+ const pathTemplate = mountPrefix ? canonicalizeTemplate(mountPrefix + route.pathTemplate) : route.pathTemplate;
3609
+ const rid = (0, import_types6.routeId)(service.pkg.name, route.method, pathTemplate);
2912
3610
  if (!graph.hasNode(rid)) {
2913
3611
  const node = {
2914
3612
  id: rid,
2915
- type: import_types5.NodeType.RouteNode,
2916
- name: `${route.method} ${route.pathTemplate}`,
3613
+ type: import_types6.NodeType.RouteNode,
3614
+ name: `${route.method} ${pathTemplate}`,
2917
3615
  service: service.pkg.name,
2918
3616
  method: route.method,
2919
- pathTemplate: route.pathTemplate,
3617
+ pathTemplate,
2920
3618
  path: relFile,
2921
3619
  line: route.line,
2922
3620
  framework: route.framework,
@@ -2925,15 +3623,15 @@ async function addRoutes(graph, services) {
2925
3623
  graph.addNode(rid, node);
2926
3624
  nodesAdded++;
2927
3625
  }
2928
- const containsId = (0, import_types5.extractedEdgeId)(service.node.id, rid, import_types5.EdgeType.CONTAINS);
3626
+ const containsId = (0, import_types6.extractedEdgeId)(service.node.id, rid, import_types6.EdgeType.CONTAINS);
2929
3627
  if (!graph.hasEdge(containsId)) {
2930
3628
  const edge = {
2931
3629
  id: containsId,
2932
3630
  source: service.node.id,
2933
3631
  target: rid,
2934
- type: import_types5.EdgeType.CONTAINS,
2935
- provenance: import_types5.Provenance.EXTRACTED,
2936
- confidence: (0, import_types5.confidenceForExtracted)("structural"),
3632
+ type: import_types6.EdgeType.CONTAINS,
3633
+ provenance: import_types6.Provenance.EXTRACTED,
3634
+ confidence: (0, import_types6.confidenceForExtracted)("structural"),
2937
3635
  evidence: {
2938
3636
  file: relFile,
2939
3637
  line: route.line,
@@ -3169,7 +3867,7 @@ function languageForExt(relPath) {
3169
3867
  function relPathForRuntimeFile(filepath, serviceNode, scanPath) {
3170
3868
  let p = toPosix2(filepath).replace(/^file:\/\//, "");
3171
3869
  if (scanPath && scanPath.length > 0) {
3172
- const absRoot = toPosix2(import_node_path9.default.resolve(scanPath, serviceNode?.repoPath ?? ""));
3870
+ const absRoot = toPosix2(import_node_path10.default.resolve(scanPath, serviceNode?.repoPath ?? ""));
3173
3871
  const anchor = absRoot.endsWith("/") ? absRoot : `${absRoot}/`;
3174
3872
  if (p.startsWith(anchor)) return p.slice(anchor.length);
3175
3873
  }
@@ -3197,10 +3895,10 @@ function resolveDistToSrc(absFilepath, line) {
3197
3895
  entry = null;
3198
3896
  const mapPath = `${absFilepath}.map`;
3199
3897
  try {
3200
- if ((0, import_node_fs8.existsSync)(mapPath)) {
3201
- const raw = JSON.parse((0, import_node_fs8.readFileSync)(mapPath, "utf8"));
3898
+ if ((0, import_node_fs9.existsSync)(mapPath)) {
3899
+ const raw = JSON.parse((0, import_node_fs9.readFileSync)(mapPath, "utf8"));
3202
3900
  const consumer = new sourceMapJs.SourceMapConsumer(raw);
3203
- entry = { consumer, dir: import_node_path9.default.dirname(mapPath) };
3901
+ entry = { consumer, dir: import_node_path10.default.dirname(mapPath) };
3204
3902
  }
3205
3903
  } catch {
3206
3904
  entry = null;
@@ -3215,7 +3913,7 @@ function resolveDistToSrc(absFilepath, line) {
3215
3913
  });
3216
3914
  if (!pos || !pos.source) return null;
3217
3915
  const root = entry.consumer.sourceRoot ?? "";
3218
- const resolved = import_node_path9.default.resolve(entry.dir, root, pos.source);
3916
+ const resolved = import_node_path10.default.resolve(entry.dir, root, pos.source);
3219
3917
  return { filepath: resolved, ...pos.line ? { line: pos.line } : {} };
3220
3918
  } catch {
3221
3919
  return null;
@@ -3248,11 +3946,11 @@ function callSiteFromSpan(span, serviceNode, scanPath) {
3248
3946
  };
3249
3947
  }
3250
3948
  function reconcileObservedRelPath(graph, serviceName, relPath) {
3251
- if (graph.hasNode((0, import_types6.fileId)(serviceName, relPath))) return relPath;
3949
+ if (graph.hasNode((0, import_types7.fileId)(serviceName, relPath))) return relPath;
3252
3950
  let best = null;
3253
3951
  graph.forEachNode((_id, attrs) => {
3254
3952
  const a = attrs;
3255
- if (a.type !== import_types6.NodeType.FileNode || a.service !== serviceName) return;
3953
+ if (a.type !== import_types7.NodeType.FileNode || a.service !== serviceName) return;
3256
3954
  if (a.discoveredVia === "otel") return;
3257
3955
  const p = a.path;
3258
3956
  if (!p) return;
@@ -3264,14 +3962,14 @@ function reconcileObservedRelPath(graph, serviceName, relPath) {
3264
3962
  }
3265
3963
  function ensureObservedFileNode(graph, serviceName, serviceNodeId, callSite) {
3266
3964
  const svcAttrs = graph.hasNode(serviceNodeId) ? graph.getNodeAttributes(serviceNodeId) : void 0;
3267
- const canonicalService = svcAttrs && svcAttrs.type === import_types6.NodeType.ServiceNode && typeof svcAttrs.name === "string" ? svcAttrs.name : serviceName;
3965
+ const canonicalService = svcAttrs && svcAttrs.type === import_types7.NodeType.ServiceNode && typeof svcAttrs.name === "string" ? svcAttrs.name : serviceName;
3268
3966
  const relPath = reconcileObservedRelPath(graph, canonicalService, callSite.relPath);
3269
- const fileNodeId = (0, import_types6.fileId)(canonicalService, relPath);
3967
+ const fileNodeId = (0, import_types7.fileId)(canonicalService, relPath);
3270
3968
  if (!graph.hasNode(fileNodeId)) {
3271
3969
  const language = languageForExt(relPath);
3272
3970
  const node = {
3273
3971
  id: fileNodeId,
3274
- type: import_types6.NodeType.FileNode,
3972
+ type: import_types7.NodeType.FileNode,
3275
3973
  service: canonicalService,
3276
3974
  path: relPath,
3277
3975
  ...language ? { language } : {},
@@ -3280,14 +3978,14 @@ function ensureObservedFileNode(graph, serviceName, serviceNodeId, callSite) {
3280
3978
  };
3281
3979
  graph.addNode(fileNodeId, node);
3282
3980
  }
3283
- const containsId = makeObservedEdgeId(import_types6.EdgeType.CONTAINS, serviceNodeId, fileNodeId);
3981
+ const containsId = makeObservedEdgeId(import_types7.EdgeType.CONTAINS, serviceNodeId, fileNodeId);
3284
3982
  if (!graph.hasEdge(containsId)) {
3285
3983
  const edge = {
3286
3984
  id: containsId,
3287
3985
  source: serviceNodeId,
3288
3986
  target: fileNodeId,
3289
- type: import_types6.EdgeType.CONTAINS,
3290
- provenance: import_types6.Provenance.OBSERVED
3987
+ type: import_types7.EdgeType.CONTAINS,
3988
+ provenance: import_types7.Provenance.OBSERVED
3291
3989
  };
3292
3990
  graph.addEdgeWithKey(containsId, serviceNodeId, fileNodeId, edge);
3293
3991
  }
@@ -3311,11 +4009,11 @@ function pickContainingSymbol(candidates, fn) {
3311
4009
  return [...candidates].sort(bySpan)[0].id;
3312
4010
  }
3313
4011
  function ensureObservedSymbolNode(graph, fileNodeId, service, relPath, fn, line) {
3314
- const sid = (0, import_types6.symbolId)(service, relPath, fn);
4012
+ const sid = (0, import_types7.symbolId)(service, relPath, fn);
3315
4013
  if (!graph.hasNode(sid)) {
3316
4014
  const node = {
3317
4015
  id: sid,
3318
- type: import_types6.NodeType.SymbolNode,
4016
+ type: import_types7.NodeType.SymbolNode,
3319
4017
  kind: "function",
3320
4018
  qualname: fn,
3321
4019
  span: { startLine: line, endLine: line },
@@ -3325,14 +4023,14 @@ function ensureObservedSymbolNode(graph, fileNodeId, service, relPath, fn, line)
3325
4023
  };
3326
4024
  graph.addNode(sid, node);
3327
4025
  }
3328
- const containsId = makeObservedEdgeId(import_types6.EdgeType.CONTAINS, fileNodeId, sid);
4026
+ const containsId = makeObservedEdgeId(import_types7.EdgeType.CONTAINS, fileNodeId, sid);
3329
4027
  if (!graph.hasEdge(containsId)) {
3330
4028
  const edge = {
3331
4029
  id: containsId,
3332
4030
  source: fileNodeId,
3333
4031
  target: sid,
3334
- type: import_types6.EdgeType.CONTAINS,
3335
- provenance: import_types6.Provenance.OBSERVED
4032
+ type: import_types7.EdgeType.CONTAINS,
4033
+ provenance: import_types7.Provenance.OBSERVED
3336
4034
  };
3337
4035
  graph.addEdgeWithKey(containsId, fileNodeId, sid, edge);
3338
4036
  }
@@ -3344,9 +4042,9 @@ function landObservedSymbol(graph, fileNodeId, service, relPath, callSite) {
3344
4042
  let sawSymbol = false;
3345
4043
  const candidates = [];
3346
4044
  graph.forEachOutboundEdge(fileNodeId, (_edge, edgeAttrs, _source, target) => {
3347
- if (edgeAttrs.type !== import_types6.EdgeType.CONTAINS) return;
4045
+ if (edgeAttrs.type !== import_types7.EdgeType.CONTAINS) return;
3348
4046
  const t = graph.getNodeAttributes(target);
3349
- if (t.type !== import_types6.NodeType.SymbolNode) return;
4047
+ if (t.type !== import_types7.NodeType.SymbolNode) return;
3350
4048
  sawSymbol = true;
3351
4049
  if (line >= t.span.startLine && line <= t.span.endLine) {
3352
4050
  candidates.push({ id: target, symbol: t });
@@ -3359,17 +4057,17 @@ function landObservedSymbol(graph, fileNodeId, service, relPath, callSite) {
3359
4057
  return fileNodeId;
3360
4058
  }
3361
4059
  function makeObservedEdgeId(type, source, target) {
3362
- return (0, import_types6.observedEdgeId)(source, target, type);
4060
+ return (0, import_types7.observedEdgeId)(source, target, type);
3363
4061
  }
3364
4062
  function makeInferredEdgeId(type, source, target) {
3365
- return (0, import_types6.inferredEdgeId)(source, target, type);
4063
+ return (0, import_types7.inferredEdgeId)(source, target, type);
3366
4064
  }
3367
4065
  var INFERRED_CONFIDENCE = 0.6;
3368
4066
  var STITCH_MAX_DEPTH = 2;
3369
4067
  var STITCH_EDGE_TYPES = /* @__PURE__ */ new Set([
3370
- import_types6.EdgeType.CALLS,
3371
- import_types6.EdgeType.CONNECTS_TO,
3372
- import_types6.EdgeType.DEPENDS_ON
4068
+ import_types7.EdgeType.CALLS,
4069
+ import_types7.EdgeType.CONNECTS_TO,
4070
+ import_types7.EdgeType.DEPENDS_ON
3373
4071
  ]);
3374
4072
  var WIRE_SPAN_KIND_CLIENT = 3;
3375
4073
  var WIRE_SPAN_KIND_PRODUCER = 4;
@@ -3385,11 +4083,11 @@ function spanServesGraphqlOperation(kind) {
3385
4083
  return kind !== WIRE_SPAN_KIND_CLIENT && kind !== WIRE_SPAN_KIND_PRODUCER && kind !== WIRE_SPAN_KIND_CONSUMER;
3386
4084
  }
3387
4085
  function ensureGraphqlOperationNode(graph, serviceName, operationType, operationName) {
3388
- const id = (0, import_types6.graphqlOperationId)(serviceName, operationType, operationName);
4086
+ const id = (0, import_types7.graphqlOperationId)(serviceName, operationType, operationName);
3389
4087
  if (graph.hasNode(id)) return id;
3390
4088
  const node = {
3391
4089
  id,
3392
- type: import_types6.NodeType.GraphQLOperationNode,
4090
+ type: import_types7.NodeType.GraphQLOperationNode,
3393
4091
  name: operationName,
3394
4092
  service: serviceName,
3395
4093
  operationType: operationType.toLowerCase(),
@@ -3403,11 +4101,11 @@ function spanServesGrpcMethod(kind) {
3403
4101
  return kind !== WIRE_SPAN_KIND_CLIENT && kind !== WIRE_SPAN_KIND_PRODUCER && kind !== WIRE_SPAN_KIND_CONSUMER;
3404
4102
  }
3405
4103
  function ensureGrpcMethodNode(graph, rpcService, rpcMethod) {
3406
- const id = (0, import_types6.grpcMethodId)(rpcService, rpcMethod);
4104
+ const id = (0, import_types7.grpcMethodId)(rpcService, rpcMethod);
3407
4105
  if (graph.hasNode(id)) return id;
3408
4106
  const node = {
3409
4107
  id,
3410
- type: import_types6.NodeType.GrpcMethodNode,
4108
+ type: import_types7.NodeType.GrpcMethodNode,
3411
4109
  name: `${rpcService}/${rpcMethod}`,
3412
4110
  rpcService,
3413
4111
  rpcMethod,
@@ -3420,11 +4118,11 @@ function spanServesWebsocketChannel(kind) {
3420
4118
  return kind !== WIRE_SPAN_KIND_CLIENT && kind !== WIRE_SPAN_KIND_PRODUCER && kind !== WIRE_SPAN_KIND_CONSUMER;
3421
4119
  }
3422
4120
  function ensureWebsocketChannelNode(graph, serviceName, channel) {
3423
- const id = (0, import_types6.websocketChannelId)(serviceName, channel);
4121
+ const id = (0, import_types7.websocketChannelId)(serviceName, channel);
3424
4122
  if (graph.hasNode(id)) return id;
3425
4123
  const node = {
3426
4124
  id,
3427
- type: import_types6.NodeType.WebSocketChannelNode,
4125
+ type: import_types7.NodeType.WebSocketChannelNode,
3428
4126
  name: channel,
3429
4127
  service: serviceName,
3430
4128
  channel,
@@ -3437,11 +4135,11 @@ function messagingDestinationKind(system) {
3437
4135
  return `${system}-topic`;
3438
4136
  }
3439
4137
  function ensureMessagingDestinationNode(graph, system, destination) {
3440
- const id = (0, import_types6.infraId)(messagingDestinationKind(system), destination);
4138
+ const id = (0, import_types7.infraId)(messagingDestinationKind(system), destination);
3441
4139
  if (graph.hasNode(id)) return id;
3442
4140
  const node = {
3443
4141
  id,
3444
- type: import_types6.NodeType.InfraNode,
4142
+ type: import_types7.NodeType.InfraNode,
3445
4143
  name: destination,
3446
4144
  provider: "self",
3447
4145
  kind: messagingDestinationKind(system)
@@ -3485,9 +4183,9 @@ function lookupParentSpan(traceId, parentSpanId, now) {
3485
4183
  };
3486
4184
  }
3487
4185
  function resolveServiceId(graph, host, env) {
3488
- const envTagged = (0, import_types6.serviceId)(host, env);
4186
+ const envTagged = (0, import_types7.serviceId)(host, env);
3489
4187
  if (graph.hasNode(envTagged)) return envTagged;
3490
- const envLess = (0, import_types6.serviceId)(host);
4188
+ const envLess = (0, import_types7.serviceId)(host);
3491
4189
  if (envLess !== envTagged && graph.hasNode(envLess)) return envLess;
3492
4190
  let sameEnv = null;
3493
4191
  let envLessMatch = null;
@@ -3495,7 +4193,7 @@ function resolveServiceId(graph, host, env) {
3495
4193
  graph.forEachNode((id, attrs) => {
3496
4194
  if (sameEnv) return;
3497
4195
  const a = attrs;
3498
- if (a.type !== import_types6.NodeType.ServiceNode) return;
4196
+ if (a.type !== import_types7.NodeType.ServiceNode) return;
3499
4197
  const matchesByName = a.name === host;
3500
4198
  const matchesByAlias = a.aliases ? a.aliases.includes(host) : false;
3501
4199
  if (!matchesByName && !matchesByAlias) return;
@@ -3510,14 +4208,14 @@ function resolveServiceId(graph, host, env) {
3510
4208
  return sameEnv ?? envLessMatch ?? anyMatch;
3511
4209
  }
3512
4210
  function frontierIdFor(host) {
3513
- return (0, import_types6.frontierId)(host);
4211
+ return (0, import_types7.frontierId)(host);
3514
4212
  }
3515
4213
  function ensureServiceNode(graph, serviceName, env) {
3516
- const id = (0, import_types6.serviceId)(serviceName, env);
4214
+ const id = (0, import_types7.serviceId)(serviceName, env);
3517
4215
  if (graph.hasNode(id)) return id;
3518
4216
  const wanted = serviceName.toLowerCase();
3519
4217
  const extractedId = graph.findNode((_nid, attrs) => {
3520
- if (attrs.type !== import_types6.NodeType.ServiceNode) return false;
4218
+ if (attrs.type !== import_types7.NodeType.ServiceNode) return false;
3521
4219
  const svc = attrs;
3522
4220
  if (svc.discoveredVia === "otel") return false;
3523
4221
  return typeof svc.name === "string" && svc.name.toLowerCase() === wanted;
@@ -3525,7 +4223,7 @@ function ensureServiceNode(graph, serviceName, env) {
3525
4223
  if (extractedId) return extractedId;
3526
4224
  const node = {
3527
4225
  id,
3528
- type: import_types6.NodeType.ServiceNode,
4226
+ type: import_types7.NodeType.ServiceNode,
3529
4227
  name: serviceName,
3530
4228
  language: "unknown",
3531
4229
  discoveredVia: "otel",
@@ -3535,11 +4233,11 @@ function ensureServiceNode(graph, serviceName, env) {
3535
4233
  return id;
3536
4234
  }
3537
4235
  function ensureInfraNode(graph, kind, name, provider) {
3538
- const id = (0, import_types6.infraId)(kind, name);
4236
+ const id = (0, import_types7.infraId)(kind, name);
3539
4237
  if (graph.hasNode(id)) return id;
3540
4238
  const node = {
3541
4239
  id,
3542
- type: import_types6.NodeType.InfraNode,
4240
+ type: import_types7.NodeType.InfraNode,
3543
4241
  name,
3544
4242
  provider,
3545
4243
  kind
@@ -3551,7 +4249,7 @@ var COLUMN_BEARING_INFRA_KINDS = /* @__PURE__ */ new Set(["sql-table", "supabase
3551
4249
  function mergeColumnsAt(graph, tableNodeId, columns, provenance, confidence) {
3552
4250
  if (!columns || columns.length === 0 || !graph.hasNode(tableNodeId)) return;
3553
4251
  const node = graph.getNodeAttributes(tableNodeId);
3554
- if (node.type !== import_types6.NodeType.InfraNode || !node.kind || !COLUMN_BEARING_INFRA_KINDS.has(node.kind)) {
4252
+ if (node.type !== import_types7.NodeType.InfraNode || !node.kind || !COLUMN_BEARING_INFRA_KINDS.has(node.kind)) {
3555
4253
  return;
3556
4254
  }
3557
4255
  graph.replaceNodeAttributes(tableNodeId, {
@@ -3560,14 +4258,14 @@ function mergeColumnsAt(graph, tableNodeId, columns, provenance, confidence) {
3560
4258
  });
3561
4259
  }
3562
4260
  function mergeObservedColumns(graph, tableNodeId, columns) {
3563
- mergeColumnsAt(graph, tableNodeId, columns, import_types6.Provenance.OBSERVED, OBSERVED_COLUMN_CONFIDENCE);
4261
+ mergeColumnsAt(graph, tableNodeId, columns, import_types7.Provenance.OBSERVED, OBSERVED_COLUMN_CONFIDENCE);
3564
4262
  }
3565
4263
  function ensureDatabaseNode(graph, host, engine) {
3566
- const id = (0, import_types6.databaseId)(host);
4264
+ const id = (0, import_types7.databaseId)(host);
3567
4265
  if (graph.hasNode(id)) return id;
3568
4266
  const node = {
3569
4267
  id,
3570
- type: import_types6.NodeType.DatabaseNode,
4268
+ type: import_types7.NodeType.DatabaseNode,
3571
4269
  name: host,
3572
4270
  engine,
3573
4271
  engineVersion: "unknown",
@@ -3579,11 +4277,11 @@ function ensureDatabaseNode(graph, host, engine) {
3579
4277
  return id;
3580
4278
  }
3581
4279
  function ensureLocalDatabaseNode(graph, serviceName, name, engine) {
3582
- const id = (0, import_types6.localDatabaseId)(serviceName, name);
4280
+ const id = (0, import_types7.localDatabaseId)(serviceName, name);
3583
4281
  if (graph.hasNode(id)) return id;
3584
4282
  const node = {
3585
4283
  id,
3586
- type: import_types6.NodeType.DatabaseNode,
4284
+ type: import_types7.NodeType.DatabaseNode,
3587
4285
  name,
3588
4286
  engine,
3589
4287
  engineVersion: "unknown",
@@ -3598,17 +4296,17 @@ function findDeclaredDatabaseForService(graph, serviceNodeId, engine) {
3598
4296
  const sources = [serviceNodeId];
3599
4297
  for (const edgeId of graph.outboundEdges(serviceNodeId)) {
3600
4298
  const e = graph.getEdgeAttributes(edgeId);
3601
- if (e.type === import_types6.EdgeType.CONTAINS) sources.push(e.target);
4299
+ if (e.type === import_types7.EdgeType.CONTAINS) sources.push(e.target);
3602
4300
  }
3603
4301
  const matches = /* @__PURE__ */ new Set();
3604
4302
  for (const src of sources) {
3605
4303
  if (!graph.hasNode(src)) continue;
3606
4304
  for (const edgeId of graph.outboundEdges(src)) {
3607
4305
  const edge = graph.getEdgeAttributes(edgeId);
3608
- if (edge.type !== import_types6.EdgeType.CONNECTS_TO || edge.provenance !== import_types6.Provenance.EXTRACTED) continue;
4306
+ if (edge.type !== import_types7.EdgeType.CONNECTS_TO || edge.provenance !== import_types7.Provenance.EXTRACTED) continue;
3609
4307
  if (!graph.hasNode(edge.target)) continue;
3610
4308
  const target = graph.getNodeAttributes(edge.target);
3611
- if (target.type !== import_types6.NodeType.DatabaseNode || target.engine !== engine) continue;
4309
+ if (target.type !== import_types7.NodeType.DatabaseNode || target.engine !== engine) continue;
3612
4310
  matches.add(edge.target);
3613
4311
  }
3614
4312
  }
@@ -3623,7 +4321,7 @@ function ensureFrontierNode(graph, host, ts) {
3623
4321
  }
3624
4322
  const node = {
3625
4323
  id,
3626
- type: import_types6.NodeType.FrontierNode,
4324
+ type: import_types7.NodeType.FrontierNode,
3627
4325
  name: host,
3628
4326
  host,
3629
4327
  firstObserved: ts,
@@ -3647,11 +4345,11 @@ function upsertObservedEdge(graph, type, source, target, ts, isError = false, ev
3647
4345
  };
3648
4346
  const updated = {
3649
4347
  ...existing,
3650
- provenance: import_types6.Provenance.OBSERVED,
4348
+ provenance: import_types7.Provenance.OBSERVED,
3651
4349
  lastObserved: ts,
3652
4350
  callCount: newSpanCount,
3653
4351
  signal: newSignal,
3654
- confidence: (0, import_types6.confidenceForObservedSignal)(newSignal),
4352
+ confidence: (0, import_types7.confidenceForObservedSignal)(newSignal),
3655
4353
  grain
3656
4354
  // backfills legacy edges that predate ADR-142
3657
4355
  };
@@ -3668,8 +4366,8 @@ function upsertObservedEdge(graph, type, source, target, ts, isError = false, ev
3668
4366
  source,
3669
4367
  target,
3670
4368
  type,
3671
- provenance: import_types6.Provenance.OBSERVED,
3672
- confidence: (0, import_types6.confidenceForObservedSignal)(signal),
4369
+ provenance: import_types7.Provenance.OBSERVED,
4370
+ confidence: (0, import_types7.confidenceForObservedSignal)(signal),
3673
4371
  lastObserved: ts,
3674
4372
  callCount: 1,
3675
4373
  signal,
@@ -3691,9 +4389,9 @@ function stitchTrace(graph, sourceServiceId, ts) {
3691
4389
  const outbound = graph.outboundEdges(nodeId);
3692
4390
  for (const edgeId of outbound) {
3693
4391
  const edge = graph.getEdgeAttributes(edgeId);
3694
- if (edge.provenance !== import_types6.Provenance.EXTRACTED) continue;
4392
+ if (edge.provenance !== import_types7.Provenance.EXTRACTED) continue;
3695
4393
  if (!STITCH_EDGE_TYPES.has(edge.type)) continue;
3696
- if (graph.hasEdge((0, import_types6.observedEdgeId)(edge.source, edge.target, edge.type))) continue;
4394
+ if (graph.hasEdge((0, import_types7.observedEdgeId)(edge.source, edge.target, edge.type))) continue;
3697
4395
  upsertInferredEdge(graph, edge.type, edge.source, edge.target, ts);
3698
4396
  if (!visited.has(edge.target)) {
3699
4397
  visited.add(edge.target);
@@ -3715,23 +4413,23 @@ function upsertInferredEdge(graph, type, source, target, ts) {
3715
4413
  source,
3716
4414
  target,
3717
4415
  type,
3718
- provenance: import_types6.Provenance.INFERRED,
4416
+ provenance: import_types7.Provenance.INFERRED,
3719
4417
  confidence: INFERRED_CONFIDENCE,
3720
4418
  lastObserved: ts
3721
4419
  };
3722
4420
  graph.addEdgeWithKey(id, source, target, edge);
3723
4421
  }
3724
4422
  async function appendErrorEvent(ctx, ev) {
3725
- await import_node_fs8.promises.mkdir(import_node_path9.default.dirname(ctx.errorsPath), { recursive: true });
3726
- await import_node_fs8.promises.appendFile(ctx.errorsPath, JSON.stringify(ev) + "\n", "utf8");
4423
+ await import_node_fs9.promises.mkdir(import_node_path10.default.dirname(ctx.errorsPath), { recursive: true });
4424
+ await import_node_fs9.promises.appendFile(ctx.errorsPath, JSON.stringify(ev) + "\n", "utf8");
3727
4425
  }
3728
4426
  function incidentAffectedNode(span, graph, scanPath) {
3729
- const sid = (0, import_types6.serviceId)(span.service, span.env);
4427
+ const sid = (0, import_types7.serviceId)(span.service, span.env);
3730
4428
  const serviceNode = graph && graph.hasNode(sid) ? graph.getNodeAttributes(sid) : void 0;
3731
4429
  const callSite = callSiteFromSpan(span, serviceNode, scanPath);
3732
4430
  if (callSite) {
3733
4431
  const relPath = graph ? reconcileObservedRelPath(graph, span.service, callSite.relPath) : callSite.relPath;
3734
- return (0, import_types6.fileId)(span.service, relPath);
4432
+ return (0, import_types7.fileId)(span.service, relPath);
3735
4433
  }
3736
4434
  return sid;
3737
4435
  }
@@ -3831,7 +4529,7 @@ function findRouteNodeByHttpRoute(graph, serviceName, method, httpRoute) {
3831
4529
  graph.forEachNode((id, attrs) => {
3832
4530
  if (found) return;
3833
4531
  const a = attrs;
3834
- if (a.type !== import_types6.NodeType.RouteNode || a.service !== serviceName) return;
4532
+ if (a.type !== import_types7.NodeType.RouteNode || a.service !== serviceName) return;
3835
4533
  if (m && a.method !== "ALL" && a.method !== m) return;
3836
4534
  if (normalizePathTemplate(a.pathTemplate) === target) found = id;
3837
4535
  });
@@ -3862,7 +4560,7 @@ async function handleSpan(ctx, span) {
3862
4560
  let targetId;
3863
4561
  if (host) {
3864
4562
  ensureDatabaseNode(ctx.graph, host, span.dbSystem);
3865
- targetId = (0, import_types6.databaseId)(host);
4563
+ targetId = (0, import_types7.databaseId)(host);
3866
4564
  } else {
3867
4565
  const declared = findDeclaredDatabaseForService(ctx.graph, sourceId, span.dbSystem);
3868
4566
  if (declared) {
@@ -3879,7 +4577,7 @@ async function handleSpan(ctx, span) {
3879
4577
  }
3880
4578
  const result = upsertObservedEdge(
3881
4579
  ctx.graph,
3882
- import_types6.EdgeType.CONNECTS_TO,
4580
+ import_types7.EdgeType.CONNECTS_TO,
3883
4581
  observedSource(),
3884
4582
  targetId,
3885
4583
  ts,
@@ -3891,7 +4589,7 @@ async function handleSpan(ctx, span) {
3891
4589
  const collectionId = ensureInfraNode(ctx.graph, "mongodb-collection", span.dbCollection, "self");
3892
4590
  upsertObservedEdge(
3893
4591
  ctx.graph,
3894
- import_types6.EdgeType.CALLS,
4592
+ import_types7.EdgeType.CALLS,
3895
4593
  observedSource(),
3896
4594
  collectionId,
3897
4595
  ts,
@@ -3903,7 +4601,7 @@ async function handleSpan(ctx, span) {
3903
4601
  const tableId = ensureInfraNode(ctx.graph, "sql-table", span.dbTable, "self");
3904
4602
  upsertObservedEdge(
3905
4603
  ctx.graph,
3906
- import_types6.EdgeType.CALLS,
4604
+ import_types7.EdgeType.CALLS,
3907
4605
  observedSource(),
3908
4606
  tableId,
3909
4607
  ts,
@@ -3919,7 +4617,7 @@ async function handleSpan(ctx, span) {
3919
4617
  span.messagingSystem,
3920
4618
  span.messagingDestination
3921
4619
  );
3922
- const edgeType = span.kind === WIRE_SPAN_KIND_CONSUMER ? import_types6.EdgeType.CONSUMES_FROM : import_types6.EdgeType.PUBLISHES_TO;
4620
+ const edgeType = span.kind === WIRE_SPAN_KIND_CONSUMER ? import_types7.EdgeType.CONSUMES_FROM : import_types7.EdgeType.PUBLISHES_TO;
3923
4621
  const result = upsertObservedEdge(
3924
4622
  ctx.graph,
3925
4623
  edgeType,
@@ -3939,7 +4637,7 @@ async function handleSpan(ctx, span) {
3939
4637
  );
3940
4638
  const result = upsertObservedEdge(
3941
4639
  ctx.graph,
3942
- import_types6.EdgeType.CONTAINS,
4640
+ import_types7.EdgeType.CONTAINS,
3943
4641
  observedSource(),
3944
4642
  targetId,
3945
4643
  ts,
@@ -3951,7 +4649,7 @@ async function handleSpan(ctx, span) {
3951
4649
  const targetId = ensureGrpcMethodNode(ctx.graph, span.rpcService, span.rpcMethod);
3952
4650
  const result = upsertObservedEdge(
3953
4651
  ctx.graph,
3954
- import_types6.EdgeType.CONTAINS,
4652
+ import_types7.EdgeType.CONTAINS,
3955
4653
  observedSource(),
3956
4654
  targetId,
3957
4655
  ts,
@@ -3967,7 +4665,7 @@ async function handleSpan(ctx, span) {
3967
4665
  );
3968
4666
  const result = upsertObservedEdge(
3969
4667
  ctx.graph,
3970
- import_types6.EdgeType.CONNECTS_TO,
4668
+ import_types7.EdgeType.CONNECTS_TO,
3971
4669
  observedSource(),
3972
4670
  targetId,
3973
4671
  ts,
@@ -3983,7 +4681,7 @@ async function handleSpan(ctx, span) {
3983
4681
  if (targetId && targetId !== sourceId) {
3984
4682
  upsertObservedEdge(
3985
4683
  ctx.graph,
3986
- import_types6.EdgeType.CALLS,
4684
+ import_types7.EdgeType.CALLS,
3987
4685
  observedSource(),
3988
4686
  targetId,
3989
4687
  ts,
@@ -3996,7 +4694,7 @@ async function handleSpan(ctx, span) {
3996
4694
  const frontierNodeId = ensureFrontierNode(ctx.graph, host, ts);
3997
4695
  upsertObservedEdge(
3998
4696
  ctx.graph,
3999
- import_types6.EdgeType.CALLS,
4697
+ import_types7.EdgeType.CALLS,
4000
4698
  observedSource(),
4001
4699
  frontierNodeId,
4002
4700
  ts,
@@ -4022,7 +4720,7 @@ async function handleSpan(ctx, span) {
4022
4720
  } : void 0;
4023
4721
  upsertObservedEdge(
4024
4722
  ctx.graph,
4025
- import_types6.EdgeType.CALLS,
4723
+ import_types7.EdgeType.CALLS,
4026
4724
  fallbackSource,
4027
4725
  sourceId,
4028
4726
  ts,
@@ -4041,7 +4739,7 @@ async function handleSpan(ctx, span) {
4041
4739
  );
4042
4740
  if (routeNodeId) {
4043
4741
  const routeSvc = ctx.graph.getNodeAttributes(routeNodeId).service;
4044
- upsertObservedEdge(ctx.graph, import_types6.EdgeType.CONTAINS, (0, import_types6.serviceId)(routeSvc), routeNodeId, ts, isError);
4742
+ upsertObservedEdge(ctx.graph, import_types7.EdgeType.CONTAINS, (0, import_types7.serviceId)(routeSvc), routeNodeId, ts, isError);
4045
4743
  }
4046
4744
  }
4047
4745
  if (span.statusCode === 2) {
@@ -4080,7 +4778,7 @@ function promoteFrontierNodes(graph, opts = {}) {
4080
4778
  const aliasIndex = /* @__PURE__ */ new Map();
4081
4779
  graph.forEachNode((id, attrs) => {
4082
4780
  const a = attrs;
4083
- if (a.type !== import_types6.NodeType.ServiceNode) return;
4781
+ if (a.type !== import_types7.NodeType.ServiceNode) return;
4084
4782
  aliasIndex.set(a.name, id);
4085
4783
  if (a.aliases) {
4086
4784
  for (const alias of a.aliases) aliasIndex.set(alias, id);
@@ -4089,7 +4787,7 @@ function promoteFrontierNodes(graph, opts = {}) {
4089
4787
  const toPromote = [];
4090
4788
  graph.forEachNode((id, attrs) => {
4091
4789
  const a = attrs;
4092
- if (a.type !== import_types6.NodeType.FrontierNode) return;
4790
+ if (a.type !== import_types7.NodeType.FrontierNode) return;
4093
4791
  const target = aliasIndex.get(a.host);
4094
4792
  if (!target) return;
4095
4793
  if (target === id) return;
@@ -4123,7 +4821,7 @@ function rewireFrontierEdges(graph, frontierId2, serviceId7) {
4123
4821
  }
4124
4822
  function rebuildEdge(graph, edge, newSource, newTarget, oldEdgeId) {
4125
4823
  graph.dropEdge(oldEdgeId);
4126
- const newId = edge.provenance === import_types6.Provenance.OBSERVED ? (0, import_types6.observedEdgeId)(newSource, newTarget, edge.type) : edge.provenance === import_types6.Provenance.INFERRED ? (0, import_types6.inferredEdgeId)(newSource, newTarget, edge.type) : (0, import_types6.extractedEdgeId)(newSource, newTarget, edge.type);
4824
+ const newId = edge.provenance === import_types7.Provenance.OBSERVED ? (0, import_types7.observedEdgeId)(newSource, newTarget, edge.type) : edge.provenance === import_types7.Provenance.INFERRED ? (0, import_types7.inferredEdgeId)(newSource, newTarget, edge.type) : (0, import_types7.extractedEdgeId)(newSource, newTarget, edge.type);
4127
4825
  if (graph.hasEdge(newId)) {
4128
4826
  const existing = graph.getEdgeAttributes(newId);
4129
4827
  const merged = {
@@ -4157,12 +4855,12 @@ async function markStaleEdges(graph, options = {}) {
4157
4855
  const project = options.project ?? DEFAULT_PROJECT;
4158
4856
  graph.forEachEdge((id, attrs) => {
4159
4857
  const e = attrs;
4160
- if (e.provenance !== import_types6.Provenance.OBSERVED) return;
4858
+ if (e.provenance !== import_types7.Provenance.OBSERVED) return;
4161
4859
  if (!e.lastObserved) return;
4162
4860
  const threshold = thresholdForEdgeType(e.type, thresholds);
4163
4861
  const age = now - new Date(e.lastObserved).getTime();
4164
4862
  if (age > threshold) {
4165
- const updated = { ...e, provenance: import_types6.Provenance.STALE, confidence: 0.3 };
4863
+ const updated = { ...e, provenance: import_types7.Provenance.STALE, confidence: 0.3 };
4166
4864
  graph.replaceEdgeAttributes(id, updated);
4167
4865
  events.push({
4168
4866
  edgeId: id,
@@ -4179,8 +4877,8 @@ async function markStaleEdges(graph, options = {}) {
4179
4877
  project,
4180
4878
  payload: {
4181
4879
  edgeId: id,
4182
- from: import_types6.Provenance.OBSERVED,
4183
- to: import_types6.Provenance.STALE
4880
+ from: import_types7.Provenance.OBSERVED,
4881
+ to: import_types7.Provenance.STALE
4184
4882
  }
4185
4883
  });
4186
4884
  }
@@ -4191,13 +4889,13 @@ async function markStaleEdges(graph, options = {}) {
4191
4889
  return { count: events.length, events };
4192
4890
  }
4193
4891
  async function appendStaleEvents(staleEventsPath, events) {
4194
- await import_node_fs8.promises.mkdir(import_node_path9.default.dirname(staleEventsPath), { recursive: true });
4892
+ await import_node_fs9.promises.mkdir(import_node_path10.default.dirname(staleEventsPath), { recursive: true });
4195
4893
  const lines = events.map((e) => JSON.stringify(e)).join("\n") + "\n";
4196
- await import_node_fs8.promises.appendFile(staleEventsPath, lines, "utf8");
4894
+ await import_node_fs9.promises.appendFile(staleEventsPath, lines, "utf8");
4197
4895
  }
4198
4896
  async function readStaleEvents(staleEventsPath) {
4199
4897
  try {
4200
- const raw = await import_node_fs8.promises.readFile(staleEventsPath, "utf8");
4898
+ const raw = await import_node_fs9.promises.readFile(staleEventsPath, "utf8");
4201
4899
  return raw.split("\n").filter((line) => line.length > 0).map((line) => JSON.parse(line));
4202
4900
  } catch (err) {
4203
4901
  if (err.code === "ENOENT") return [];
@@ -4231,7 +4929,7 @@ function startStalenessLoop(graph, options = {}) {
4231
4929
  }
4232
4930
  async function readErrorEvents(errorsPath) {
4233
4931
  try {
4234
- const raw = await import_node_fs8.promises.readFile(errorsPath, "utf8");
4932
+ const raw = await import_node_fs9.promises.readFile(errorsPath, "utf8");
4235
4933
  const events = raw.split("\n").filter((line) => line.length > 0).map((line) => JSON.parse(line));
4236
4934
  return dedupeIncidents(events);
4237
4935
  } catch (err) {
@@ -4289,7 +4987,7 @@ function mergeSnapshot(graph, snapshot) {
4289
4987
  const validEdges = [];
4290
4988
  for (const node of incomingNodes) {
4291
4989
  if (node.attributes === void 0) continue;
4292
- const parsed = import_types6.GraphNodeSchema.safeParse(node.attributes);
4990
+ const parsed = import_types7.GraphNodeSchema.safeParse(node.attributes);
4293
4991
  if (!parsed.success) {
4294
4992
  issues.push(`node "${node.key}": ${describeZodIssues(parsed.error)}`);
4295
4993
  continue;
@@ -4298,7 +4996,7 @@ function mergeSnapshot(graph, snapshot) {
4298
4996
  }
4299
4997
  for (const edge of incomingEdges) {
4300
4998
  if (edge.attributes === void 0) continue;
4301
- const parsed = import_types6.GraphEdgeSchema.safeParse(edge.attributes);
4999
+ const parsed = import_types7.GraphEdgeSchema.safeParse(edge.attributes);
4302
5000
  if (!parsed.success) {
4303
5001
  const label = edge.key ?? `${edge.source}->${edge.target}`;
4304
5002
  issues.push(`edge "${label}": ${describeZodIssues(parsed.error)}`);
@@ -4327,29 +5025,29 @@ function mergeSnapshot(graph, snapshot) {
4327
5025
  }
4328
5026
 
4329
5027
  // src/traverse.ts
4330
- var import_types7 = require("@neat.is/types");
5028
+ var import_types8 = require("@neat.is/types");
4331
5029
  var ROOT_CAUSE_MAX_DEPTH = 5;
4332
5030
  var BLAST_RADIUS_DEFAULT_DEPTH = 10;
4333
5031
  function isFrontierNode(graph, nodeId) {
4334
5032
  if (!graph.hasNode(nodeId)) return false;
4335
5033
  const attrs = graph.getNodeAttributes(nodeId);
4336
- return attrs.type === import_types7.NodeType.FrontierNode;
5034
+ return attrs.type === import_types8.NodeType.FrontierNode;
4337
5035
  }
4338
5036
  function resolveOwningService(graph, nodeId) {
4339
5037
  if (!graph.hasNode(nodeId)) return null;
4340
5038
  const attrs = graph.getNodeAttributes(nodeId);
4341
- if (attrs.type === import_types7.NodeType.ServiceNode) {
5039
+ if (attrs.type === import_types8.NodeType.ServiceNode) {
4342
5040
  return { id: nodeId, svc: attrs };
4343
5041
  }
4344
- if (attrs.type === import_types7.NodeType.FileNode || attrs.type === import_types7.NodeType.SymbolNode) {
5042
+ if (attrs.type === import_types8.NodeType.FileNode || attrs.type === import_types8.NodeType.SymbolNode) {
4345
5043
  for (const edgeId of graph.inboundEdges(nodeId)) {
4346
5044
  const e = graph.getEdgeAttributes(edgeId);
4347
- if (e.type !== import_types7.EdgeType.CONTAINS) continue;
5045
+ if (e.type !== import_types8.EdgeType.CONTAINS) continue;
4348
5046
  const owner = graph.getNodeAttributes(e.source);
4349
- if (owner.type === import_types7.NodeType.ServiceNode) {
5047
+ if (owner.type === import_types8.NodeType.ServiceNode) {
4350
5048
  return { id: e.source, svc: owner };
4351
5049
  }
4352
- if (owner.type === import_types7.NodeType.FileNode) {
5050
+ if (owner.type === import_types8.NodeType.FileNode) {
4353
5051
  return resolveOwningService(graph, e.source);
4354
5052
  }
4355
5053
  }
@@ -4362,7 +5060,7 @@ function bestEdgeBySource(graph, edgeIds) {
4362
5060
  const e = graph.getEdgeAttributes(id);
4363
5061
  if (isFrontierNode(graph, e.source)) continue;
4364
5062
  const cur = best.get(e.source);
4365
- if (!cur || import_types7.PROV_RANK[e.provenance] > import_types7.PROV_RANK[cur.provenance]) {
5063
+ if (!cur || import_types8.PROV_RANK[e.provenance] > import_types8.PROV_RANK[cur.provenance]) {
4366
5064
  best.set(e.source, e);
4367
5065
  }
4368
5066
  }
@@ -4374,7 +5072,7 @@ function bestEdgeByTarget(graph, edgeIds) {
4374
5072
  const e = graph.getEdgeAttributes(id);
4375
5073
  if (isFrontierNode(graph, e.target)) continue;
4376
5074
  const cur = best.get(e.target);
4377
- if (!cur || import_types7.PROV_RANK[e.provenance] > import_types7.PROV_RANK[cur.provenance]) {
5075
+ if (!cur || import_types8.PROV_RANK[e.provenance] > import_types8.PROV_RANK[cur.provenance]) {
4378
5076
  best.set(e.target, e);
4379
5077
  }
4380
5078
  }
@@ -4536,10 +5234,10 @@ function symbolRootCauseShape(graph, origin, walk6) {
4536
5234
  return serviceRootCauseShape(graph, owner.svc, walk6);
4537
5235
  }
4538
5236
  var rootCauseShapes = {
4539
- [import_types7.NodeType.DatabaseNode]: databaseRootCauseShape,
4540
- [import_types7.NodeType.ServiceNode]: serviceRootCauseShape,
4541
- [import_types7.NodeType.FileNode]: fileRootCauseShape,
4542
- [import_types7.NodeType.SymbolNode]: symbolRootCauseShape
5237
+ [import_types8.NodeType.DatabaseNode]: databaseRootCauseShape,
5238
+ [import_types8.NodeType.ServiceNode]: serviceRootCauseShape,
5239
+ [import_types8.NodeType.FileNode]: fileRootCauseShape,
5240
+ [import_types8.NodeType.SymbolNode]: symbolRootCauseShape
4543
5241
  };
4544
5242
  function getRootCause(graph, errorNodeId, errorEvent, incidents) {
4545
5243
  if (!graph.hasNode(errorNodeId)) return null;
@@ -4550,7 +5248,7 @@ function getRootCause(graph, errorNodeId, errorEvent, incidents) {
4550
5248
  const match = shape(graph, origin, walk6);
4551
5249
  if (match) {
4552
5250
  const reason = errorEvent ? `${match.rootCauseReason} (observed error: ${errorEvent.errorMessage})` : match.rootCauseReason;
4553
- return import_types7.RootCauseResultSchema.parse({
5251
+ return import_types8.RootCauseResultSchema.parse({
4554
5252
  rootCauseNode: match.rootCauseNode,
4555
5253
  rootCauseReason: reason,
4556
5254
  traversalPath: walk6.path,
@@ -4560,7 +5258,7 @@ function getRootCause(graph, errorNodeId, errorEvent, incidents) {
4560
5258
  });
4561
5259
  }
4562
5260
  }
4563
- if (origin.type === import_types7.NodeType.ServiceNode) {
5261
+ if (origin.type === import_types8.NodeType.ServiceNode) {
4564
5262
  const crossService = crossServiceRootCause(graph, errorNodeId, incidents, errorEvent);
4565
5263
  if (crossService) return crossService;
4566
5264
  }
@@ -4600,8 +5298,8 @@ function rootCauseFromIncidents(nodeId, incidents, errorEvent) {
4600
5298
  const loc = localizeFromIncidents(nodeId, incidents, errorEvent);
4601
5299
  if (!loc) return null;
4602
5300
  const traversalPath = loc.fileNode ? [nodeId, loc.fileNode] : [nodeId];
4603
- const edgeProvenances = loc.fileNode ? [import_types7.Provenance.OBSERVED] : [];
4604
- return import_types7.RootCauseResultSchema.parse({
5301
+ const edgeProvenances = loc.fileNode ? [import_types8.Provenance.OBSERVED] : [];
5302
+ return import_types8.RootCauseResultSchema.parse({
4605
5303
  rootCauseNode: loc.rootCauseNode,
4606
5304
  rootCauseReason: loc.rootCauseReason,
4607
5305
  traversalPath,
@@ -4611,21 +5309,21 @@ function rootCauseFromIncidents(nodeId, incidents, errorEvent) {
4611
5309
  });
4612
5310
  }
4613
5311
  function isFailingCallEdge(e) {
4614
- return e.type === import_types7.EdgeType.CALLS && (e.signal?.errorCount ?? 0) > 0;
5312
+ return e.type === import_types8.EdgeType.CALLS && (e.signal?.errorCount ?? 0) > 0;
4615
5313
  }
4616
5314
  function callSourcesForService(graph, serviceId7) {
4617
5315
  const ids = [serviceId7];
4618
5316
  for (const edgeId of graph.outboundEdges(serviceId7)) {
4619
5317
  const e = graph.getEdgeAttributes(edgeId);
4620
- if (e.type !== import_types7.EdgeType.CONTAINS) continue;
5318
+ if (e.type !== import_types8.EdgeType.CONTAINS) continue;
4621
5319
  const tgt = graph.getNodeAttributes(e.target);
4622
- if (tgt.type !== import_types7.NodeType.FileNode) continue;
5320
+ if (tgt.type !== import_types8.NodeType.FileNode) continue;
4623
5321
  ids.push(e.target);
4624
5322
  for (const symEdgeId of graph.outboundEdges(e.target)) {
4625
5323
  const se = graph.getEdgeAttributes(symEdgeId);
4626
- if (se.type !== import_types7.EdgeType.CONTAINS) continue;
5324
+ if (se.type !== import_types8.EdgeType.CONTAINS) continue;
4627
5325
  const sym = graph.getNodeAttributes(se.target);
4628
- if (sym.type === import_types7.NodeType.SymbolNode) ids.push(se.target);
5326
+ if (sym.type === import_types8.NodeType.SymbolNode) ids.push(se.target);
4629
5327
  }
4630
5328
  }
4631
5329
  return ids;
@@ -4634,8 +5332,8 @@ function failingCallDominates(e, id, curEdge, curId) {
4634
5332
  const ec = e.signal?.errorCount ?? 0;
4635
5333
  const cc = curEdge.signal?.errorCount ?? 0;
4636
5334
  if (ec !== cc) return ec > cc;
4637
- if (import_types7.PROV_RANK[e.provenance] !== import_types7.PROV_RANK[curEdge.provenance]) {
4638
- return import_types7.PROV_RANK[e.provenance] > import_types7.PROV_RANK[curEdge.provenance];
5335
+ if (import_types8.PROV_RANK[e.provenance] !== import_types8.PROV_RANK[curEdge.provenance]) {
5336
+ return import_types8.PROV_RANK[e.provenance] > import_types8.PROV_RANK[curEdge.provenance];
4639
5337
  }
4640
5338
  return id < curId;
4641
5339
  }
@@ -4684,10 +5382,10 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
4684
5382
  let rootCauseNode = culprit;
4685
5383
  if (loc.fileNode) {
4686
5384
  path60.push(loc.fileNode);
4687
- edgeProvenances.push(import_types7.Provenance.OBSERVED);
5385
+ edgeProvenances.push(import_types8.Provenance.OBSERVED);
4688
5386
  rootCauseNode = loc.fileNode;
4689
5387
  }
4690
- return import_types7.RootCauseResultSchema.parse({
5388
+ return import_types8.RootCauseResultSchema.parse({
4691
5389
  rootCauseNode,
4692
5390
  rootCauseReason: loc.rootCauseReason,
4693
5391
  traversalPath: path60,
@@ -4699,7 +5397,7 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
4699
5397
  const lastEdge = chain.edges[chain.edges.length - 1];
4700
5398
  const errs = lastEdge.signal?.errorCount ?? 0;
4701
5399
  const culpritName = culprit.replace(/^service:/, "");
4702
- return import_types7.RootCauseResultSchema.parse({
5400
+ return import_types8.RootCauseResultSchema.parse({
4703
5401
  rootCauseNode: culprit,
4704
5402
  rootCauseReason: `${culpritName} is failing downstream calls (${errs} observed error${errs === 1 ? "" : "s"})`,
4705
5403
  traversalPath: path60,
@@ -4710,7 +5408,7 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
4710
5408
  }
4711
5409
  function getBlastRadius(graph, nodeId, maxDepth = BLAST_RADIUS_DEFAULT_DEPTH) {
4712
5410
  if (!graph.hasNode(nodeId)) {
4713
- return import_types7.BlastRadiusResultSchema.parse({ origin: nodeId, affectedNodes: [], totalAffected: 0 });
5411
+ return import_types8.BlastRadiusResultSchema.parse({ origin: nodeId, affectedNodes: [], totalAffected: 0 });
4714
5412
  }
4715
5413
  const seen = /* @__PURE__ */ new Map();
4716
5414
  const queue = [{ nodeId, distance: 0, path: [nodeId], pathEdges: [] }];
@@ -4743,7 +5441,7 @@ function getBlastRadius(graph, nodeId, maxDepth = BLAST_RADIUS_DEFAULT_DEPTH) {
4743
5441
  const affectedNodes = [...seen.values()].sort(
4744
5442
  (a, b) => a.distance - b.distance || a.nodeId.localeCompare(b.nodeId)
4745
5443
  );
4746
- return import_types7.BlastRadiusResultSchema.parse({
5444
+ return import_types8.BlastRadiusResultSchema.parse({
4747
5445
  origin: nodeId,
4748
5446
  affectedNodes,
4749
5447
  totalAffected: affectedNodes.length
@@ -4753,7 +5451,7 @@ var TRANSITIVE_DEPENDENCIES_DEFAULT_DEPTH = 3;
4753
5451
  var TRANSITIVE_DEPENDENCIES_MAX_DEPTH = 10;
4754
5452
  function getTransitiveDependencies(graph, nodeId, depth = TRANSITIVE_DEPENDENCIES_DEFAULT_DEPTH) {
4755
5453
  if (!graph.hasNode(nodeId)) {
4756
- return import_types7.TransitiveDependenciesResultSchema.parse({
5454
+ return import_types8.TransitiveDependenciesResultSchema.parse({
4757
5455
  origin: nodeId,
4758
5456
  depth,
4759
5457
  dependencies: [],
@@ -4765,7 +5463,7 @@ function getTransitiveDependencies(graph, nodeId, depth = TRANSITIVE_DEPENDENCIE
4765
5463
  const enqueued = /* @__PURE__ */ new Set([nodeId]);
4766
5464
  while (queue.length > 0) {
4767
5465
  const frame = queue.shift();
4768
- if (frame.distance > 0 && frame.edge && frame.edge.type !== import_types7.EdgeType.CONTAINS) {
5466
+ if (frame.distance > 0 && frame.edge && frame.edge.type !== import_types8.EdgeType.CONTAINS) {
4769
5467
  seen.set(frame.nodeId, {
4770
5468
  nodeId: frame.nodeId,
4771
5469
  distance: frame.distance,
@@ -4784,7 +5482,7 @@ function getTransitiveDependencies(graph, nodeId, depth = TRANSITIVE_DEPENDENCIE
4784
5482
  const dependencies = [...seen.values()].sort(
4785
5483
  (a, b) => a.distance - b.distance || a.nodeId.localeCompare(b.nodeId)
4786
5484
  );
4787
- return import_types7.TransitiveDependenciesResultSchema.parse({
5485
+ return import_types8.TransitiveDependenciesResultSchema.parse({
4788
5486
  origin: nodeId,
4789
5487
  depth,
4790
5488
  dependencies,
@@ -4793,7 +5491,7 @@ function getTransitiveDependencies(graph, nodeId, depth = TRANSITIVE_DEPENDENCIE
4793
5491
  }
4794
5492
  function getObservedDependencies(graph, nodeId) {
4795
5493
  if (!graph.hasNode(nodeId)) {
4796
- return import_types7.ObservedDependenciesResultSchema.parse({
5494
+ return import_types8.ObservedDependenciesResultSchema.parse({
4797
5495
  origin: nodeId,
4798
5496
  dependencies: [],
4799
5497
  observed: false,
@@ -4803,12 +5501,12 @@ function getObservedDependencies(graph, nodeId) {
4803
5501
  }
4804
5502
  const attrs = graph.getNodeAttributes(nodeId);
4805
5503
  const scope = [nodeId];
4806
- if (attrs.type === import_types7.NodeType.ServiceNode) {
5504
+ if (attrs.type === import_types8.NodeType.ServiceNode) {
4807
5505
  for (const edgeId of graph.outboundEdges(nodeId)) {
4808
5506
  const e = graph.getEdgeAttributes(edgeId);
4809
- if (e.type !== import_types7.EdgeType.CONTAINS) continue;
5507
+ if (e.type !== import_types8.EdgeType.CONTAINS) continue;
4810
5508
  const owned = graph.getNodeAttributes(e.target);
4811
- if (owned.type === import_types7.NodeType.FileNode) scope.push(e.target);
5509
+ if (owned.type === import_types8.NodeType.FileNode) scope.push(e.target);
4812
5510
  }
4813
5511
  }
4814
5512
  const dependencies = [];
@@ -4817,13 +5515,13 @@ function getObservedDependencies(graph, nodeId) {
4817
5515
  for (const src of scope) {
4818
5516
  for (const edgeId of graph.outboundEdges(src)) {
4819
5517
  const e = graph.getEdgeAttributes(edgeId);
4820
- if (e.type === import_types7.EdgeType.CONTAINS) continue;
4821
- if (e.provenance === import_types7.Provenance.OBSERVED) {
5518
+ if (e.type === import_types8.EdgeType.CONTAINS) continue;
5519
+ if (e.provenance === import_types8.Provenance.OBSERVED) {
4822
5520
  if (!seenEdge.has(e.id)) {
4823
5521
  seenEdge.add(e.id);
4824
5522
  dependencies.push(e);
4825
5523
  }
4826
- } else if (e.provenance === import_types7.Provenance.EXTRACTED) {
5524
+ } else if (e.provenance === import_types8.Provenance.EXTRACTED) {
4827
5525
  hasExtractedOutbound = true;
4828
5526
  }
4829
5527
  }
@@ -4832,14 +5530,14 @@ function getObservedDependencies(graph, nodeId) {
4832
5530
  for (const tgt of scope) {
4833
5531
  for (const edgeId of graph.inboundEdges(tgt)) {
4834
5532
  const e = graph.getEdgeAttributes(edgeId);
4835
- if (e.type === import_types7.EdgeType.CONTAINS) continue;
4836
- if (e.provenance === import_types7.Provenance.OBSERVED) inboundObservedCount += 1;
5533
+ if (e.type === import_types8.EdgeType.CONTAINS) continue;
5534
+ if (e.provenance === import_types8.Provenance.OBSERVED) inboundObservedCount += 1;
4837
5535
  }
4838
5536
  }
4839
5537
  dependencies.sort(
4840
5538
  (a, b) => a.target.localeCompare(b.target) || a.source.localeCompare(b.source) || a.id.localeCompare(b.id)
4841
5539
  );
4842
- return import_types7.ObservedDependenciesResultSchema.parse({
5540
+ return import_types8.ObservedDependenciesResultSchema.parse({
4843
5541
  origin: nodeId,
4844
5542
  dependencies,
4845
5543
  observed: dependencies.length > 0 || inboundObservedCount > 0,
@@ -4853,34 +5551,34 @@ function bucketKey(source, target, type) {
4853
5551
  return `${type}|${source}|${target}`;
4854
5552
  }
4855
5553
  function bucketSourceFor(graph, edge) {
4856
- if (edge.type !== import_types8.EdgeType.CONNECTS_TO) return edge.source;
4857
- const parsed = (0, import_types8.parseFileId)(edge.source);
5554
+ if (edge.type !== import_types9.EdgeType.CONNECTS_TO) return edge.source;
5555
+ const parsed = (0, import_types9.parseFileId)(edge.source);
4858
5556
  if (!parsed || !graph.hasNode(edge.target)) return edge.source;
4859
5557
  const target = graph.getNodeAttributes(edge.target);
4860
- if (target.type !== import_types8.NodeType.DatabaseNode) return edge.source;
4861
- return (0, import_types8.serviceId)(parsed.service);
5558
+ if (target.type !== import_types9.NodeType.DatabaseNode) return edge.source;
5559
+ return (0, import_types9.serviceId)(parsed.service);
4862
5560
  }
4863
5561
  function bucketEdges(graph) {
4864
5562
  const buckets2 = /* @__PURE__ */ new Map();
4865
5563
  graph.forEachEdge((id, attrs) => {
4866
5564
  const e = attrs;
4867
- const parsed = (0, import_types8.parseEdgeId)(id);
5565
+ const parsed = (0, import_types9.parseEdgeId)(id);
4868
5566
  const provenance = parsed?.provenance ?? e.provenance;
4869
5567
  const source = bucketSourceFor(graph, e);
4870
5568
  const key = bucketKey(source, e.target, e.type);
4871
5569
  const cur = buckets2.get(key) ?? { source, target: e.target, type: e.type };
4872
5570
  switch (provenance) {
4873
- case import_types8.Provenance.EXTRACTED:
5571
+ case import_types9.Provenance.EXTRACTED:
4874
5572
  cur.extracted = e;
4875
5573
  break;
4876
- case import_types8.Provenance.OBSERVED:
5574
+ case import_types9.Provenance.OBSERVED:
4877
5575
  cur.observed = e;
4878
5576
  break;
4879
- case import_types8.Provenance.INFERRED:
5577
+ case import_types9.Provenance.INFERRED:
4880
5578
  cur.inferred = e;
4881
5579
  break;
4882
5580
  default:
4883
- if (e.provenance === import_types8.Provenance.STALE) cur.stale = e;
5581
+ if (e.provenance === import_types9.Provenance.STALE) cur.stale = e;
4884
5582
  }
4885
5583
  buckets2.set(key, cur);
4886
5584
  });
@@ -4889,17 +5587,17 @@ function bucketEdges(graph) {
4889
5587
  function nodeIsFrontier(graph, nodeId) {
4890
5588
  if (!graph.hasNode(nodeId)) return false;
4891
5589
  const attrs = graph.getNodeAttributes(nodeId);
4892
- return attrs.type === import_types8.NodeType.FrontierNode;
5590
+ return attrs.type === import_types9.NodeType.FrontierNode;
4893
5591
  }
4894
5592
  function nodeIsWebsocketChannel(graph, nodeId) {
4895
5593
  if (!graph.hasNode(nodeId)) return false;
4896
5594
  const attrs = graph.getNodeAttributes(nodeId);
4897
- return attrs.type === import_types8.NodeType.WebSocketChannelNode;
5595
+ return attrs.type === import_types9.NodeType.WebSocketChannelNode;
4898
5596
  }
4899
5597
  function nodeIsSymbol(graph, nodeId) {
4900
5598
  if (!graph.hasNode(nodeId)) return false;
4901
5599
  const attrs = graph.getNodeAttributes(nodeId);
4902
- return attrs.type === import_types8.NodeType.SymbolNode;
5600
+ return attrs.type === import_types9.NodeType.SymbolNode;
4903
5601
  }
4904
5602
  function clampConfidence(n) {
4905
5603
  if (!Number.isFinite(n)) return 0;
@@ -4919,14 +5617,14 @@ function gradedConfidence(edge) {
4919
5617
  return clampConfidence(confidenceForEdge(edge));
4920
5618
  }
4921
5619
  var OBSERVABLE_EDGE_TYPES = /* @__PURE__ */ new Set([
4922
- import_types8.EdgeType.CALLS,
4923
- import_types8.EdgeType.CONNECTS_TO,
4924
- import_types8.EdgeType.PUBLISHES_TO,
4925
- import_types8.EdgeType.CONSUMES_FROM
5620
+ import_types9.EdgeType.CALLS,
5621
+ import_types9.EdgeType.CONNECTS_TO,
5622
+ import_types9.EdgeType.PUBLISHES_TO,
5623
+ import_types9.EdgeType.CONSUMES_FROM
4926
5624
  ]);
4927
5625
  function detectMissingDivergences(graph, bucket) {
4928
5626
  const out = [];
4929
- if (bucket.type === import_types8.EdgeType.CONTAINS) return out;
5627
+ if (bucket.type === import_types9.EdgeType.CONTAINS) return out;
4930
5628
  if (nodeIsSymbol(graph, bucket.source) || nodeIsSymbol(graph, bucket.target)) return out;
4931
5629
  if (bucket.extracted && !bucket.observed && OBSERVABLE_EDGE_TYPES.has(bucket.type)) {
4932
5630
  if (!nodeIsFrontier(graph, bucket.target)) {
@@ -4968,7 +5666,7 @@ function declaredHostFor(svc) {
4968
5666
  function hasExtractedConfiguredBy(graph, svcId) {
4969
5667
  for (const edgeId of graph.outboundEdges(svcId)) {
4970
5668
  const e = graph.getEdgeAttributes(edgeId);
4971
- if (e.type === import_types8.EdgeType.CONFIGURED_BY && e.provenance === import_types8.Provenance.EXTRACTED) {
5669
+ if (e.type === import_types9.EdgeType.CONFIGURED_BY && e.provenance === import_types9.Provenance.EXTRACTED) {
4972
5670
  return true;
4973
5671
  }
4974
5672
  }
@@ -4981,10 +5679,10 @@ function detectHostMismatch(graph, svcId, svc) {
4981
5679
  const out = [];
4982
5680
  for (const edgeId of graph.outboundEdges(svcId)) {
4983
5681
  const edge = graph.getEdgeAttributes(edgeId);
4984
- if (edge.type !== import_types8.EdgeType.CONNECTS_TO) continue;
4985
- if (edge.provenance !== import_types8.Provenance.OBSERVED) continue;
5682
+ if (edge.type !== import_types9.EdgeType.CONNECTS_TO) continue;
5683
+ if (edge.provenance !== import_types9.Provenance.OBSERVED) continue;
4986
5684
  const target = graph.getNodeAttributes(edge.target);
4987
- if (target.type !== import_types8.NodeType.DatabaseNode) continue;
5685
+ if (target.type !== import_types9.NodeType.DatabaseNode) continue;
4988
5686
  const observedHost = target.host?.trim();
4989
5687
  if (!observedHost) continue;
4990
5688
  if (observedHost === declaredHost) continue;
@@ -5006,10 +5704,10 @@ function detectCompatDivergences(graph, svcId, svc) {
5006
5704
  const deps = svc.dependencies ?? {};
5007
5705
  for (const edgeId of graph.outboundEdges(svcId)) {
5008
5706
  const edge = graph.getEdgeAttributes(edgeId);
5009
- if (edge.type !== import_types8.EdgeType.CONNECTS_TO) continue;
5010
- if (edge.provenance !== import_types8.Provenance.OBSERVED) continue;
5707
+ if (edge.type !== import_types9.EdgeType.CONNECTS_TO) continue;
5708
+ if (edge.provenance !== import_types9.Provenance.OBSERVED) continue;
5011
5709
  const target = graph.getNodeAttributes(edge.target);
5012
- if (target.type !== import_types8.NodeType.DatabaseNode) continue;
5710
+ if (target.type !== import_types9.NodeType.DatabaseNode) continue;
5013
5711
  for (const pair of compatPairs()) {
5014
5712
  if (pair.engine !== target.engine) continue;
5015
5713
  const declared = deps[pair.driver];
@@ -5106,7 +5804,7 @@ function suppressHostMismatchHalves(all) {
5106
5804
  for (const d of all) {
5107
5805
  if (d.type !== "host-mismatch") continue;
5108
5806
  observedHalf.add(`${d.source}->${d.target}`);
5109
- declaredHalf.add((0, import_types8.databaseId)(d.extractedHost));
5807
+ declaredHalf.add((0, import_types9.databaseId)(d.extractedHost));
5110
5808
  }
5111
5809
  if (observedHalf.size === 0) return all;
5112
5810
  return all.filter((d) => {
@@ -5125,13 +5823,13 @@ function computeDivergences(graph, opts = {}) {
5125
5823
  }
5126
5824
  graph.forEachNode((nodeId, attrs) => {
5127
5825
  const n = attrs;
5128
- if (n.type === import_types8.NodeType.ServiceNode) {
5826
+ if (n.type === import_types9.NodeType.ServiceNode) {
5129
5827
  const svc = n;
5130
5828
  for (const d of detectHostMismatch(graph, nodeId, svc)) all.push(d);
5131
5829
  for (const d of detectCompatDivergences(graph, nodeId, svc)) all.push(d);
5132
5830
  return;
5133
5831
  }
5134
- if (n.type === import_types8.NodeType.InfraNode && n.kind === "sql-table") {
5832
+ if (n.type === import_types9.NodeType.InfraNode && n.kind === "sql-table") {
5135
5833
  for (const d of detectColumnDrift(n)) all.push(d);
5136
5834
  }
5137
5835
  });
@@ -5167,7 +5865,7 @@ function computeDivergences(graph, opts = {}) {
5167
5865
  const bc = "column" in b && b.column ? b.column : "";
5168
5866
  return ac.localeCompare(bc);
5169
5867
  });
5170
- return import_types8.DivergenceResultSchema.parse({
5868
+ return import_types9.DivergenceResultSchema.parse({
5171
5869
  divergences: filtered,
5172
5870
  totalAffected: filtered.length,
5173
5871
  computedAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -5227,16 +5925,16 @@ init_cjs_shims();
5227
5925
 
5228
5926
  // src/extract/services.ts
5229
5927
  init_cjs_shims();
5230
- var import_node_fs12 = require("fs");
5231
- var import_node_path13 = __toESM(require("path"), 1);
5928
+ var import_node_fs13 = require("fs");
5929
+ var import_node_path14 = __toESM(require("path"), 1);
5232
5930
  var import_ignore = __toESM(require("ignore"), 1);
5233
5931
  var import_minimatch2 = require("minimatch");
5234
- var import_types10 = require("@neat.is/types");
5932
+ var import_types11 = require("@neat.is/types");
5235
5933
 
5236
5934
  // src/extract/python.ts
5237
5935
  init_cjs_shims();
5238
- var import_node_fs9 = require("fs");
5239
- var import_node_path10 = __toESM(require("path"), 1);
5936
+ var import_node_fs10 = require("fs");
5937
+ var import_node_path11 = __toESM(require("path"), 1);
5240
5938
  var import_smol_toml = require("smol-toml");
5241
5939
  var REQUIREMENT_LINE = /^\s*([A-Za-z0-9_.-]+)(?:\[[^\]]*\])?\s*(?:(==)\s*([A-Za-z0-9_.+-]+))?/;
5242
5940
  function parseRequirementsTxt(content) {
@@ -5269,25 +5967,25 @@ function depsFromPyProject(pyproject) {
5269
5967
  return out;
5270
5968
  }
5271
5969
  async function discoverPythonService(serviceDir) {
5272
- const pyprojectPath = import_node_path10.default.join(serviceDir, "pyproject.toml");
5273
- const requirementsPath = import_node_path10.default.join(serviceDir, "requirements.txt");
5274
- const setupPath = import_node_path10.default.join(serviceDir, "setup.py");
5970
+ const pyprojectPath = import_node_path11.default.join(serviceDir, "pyproject.toml");
5971
+ const requirementsPath = import_node_path11.default.join(serviceDir, "requirements.txt");
5972
+ const setupPath = import_node_path11.default.join(serviceDir, "setup.py");
5275
5973
  const hasPyproject = await exists2(pyprojectPath);
5276
5974
  const hasRequirements = await exists2(requirementsPath);
5277
5975
  const hasSetup = await exists2(setupPath);
5278
5976
  if (!hasPyproject && !hasRequirements && !hasSetup) return null;
5279
- let name = import_node_path10.default.basename(serviceDir);
5977
+ let name = import_node_path11.default.basename(serviceDir);
5280
5978
  let version;
5281
5979
  const dependencies = {};
5282
5980
  if (hasPyproject) {
5283
- const raw = await import_node_fs9.promises.readFile(pyprojectPath, "utf8");
5981
+ const raw = await import_node_fs10.promises.readFile(pyprojectPath, "utf8");
5284
5982
  const pyproject = (0, import_smol_toml.parse)(raw);
5285
5983
  name = pyproject.project?.name ?? pyproject.tool?.poetry?.name ?? name;
5286
5984
  version = pyproject.project?.version ?? pyproject.tool?.poetry?.version ?? void 0;
5287
5985
  Object.assign(dependencies, depsFromPyProject(pyproject));
5288
5986
  }
5289
5987
  if (hasRequirements) {
5290
- const raw = await import_node_fs9.promises.readFile(requirementsPath, "utf8");
5988
+ const raw = await import_node_fs10.promises.readFile(requirementsPath, "utf8");
5291
5989
  Object.assign(dependencies, parseRequirementsTxt(raw));
5292
5990
  }
5293
5991
  return { name, version, dependencies };
@@ -5302,9 +6000,9 @@ function pythonToPackage(service) {
5302
6000
 
5303
6001
  // src/extract/go.ts
5304
6002
  init_cjs_shims();
5305
- var import_node_fs10 = require("fs");
5306
- var import_node_path11 = __toESM(require("path"), 1);
5307
- var import_types9 = require("@neat.is/types");
6003
+ var import_node_fs11 = require("fs");
6004
+ var import_node_path12 = __toESM(require("path"), 1);
6005
+ var import_types10 = require("@neat.is/types");
5308
6006
  function parseGoMod(source) {
5309
6007
  const module2 = source.match(/^\s*module\s+(\S+)\s*$/m)?.[1];
5310
6008
  if (!module2) return null;
@@ -5323,7 +6021,7 @@ function parseGoMod(source) {
5323
6021
  async function discoverGoService(scanPath, dir) {
5324
6022
  let raw;
5325
6023
  try {
5326
- raw = await import_node_fs10.promises.readFile(import_node_path11.default.join(dir, "go.mod"), "utf8");
6024
+ raw = await import_node_fs11.promises.readFile(import_node_path12.default.join(dir, "go.mod"), "utf8");
5327
6025
  } catch {
5328
6026
  return null;
5329
6027
  }
@@ -5332,12 +6030,12 @@ async function discoverGoService(scanPath, dir) {
5332
6030
  const name = mod.module.split("/").filter(Boolean).pop() ?? mod.module;
5333
6031
  const pkg = { name, dependencies: mod.dependencies };
5334
6032
  const node = {
5335
- id: (0, import_types9.serviceId)(name),
5336
- type: import_types9.NodeType.ServiceNode,
6033
+ id: (0, import_types10.serviceId)(name),
6034
+ type: import_types10.NodeType.ServiceNode,
5337
6035
  name,
5338
6036
  language: "go",
5339
6037
  dependencies: mod.dependencies,
5340
- repoPath: import_node_path11.default.relative(scanPath, dir),
6038
+ repoPath: import_node_path12.default.relative(scanPath, dir),
5341
6039
  ...mod.dependencies["github.com/gin-gonic/gin"] ? { framework: "gin" } : {}
5342
6040
  };
5343
6041
  return { pkg, dir, node };
@@ -5345,17 +6043,17 @@ async function discoverGoService(scanPath, dir) {
5345
6043
 
5346
6044
  // src/extract/owners.ts
5347
6045
  init_cjs_shims();
5348
- var import_node_fs11 = require("fs");
5349
- var import_node_path12 = __toESM(require("path"), 1);
6046
+ var import_node_fs12 = require("fs");
6047
+ var import_node_path13 = __toESM(require("path"), 1);
5350
6048
  var import_minimatch = require("minimatch");
5351
6049
  async function loadCodeowners(scanPath) {
5352
6050
  const candidates = [
5353
- import_node_path12.default.join(scanPath, "CODEOWNERS"),
5354
- import_node_path12.default.join(scanPath, ".github", "CODEOWNERS")
6051
+ import_node_path13.default.join(scanPath, "CODEOWNERS"),
6052
+ import_node_path13.default.join(scanPath, ".github", "CODEOWNERS")
5355
6053
  ];
5356
6054
  for (const file of candidates) {
5357
6055
  if (await exists2(file)) {
5358
- const raw = await import_node_fs11.promises.readFile(file, "utf8");
6056
+ const raw = await import_node_fs12.promises.readFile(file, "utf8");
5359
6057
  return parseCodeowners(raw);
5360
6058
  }
5361
6059
  }
@@ -5373,7 +6071,7 @@ function parseCodeowners(raw) {
5373
6071
  return { rules };
5374
6072
  }
5375
6073
  function matchOwner(file, repoPath) {
5376
- const normalized = repoPath.split(import_node_path12.default.sep).join("/");
6074
+ const normalized = repoPath.split(import_node_path13.default.sep).join("/");
5377
6075
  for (const rule of file.rules) {
5378
6076
  if (matchesPattern(rule.pattern, normalized)) return rule.owners;
5379
6077
  }
@@ -5389,7 +6087,7 @@ function matchesPattern(rawPattern, repoPath) {
5389
6087
  return false;
5390
6088
  }
5391
6089
  async function readPackageJsonAuthor(serviceDir) {
5392
- const pkgPath = import_node_path12.default.join(serviceDir, "package.json");
6090
+ const pkgPath = import_node_path13.default.join(serviceDir, "package.json");
5393
6091
  if (!await exists2(pkgPath)) return null;
5394
6092
  try {
5395
6093
  const pkg = await readJson(pkgPath);
@@ -5426,27 +6124,27 @@ function workspaceGlobs(pkg) {
5426
6124
  return null;
5427
6125
  }
5428
6126
  async function hasPythonManifest(dir) {
5429
- return await exists2(import_node_path13.default.join(dir, "pyproject.toml")) || await exists2(import_node_path13.default.join(dir, "requirements.txt")) || await exists2(import_node_path13.default.join(dir, "setup.py"));
6127
+ return await exists2(import_node_path14.default.join(dir, "pyproject.toml")) || await exists2(import_node_path14.default.join(dir, "requirements.txt")) || await exists2(import_node_path14.default.join(dir, "setup.py"));
5430
6128
  }
5431
6129
  async function hasGoManifest(dir) {
5432
- return exists2(import_node_path13.default.join(dir, "go.mod"));
6130
+ return exists2(import_node_path14.default.join(dir, "go.mod"));
5433
6131
  }
5434
6132
  async function loadGitignore(scanPath) {
5435
- const gitignorePath = import_node_path13.default.join(scanPath, ".gitignore");
6133
+ const gitignorePath = import_node_path14.default.join(scanPath, ".gitignore");
5436
6134
  if (!await exists2(gitignorePath)) return null;
5437
- const raw = await import_node_fs12.promises.readFile(gitignorePath, "utf8");
6135
+ const raw = await import_node_fs13.promises.readFile(gitignorePath, "utf8");
5438
6136
  return (0, import_ignore.default)().add(raw);
5439
6137
  }
5440
6138
  async function walkDirs(start, scanPath, options, visit) {
5441
6139
  async function recurse(current, depth) {
5442
6140
  if (depth > options.maxDepth) return;
5443
- const entries = await import_node_fs12.promises.readdir(current, { withFileTypes: true }).catch(() => []);
6141
+ const entries = await import_node_fs13.promises.readdir(current, { withFileTypes: true }).catch(() => []);
5444
6142
  for (const entry of entries) {
5445
6143
  if (!entry.isDirectory()) continue;
5446
6144
  if (IGNORED_DIRS.has(entry.name)) continue;
5447
- const child = import_node_path13.default.join(current, entry.name);
6145
+ const child = import_node_path14.default.join(current, entry.name);
5448
6146
  if (options.ig) {
5449
- const rel = import_node_path13.default.relative(scanPath, child).split(import_node_path13.default.sep).join("/");
6147
+ const rel = import_node_path14.default.relative(scanPath, child).split(import_node_path14.default.sep).join("/");
5450
6148
  if (rel && options.ig.ignores(rel + "/")) continue;
5451
6149
  }
5452
6150
  if (await isPythonVenvDir(child)) continue;
@@ -5462,8 +6160,8 @@ async function expandWorkspaceGlobs(scanPath, globs) {
5462
6160
  for (const raw of globs) {
5463
6161
  const pattern = raw.replace(/^\.\//, "");
5464
6162
  if (!pattern.includes("*")) {
5465
- const candidate = import_node_path13.default.join(scanPath, pattern);
5466
- if (await exists2(import_node_path13.default.join(candidate, "package.json"))) found.add(candidate);
6163
+ const candidate = import_node_path14.default.join(scanPath, pattern);
6164
+ if (await exists2(import_node_path14.default.join(candidate, "package.json"))) found.add(candidate);
5467
6165
  continue;
5468
6166
  }
5469
6167
  const segments = pattern.split("/");
@@ -5472,13 +6170,13 @@ async function expandWorkspaceGlobs(scanPath, globs) {
5472
6170
  if (seg.includes("*")) break;
5473
6171
  staticSegments.push(seg);
5474
6172
  }
5475
- const start = import_node_path13.default.join(scanPath, ...staticSegments);
6173
+ const start = import_node_path14.default.join(scanPath, ...staticSegments);
5476
6174
  if (!await exists2(start)) continue;
5477
6175
  const hasDoubleStar = pattern.includes("**");
5478
6176
  const walkDepth = hasDoubleStar ? scanDepth : Math.max(0, segments.length - staticSegments.length - 1);
5479
6177
  await walkDirs(start, scanPath, { maxDepth: walkDepth, ig: null }, async (dir) => {
5480
- const rel = import_node_path13.default.relative(scanPath, dir).split(import_node_path13.default.sep).join("/");
5481
- if ((0, import_minimatch2.minimatch)(rel, pattern) && await exists2(import_node_path13.default.join(dir, "package.json"))) {
6178
+ const rel = import_node_path14.default.relative(scanPath, dir).split(import_node_path14.default.sep).join("/");
6179
+ if ((0, import_minimatch2.minimatch)(rel, pattern) && await exists2(import_node_path14.default.join(dir, "package.json"))) {
5482
6180
  found.add(dir);
5483
6181
  }
5484
6182
  });
@@ -5501,31 +6199,31 @@ function detectJsFramework(pkg) {
5501
6199
  async function detectJsServiceLanguage(dir, pkg) {
5502
6200
  const deps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
5503
6201
  if (deps["typescript"] !== void 0) return "typescript";
5504
- const entries = await import_node_fs12.promises.readdir(dir).catch(() => []);
6202
+ const entries = await import_node_fs13.promises.readdir(dir).catch(() => []);
5505
6203
  if (entries.some((name) => /^tsconfig(\..+)?\.json$/.test(name))) return "typescript";
5506
6204
  return "javascript";
5507
6205
  }
5508
6206
  async function discoverNodeService(scanPath, dir) {
5509
- const pkgPath = import_node_path13.default.join(dir, "package.json");
6207
+ const pkgPath = import_node_path14.default.join(dir, "package.json");
5510
6208
  if (!await exists2(pkgPath)) return null;
5511
6209
  let pkg;
5512
6210
  try {
5513
6211
  pkg = await readJson(pkgPath);
5514
6212
  } catch (err) {
5515
- recordExtractionError("services", import_node_path13.default.relative(scanPath, pkgPath), err);
6213
+ recordExtractionError("services", import_node_path14.default.relative(scanPath, pkgPath), err);
5516
6214
  return null;
5517
6215
  }
5518
6216
  if (!pkg.name) return null;
5519
6217
  const framework = detectJsFramework(pkg);
5520
6218
  const language = await detectJsServiceLanguage(dir, pkg);
5521
6219
  const node = {
5522
- id: (0, import_types10.serviceId)(pkg.name),
5523
- type: import_types10.NodeType.ServiceNode,
6220
+ id: (0, import_types11.serviceId)(pkg.name),
6221
+ type: import_types11.NodeType.ServiceNode,
5524
6222
  name: pkg.name,
5525
6223
  language,
5526
6224
  version: pkg.version,
5527
6225
  dependencies: pkg.dependencies ?? {},
5528
- repoPath: import_node_path13.default.relative(scanPath, dir),
6226
+ repoPath: import_node_path14.default.relative(scanPath, dir),
5529
6227
  ...pkg.engines?.node ? { nodeEngine: pkg.engines.node } : {},
5530
6228
  ...framework ? { framework } : {}
5531
6229
  };
@@ -5536,18 +6234,18 @@ async function discoverPyService(scanPath, dir) {
5536
6234
  if (!py) return null;
5537
6235
  const pkg = pythonToPackage(py);
5538
6236
  const node = {
5539
- id: (0, import_types10.serviceId)(py.name),
5540
- type: import_types10.NodeType.ServiceNode,
6237
+ id: (0, import_types11.serviceId)(py.name),
6238
+ type: import_types11.NodeType.ServiceNode,
5541
6239
  name: py.name,
5542
6240
  language: "python",
5543
6241
  version: py.version,
5544
6242
  dependencies: py.dependencies,
5545
- repoPath: import_node_path13.default.relative(scanPath, dir)
6243
+ repoPath: import_node_path14.default.relative(scanPath, dir)
5546
6244
  };
5547
6245
  return { pkg, dir, node };
5548
6246
  }
5549
6247
  async function discoverServices(scanPath) {
5550
- const rootPkgPath = import_node_path13.default.join(scanPath, "package.json");
6248
+ const rootPkgPath = import_node_path14.default.join(scanPath, "package.json");
5551
6249
  let rootPkg = null;
5552
6250
  if (await exists2(rootPkgPath)) {
5553
6251
  try {
@@ -5555,7 +6253,7 @@ async function discoverServices(scanPath) {
5555
6253
  } catch (err) {
5556
6254
  recordExtractionError(
5557
6255
  "services workspaces",
5558
- import_node_path13.default.relative(scanPath, rootPkgPath),
6256
+ import_node_path14.default.relative(scanPath, rootPkgPath),
5559
6257
  err
5560
6258
  );
5561
6259
  }
@@ -5576,7 +6274,7 @@ async function discoverServices(scanPath) {
5576
6274
  scanPath,
5577
6275
  { maxDepth: parseScanDepth(), ig },
5578
6276
  async (dir) => {
5579
- if (await exists2(import_node_path13.default.join(dir, "package.json"))) {
6277
+ if (await exists2(import_node_path14.default.join(dir, "package.json"))) {
5580
6278
  candidateDirs.push(dir);
5581
6279
  } else if (await hasPythonManifest(dir) || await hasGoManifest(dir)) {
5582
6280
  candidateDirs.push(dir);
@@ -5592,8 +6290,8 @@ async function discoverServices(scanPath) {
5592
6290
  if (!service) continue;
5593
6291
  const existingDir = seen.get(service.node.name);
5594
6292
  if (existingDir !== void 0) {
5595
- const a = import_node_path13.default.relative(scanPath, existingDir) || ".";
5596
- const b = import_node_path13.default.relative(scanPath, dir) || ".";
6293
+ const a = import_node_path14.default.relative(scanPath, existingDir) || ".";
6294
+ const b = import_node_path14.default.relative(scanPath, dir) || ".";
5597
6295
  console.warn(
5598
6296
  `[neat] duplicate package name "${service.node.name}" \u2014 keeping ${a}, ignoring ${b}`
5599
6297
  );
@@ -5630,10 +6328,10 @@ function addServiceNodes(graph, services) {
5630
6328
 
5631
6329
  // src/extract/aliases.ts
5632
6330
  init_cjs_shims();
5633
- var import_node_path14 = __toESM(require("path"), 1);
5634
- var import_node_fs13 = require("fs");
6331
+ var import_node_path15 = __toESM(require("path"), 1);
6332
+ var import_node_fs14 = require("fs");
5635
6333
  var import_yaml2 = require("yaml");
5636
- var import_types11 = require("@neat.is/types");
6334
+ var import_types12 = require("@neat.is/types");
5637
6335
  var K8S_KINDS_WITH_HOSTNAMES = /* @__PURE__ */ new Set([
5638
6336
  "Service",
5639
6337
  "Deployment",
@@ -5643,7 +6341,7 @@ var K8S_KINDS_WITH_HOSTNAMES = /* @__PURE__ */ new Set([
5643
6341
  function addAliases(graph, serviceId7, candidates) {
5644
6342
  if (!graph.hasNode(serviceId7)) return;
5645
6343
  const node = graph.getNodeAttributes(serviceId7);
5646
- if (node.type !== import_types11.NodeType.ServiceNode) return;
6344
+ if (node.type !== import_types12.NodeType.ServiceNode) return;
5647
6345
  const set = new Set(node.aliases ?? []);
5648
6346
  for (const c of candidates) {
5649
6347
  if (!c) continue;
@@ -5658,14 +6356,14 @@ function indexServicesByName(services) {
5658
6356
  const map = /* @__PURE__ */ new Map();
5659
6357
  for (const s of services) {
5660
6358
  map.set(s.node.name, s.node.id);
5661
- map.set(import_node_path14.default.basename(s.dir), s.node.id);
6359
+ map.set(import_node_path15.default.basename(s.dir), s.node.id);
5662
6360
  }
5663
6361
  return map;
5664
6362
  }
5665
6363
  async function collectComposeAliases(graph, scanPath, serviceIndex) {
5666
6364
  let composePath = null;
5667
6365
  for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
5668
- const abs = import_node_path14.default.join(scanPath, name);
6366
+ const abs = import_node_path15.default.join(scanPath, name);
5669
6367
  if (await exists2(abs)) {
5670
6368
  composePath = abs;
5671
6369
  break;
@@ -5678,7 +6376,7 @@ async function collectComposeAliases(graph, scanPath, serviceIndex) {
5678
6376
  } catch (err) {
5679
6377
  recordExtractionError(
5680
6378
  "aliases compose",
5681
- import_node_path14.default.relative(scanPath, composePath),
6379
+ import_node_path15.default.relative(scanPath, composePath),
5682
6380
  err
5683
6381
  );
5684
6382
  return;
@@ -5721,11 +6419,11 @@ function parseDockerfileLabels(content) {
5721
6419
  }
5722
6420
  async function collectDockerfileAliases(graph, services) {
5723
6421
  for (const service of services) {
5724
- const dockerfilePath = import_node_path14.default.join(service.dir, "Dockerfile");
6422
+ const dockerfilePath = import_node_path15.default.join(service.dir, "Dockerfile");
5725
6423
  if (!await exists2(dockerfilePath)) continue;
5726
6424
  let content;
5727
6425
  try {
5728
- content = await import_node_fs13.promises.readFile(dockerfilePath, "utf8");
6426
+ content = await import_node_fs14.promises.readFile(dockerfilePath, "utf8");
5729
6427
  } catch (err) {
5730
6428
  recordExtractionError("aliases dockerfile", dockerfilePath, err);
5731
6429
  continue;
@@ -5737,15 +6435,15 @@ async function collectDockerfileAliases(graph, services) {
5737
6435
  async function walkYamlFiles(start, depth = 0, max = 5) {
5738
6436
  if (depth > max) return [];
5739
6437
  const out = [];
5740
- const entries = await import_node_fs13.promises.readdir(start, { withFileTypes: true }).catch(() => []);
6438
+ const entries = await import_node_fs14.promises.readdir(start, { withFileTypes: true }).catch(() => []);
5741
6439
  for (const entry of entries) {
5742
6440
  if (entry.isDirectory()) {
5743
6441
  if (IGNORED_DIRS.has(entry.name)) continue;
5744
- const child = import_node_path14.default.join(start, entry.name);
6442
+ const child = import_node_path15.default.join(start, entry.name);
5745
6443
  if (await isPythonVenvDir(child)) continue;
5746
6444
  out.push(...await walkYamlFiles(child, depth + 1, max));
5747
- } else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path14.default.extname(entry.name))) {
5748
- out.push(import_node_path14.default.join(start, entry.name));
6445
+ } else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path15.default.extname(entry.name))) {
6446
+ out.push(import_node_path15.default.join(start, entry.name));
5749
6447
  }
5750
6448
  }
5751
6449
  return out;
@@ -5772,614 +6470,230 @@ function k8sServiceTarget(doc, byName) {
5772
6470
  async function collectK8sAliases(graph, scanPath, serviceIndex) {
5773
6471
  const files = await walkYamlFiles(scanPath);
5774
6472
  for (const file of files) {
5775
- const content = await import_node_fs13.promises.readFile(file, "utf8");
6473
+ const content = await import_node_fs14.promises.readFile(file, "utf8");
5776
6474
  let docs;
5777
6475
  try {
5778
- docs = (0, import_yaml2.parseAllDocuments)(content).map((d) => d.toJSON());
5779
- } catch {
5780
- continue;
5781
- }
5782
- for (const doc of docs) {
5783
- if (!doc?.kind || !doc.metadata?.name) continue;
5784
- if (!K8S_KINDS_WITH_HOSTNAMES.has(doc.kind)) continue;
5785
- const target = k8sServiceTarget(doc, serviceIndex);
5786
- if (!target) continue;
5787
- addAliases(graph, target, k8sHostnames(doc.metadata.name, doc.metadata.namespace));
5788
- }
5789
- }
5790
- }
5791
- async function addServiceAliases(graph, scanPath, services) {
5792
- const byName = indexServicesByName(services);
5793
- await collectComposeAliases(graph, scanPath, byName);
5794
- await collectDockerfileAliases(graph, services);
5795
- await collectK8sAliases(graph, scanPath, byName);
5796
- }
5797
-
5798
- // src/extract/files.ts
5799
- init_cjs_shims();
5800
- var import_node_path15 = __toESM(require("path"), 1);
5801
- async function addFiles(graph, services) {
5802
- let nodesAdded = 0;
5803
- let edgesAdded = 0;
5804
- for (const service of services) {
5805
- const filePaths = await walkSourceFiles(service.dir);
5806
- for (const filePath of filePaths) {
5807
- const relPath = toPosix(import_node_path15.default.relative(service.dir, filePath));
5808
- const { nodesAdded: n, edgesAdded: e } = ensureFileNode(
5809
- graph,
5810
- service.pkg.name,
5811
- service.node.id,
5812
- relPath
5813
- );
5814
- nodesAdded += n;
5815
- edgesAdded += e;
5816
- }
5817
- }
5818
- return { nodesAdded, edgesAdded };
5819
- }
5820
-
5821
- // src/extract/symbols.ts
5822
- init_cjs_shims();
5823
- var import_node_path16 = __toESM(require("path"), 1);
5824
- var import_tree_sitter2 = __toESM(require("tree-sitter"), 1);
5825
- var import_tree_sitter_javascript2 = __toESM(require("tree-sitter-javascript"), 1);
5826
- var import_tree_sitter_typescript = __toESM(require("tree-sitter-typescript"), 1);
5827
- var import_types12 = require("@neat.is/types");
5828
- var PARSE_CHUNK2 = 16384;
5829
- var GRAMMAR_BY_EXT = {
5830
- ".ts": import_tree_sitter_typescript.default.typescript,
5831
- ".tsx": import_tree_sitter_typescript.default.tsx,
5832
- ".js": import_tree_sitter_javascript2.default,
5833
- ".jsx": import_tree_sitter_javascript2.default,
5834
- ".mjs": import_tree_sitter_javascript2.default,
5835
- ".cjs": import_tree_sitter_javascript2.default
5836
- };
5837
- function parseSource2(parser, source) {
5838
- return parser.parse(
5839
- (index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK2)
5840
- );
5841
- }
5842
- function methodName(node) {
5843
- const name = node.childForFieldName("name");
5844
- return name ? name.text : null;
5845
- }
5846
- function collectSymbolDefs(root) {
5847
- const out = [];
5848
- const push = (kind, qualname, node) => {
5849
- out.push({
5850
- kind,
5851
- qualname,
5852
- startLine: node.startPosition.row + 1,
5853
- endLine: node.endPosition.row + 1
5854
- });
5855
- };
5856
- const visit = (node, classCtx) => {
5857
- switch (node.type) {
5858
- case "function_declaration":
5859
- case "generator_function_declaration": {
5860
- const name = node.childForFieldName("name")?.text;
5861
- if (name) push("function", name, node);
5862
- break;
5863
- }
5864
- case "class_declaration":
5865
- case "abstract_class_declaration":
5866
- case "class": {
5867
- const name = node.childForFieldName("name")?.text;
5868
- if (name) push("class", name, node);
5869
- const body = node.childForFieldName("body");
5870
- if (body) {
5871
- for (let i = 0; i < body.namedChildCount; i++) {
5872
- const child = body.namedChild(i);
5873
- if (child) visit(child, name ?? classCtx);
5874
- }
5875
- }
5876
- return;
5877
- }
5878
- case "method_definition": {
5879
- const name = methodName(node);
5880
- if (name) {
5881
- const kind = name === "constructor" ? "constructor" : "method";
5882
- push(kind, classCtx ? `${classCtx}.${name}` : name, node);
5883
- }
5884
- break;
5885
- }
5886
- case "variable_declarator": {
5887
- const value = node.childForFieldName("value");
5888
- if (value && (value.type === "arrow_function" || value.type === "function" || value.type === "function_expression" || value.type === "generator_function")) {
5889
- const nameNode = node.childForFieldName("name");
5890
- if (nameNode && nameNode.type === "identifier") {
5891
- push("function", nameNode.text, node);
5892
- }
5893
- }
5894
- break;
5895
- }
5896
- }
5897
- for (let i = 0; i < node.namedChildCount; i++) {
5898
- const child = node.namedChild(i);
5899
- if (child) visit(child, classCtx);
5900
- }
5901
- };
5902
- visit(root, void 0);
5903
- return out;
5904
- }
5905
- function disambiguate(defs) {
5906
- const counts = /* @__PURE__ */ new Map();
5907
- for (const def of defs) counts.set(def.qualname, (counts.get(def.qualname) ?? 0) + 1);
5908
- const seen = /* @__PURE__ */ new Map();
5909
- return defs.map((def) => {
5910
- if ((counts.get(def.qualname) ?? 0) <= 1) return { def };
5911
- const ordinal = seen.get(def.qualname) ?? 0;
5912
- seen.set(def.qualname, ordinal + 1);
5913
- return { def, disambiguator: ordinal };
5914
- });
5915
- }
5916
- async function addSymbols(graph, services) {
5917
- const parsers = /* @__PURE__ */ new Map();
5918
- const parserForExt2 = (ext) => {
5919
- const grammar = GRAMMAR_BY_EXT[ext];
5920
- if (!grammar) return null;
5921
- let parser = parsers.get(ext);
5922
- if (!parser) {
5923
- parser = new import_tree_sitter2.default();
5924
- parser.setLanguage(grammar);
5925
- parsers.set(ext, parser);
6476
+ docs = (0, import_yaml2.parseAllDocuments)(content).map((d) => d.toJSON());
6477
+ } catch {
6478
+ continue;
5926
6479
  }
5927
- return parser;
5928
- };
6480
+ for (const doc of docs) {
6481
+ if (!doc?.kind || !doc.metadata?.name) continue;
6482
+ if (!K8S_KINDS_WITH_HOSTNAMES.has(doc.kind)) continue;
6483
+ const target = k8sServiceTarget(doc, serviceIndex);
6484
+ if (!target) continue;
6485
+ addAliases(graph, target, k8sHostnames(doc.metadata.name, doc.metadata.namespace));
6486
+ }
6487
+ }
6488
+ }
6489
+ async function addServiceAliases(graph, scanPath, services) {
6490
+ const byName = indexServicesByName(services);
6491
+ await collectComposeAliases(graph, scanPath, byName);
6492
+ await collectDockerfileAliases(graph, services);
6493
+ await collectK8sAliases(graph, scanPath, byName);
6494
+ }
6495
+
6496
+ // src/extract/files.ts
6497
+ init_cjs_shims();
6498
+ var import_node_path16 = __toESM(require("path"), 1);
6499
+ async function addFiles(graph, services) {
5929
6500
  let nodesAdded = 0;
5930
6501
  let edgesAdded = 0;
5931
6502
  for (const service of services) {
5932
- const files = await loadSourceFiles(service.dir);
5933
- for (const file of files) {
5934
- const parser = parserForExt2(import_node_path16.default.extname(file.path));
5935
- if (!parser) continue;
5936
- const relPath = toPosix(import_node_path16.default.relative(service.dir, file.path));
5937
- let defs;
5938
- try {
5939
- const tree = parseSource2(parser, file.content);
5940
- defs = collectSymbolDefs(tree.rootNode);
5941
- } catch (err) {
5942
- recordExtractionError("symbol extraction", file.path, err);
5943
- continue;
5944
- }
5945
- if (defs.length === 0) continue;
5946
- const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
6503
+ const filePaths = await walkSourceFiles(service.dir);
6504
+ for (const filePath of filePaths) {
6505
+ const relPath = toPosix(import_node_path16.default.relative(service.dir, filePath));
6506
+ const { nodesAdded: n, edgesAdded: e } = ensureFileNode(
5947
6507
  graph,
5948
6508
  service.pkg.name,
5949
6509
  service.node.id,
5950
6510
  relPath
5951
6511
  );
5952
- nodesAdded += fn;
5953
- edgesAdded += fe;
5954
- for (const { def, disambiguator } of disambiguate(defs)) {
5955
- const sid = (0, import_types12.symbolId)(service.pkg.name, relPath, def.qualname, disambiguator);
5956
- if (!graph.hasNode(sid)) {
5957
- const node = {
5958
- id: sid,
5959
- type: import_types12.NodeType.SymbolNode,
5960
- kind: def.kind,
5961
- qualname: def.qualname,
5962
- span: { startLine: def.startLine, endLine: def.endLine },
5963
- service: service.pkg.name,
5964
- relPath,
5965
- discoveredVia: "static"
5966
- };
5967
- graph.addNode(sid, node);
5968
- nodesAdded++;
5969
- }
5970
- const containsId = (0, import_types12.extractedEdgeId)(fileNodeId, sid, import_types12.EdgeType.CONTAINS);
5971
- if (!graph.hasEdge(containsId)) {
5972
- const edge = {
5973
- id: containsId,
5974
- source: fileNodeId,
5975
- target: sid,
5976
- type: import_types12.EdgeType.CONTAINS,
5977
- provenance: import_types12.Provenance.EXTRACTED,
5978
- confidence: (0, import_types12.confidenceForExtracted)("structural"),
5979
- evidence: {
5980
- file: relPath,
5981
- line: def.startLine,
5982
- snippet: snippet(file.content, def.startLine)
5983
- }
5984
- };
5985
- graph.addEdgeWithKey(containsId, fileNodeId, sid, edge);
5986
- edgesAdded++;
5987
- }
5988
- }
6512
+ nodesAdded += n;
6513
+ edgesAdded += e;
5989
6514
  }
5990
6515
  }
5991
6516
  return { nodesAdded, edgesAdded };
5992
6517
  }
5993
6518
 
5994
- // src/extract/symbol-edges.ts
5995
- init_cjs_shims();
5996
- var import_node_path18 = __toESM(require("path"), 1);
5997
- var import_tree_sitter4 = __toESM(require("tree-sitter"), 1);
5998
- var import_types14 = require("@neat.is/types");
5999
-
6000
- // src/extract/imports.ts
6519
+ // src/extract/symbols.ts
6001
6520
  init_cjs_shims();
6002
6521
  var import_node_path17 = __toESM(require("path"), 1);
6003
- var import_node_fs14 = require("fs");
6004
6522
  var import_tree_sitter3 = __toESM(require("tree-sitter"), 1);
6005
6523
  var import_tree_sitter_javascript3 = __toESM(require("tree-sitter-javascript"), 1);
6006
- var import_tree_sitter_python2 = __toESM(require("tree-sitter-python"), 1);
6007
- var import_tree_sitter_go2 = __toESM(require("tree-sitter-go"), 1);
6524
+ var import_tree_sitter_typescript = __toESM(require("tree-sitter-typescript"), 1);
6008
6525
  var import_types13 = require("@neat.is/types");
6009
6526
  var PARSE_CHUNK3 = 16384;
6527
+ var GRAMMAR_BY_EXT = {
6528
+ ".ts": import_tree_sitter_typescript.default.typescript,
6529
+ ".tsx": import_tree_sitter_typescript.default.tsx,
6530
+ ".js": import_tree_sitter_javascript3.default,
6531
+ ".jsx": import_tree_sitter_javascript3.default,
6532
+ ".mjs": import_tree_sitter_javascript3.default,
6533
+ ".cjs": import_tree_sitter_javascript3.default
6534
+ };
6010
6535
  function parseSource3(parser, source) {
6011
6536
  return parser.parse(
6012
6537
  (index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK3)
6013
- );
6014
- }
6015
- function makeJsParser2() {
6016
- const p = new import_tree_sitter3.default();
6017
- p.setLanguage(import_tree_sitter_javascript3.default);
6018
- return p;
6019
- }
6020
- function makePyParser2() {
6021
- const p = new import_tree_sitter3.default();
6022
- p.setLanguage(import_tree_sitter_python2.default);
6023
- return p;
6024
- }
6025
- function makeGoParser2() {
6026
- const p = new import_tree_sitter3.default();
6027
- p.setLanguage(import_tree_sitter_go2.default);
6028
- return p;
6029
- }
6030
- function stringLiteralText(node) {
6031
- for (let i = 0; i < node.childCount; i++) {
6032
- const child = node.child(i);
6033
- if (child?.type === "string_fragment") return child.text;
6034
- }
6035
- const raw = node.text;
6036
- if (raw.length >= 2) return raw.slice(1, -1);
6037
- return raw.length === 0 ? null : "";
6038
- }
6039
- function clipSnippet(text) {
6040
- const oneLine = text.split("\n")[0] ?? text;
6041
- return oneLine.length > 120 ? oneLine.slice(0, 120) : oneLine;
6042
- }
6043
- function collectGoImports(node, out) {
6044
- if (node.type === "import_spec") {
6045
- const pathNode = node.childForFieldName("path");
6046
- if (pathNode) {
6047
- const specifier = pathNode.text.replace(/^`|`$/g, "").replace(/^"|"$/g, "");
6048
- if (specifier) out.push({ specifier, line: node.startPosition.row + 1, snippet: clipSnippet(node.text) });
6049
- }
6050
- return;
6051
- }
6052
- for (let i = 0; i < node.namedChildCount; i++) {
6053
- const child = node.namedChild(i);
6054
- if (child) collectGoImports(child, out);
6055
- }
6056
- }
6057
- function collectJsImports(node, out) {
6058
- if (node.type === "import_statement") {
6059
- const source = node.childForFieldName("source");
6060
- if (source) {
6061
- const specifier = stringLiteralText(source);
6062
- if (specifier) {
6063
- out.push({ specifier, line: node.startPosition.row + 1, snippet: clipSnippet(node.text) });
6064
- }
6065
- }
6066
- return;
6067
- }
6068
- if (node.type === "call_expression") {
6069
- const fn = node.childForFieldName("function");
6070
- if (fn?.type === "identifier" && fn.text === "require") {
6071
- const args = node.childForFieldName("arguments");
6072
- const firstArg = args?.namedChild(0);
6073
- if (firstArg?.type === "string") {
6074
- const specifier = stringLiteralText(firstArg);
6075
- if (specifier) {
6076
- out.push({ specifier, line: node.startPosition.row + 1, snippet: clipSnippet(node.text) });
6077
- }
6078
- }
6079
- }
6080
- }
6081
- for (let i = 0; i < node.namedChildCount; i++) {
6082
- const child = node.namedChild(i);
6083
- if (child) collectJsImports(child, out);
6084
- }
6085
- }
6086
- function collectImportedNames(node, out) {
6087
- if (node.type === "aliased_import") {
6088
- const nameNode = node.childForFieldName("name");
6089
- if (nameNode) out.push(nameNode.text);
6090
- return;
6091
- }
6092
- if (node.type === "dotted_name") {
6093
- out.push(node.text);
6094
- return;
6095
- }
6096
- for (let i = 0; i < node.namedChildCount; i++) {
6097
- const child = node.namedChild(i);
6098
- if (child) collectImportedNames(child, out);
6099
- }
6100
- }
6101
- function collectPyImports(node, out) {
6102
- if (node.type === "import_from_statement") {
6103
- let level = 0;
6104
- let modulePath = "";
6105
- const names = [];
6106
- let pastFrom = false;
6107
- let pastImport = false;
6108
- for (let i = 0; i < node.childCount; i++) {
6109
- const child = node.child(i);
6110
- if (!child) continue;
6111
- if (!pastFrom) {
6112
- if (child.type === "from") pastFrom = true;
6113
- continue;
6114
- }
6115
- if (!pastImport) {
6116
- if (child.type === "import") {
6117
- pastImport = true;
6118
- continue;
6119
- }
6120
- if (child.type === "relative_import") {
6121
- for (let j = 0; j < child.childCount; j++) {
6122
- const rc = child.child(j);
6123
- if (!rc) continue;
6124
- if (rc.type === "import_prefix") {
6125
- for (let k = 0; k < rc.childCount; k++) {
6126
- if (rc.child(k)?.type === ".") level++;
6127
- }
6128
- } else if (rc.type === "dotted_name") modulePath = rc.text;
6129
- }
6130
- } else if (child.type === "dotted_name") {
6131
- modulePath = child.text;
6132
- }
6133
- continue;
6134
- }
6135
- collectImportedNames(child, names);
6136
- }
6137
- if (level > 0 || modulePath) {
6138
- out.push({ modulePath, level, names, line: node.startPosition.row + 1, snippet: clipSnippet(node.text) });
6139
- }
6140
- }
6141
- for (let i = 0; i < node.namedChildCount; i++) {
6142
- const child = node.namedChild(i);
6143
- if (child) collectPyImports(child, out);
6144
- }
6145
- }
6146
- async function fileExists2(p) {
6147
- try {
6148
- await import_node_fs14.promises.access(p);
6149
- return true;
6150
- } catch {
6151
- return false;
6152
- }
6153
- }
6154
- function isWithinServiceDir(candidate, serviceDir) {
6155
- const rel = import_node_path17.default.relative(serviceDir, candidate);
6156
- return rel !== "" && !rel.startsWith("..") && !import_node_path17.default.isAbsolute(rel);
6157
- }
6158
- var JS_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
6159
- var JS_INDEX_FILES = JS_EXTENSIONS.map((ext) => `index${ext}`);
6160
- async function firstExistingCandidate(base, serviceDir) {
6161
- for (const ext of JS_EXTENSIONS) {
6162
- const candidate = base + ext;
6163
- if (isWithinServiceDir(candidate, serviceDir) && await fileExists2(candidate)) {
6164
- return toPosix(import_node_path17.default.relative(serviceDir, candidate));
6165
- }
6166
- }
6167
- for (const indexFile of JS_INDEX_FILES) {
6168
- const candidate = import_node_path17.default.join(base, indexFile);
6169
- if (isWithinServiceDir(candidate, serviceDir) && await fileExists2(candidate)) {
6170
- return toPosix(import_node_path17.default.relative(serviceDir, candidate));
6171
- }
6172
- }
6173
- return null;
6174
- }
6175
- async function loadTsPathConfig(serviceDir) {
6176
- const tsconfigPath = import_node_path17.default.join(serviceDir, "tsconfig.json");
6177
- let raw;
6178
- try {
6179
- raw = await import_node_fs14.promises.readFile(tsconfigPath, "utf8");
6180
- } catch {
6181
- return null;
6182
- }
6183
- try {
6184
- const parsed = JSON.parse(raw);
6185
- const paths = parsed.compilerOptions?.paths;
6186
- if (!paths || Object.keys(paths).length === 0) return null;
6187
- const baseUrl = parsed.compilerOptions?.baseUrl;
6188
- return { paths, baseDir: baseUrl ? import_node_path17.default.resolve(serviceDir, baseUrl) : serviceDir };
6189
- } catch (err) {
6190
- recordExtractionError("import alias resolution", tsconfigPath, err);
6191
- return null;
6192
- }
6538
+ );
6193
6539
  }
6194
- async function resolveTsAlias(specifier, config, serviceDir) {
6195
- for (const [pattern, targets] of Object.entries(config.paths)) {
6196
- let suffix = null;
6197
- if (pattern === specifier) {
6198
- suffix = "";
6199
- } else if (pattern.endsWith("/*")) {
6200
- const prefix = pattern.slice(0, -1);
6201
- if (specifier.startsWith(prefix)) suffix = specifier.slice(prefix.length);
6202
- }
6203
- if (suffix === null) continue;
6204
- for (const target of targets) {
6205
- const targetBase = target.endsWith("/*") ? target.slice(0, -2) : target.replace(/\*$/, "");
6206
- const resolvedBase = import_node_path17.default.resolve(config.baseDir, targetBase, suffix);
6207
- const hit = await firstExistingCandidate(resolvedBase, serviceDir);
6208
- if (hit) return hit;
6209
- if (isWithinServiceDir(resolvedBase, serviceDir) && await fileExists2(resolvedBase)) {
6210
- return toPosix(import_node_path17.default.relative(serviceDir, resolvedBase));
6211
- }
6212
- }
6213
- }
6214
- return null;
6540
+ function methodName(node) {
6541
+ const name = node.childForFieldName("name");
6542
+ return name ? name.text : null;
6215
6543
  }
6216
- async function resolveJsImport(specifier, importerDir, serviceDir, tsPaths) {
6217
- if (!specifier) return null;
6218
- if (specifier.startsWith("./") || specifier.startsWith("../")) {
6219
- const base = import_node_path17.default.resolve(importerDir, specifier);
6220
- const ext = import_node_path17.default.extname(specifier);
6221
- if (ext) {
6222
- if (ext === ".js" || ext === ".jsx") {
6223
- const tsExt = ext === ".jsx" ? ".tsx" : ".ts";
6224
- const tsSibling = base.slice(0, -ext.length) + tsExt;
6225
- if (isWithinServiceDir(tsSibling, serviceDir) && await fileExists2(tsSibling)) {
6226
- return toPosix(import_node_path17.default.relative(serviceDir, tsSibling));
6544
+ function collectSymbolDefs(root) {
6545
+ const out = [];
6546
+ const push = (kind, qualname, node) => {
6547
+ out.push({
6548
+ kind,
6549
+ qualname,
6550
+ startLine: node.startPosition.row + 1,
6551
+ endLine: node.endPosition.row + 1
6552
+ });
6553
+ };
6554
+ const visit = (node, classCtx) => {
6555
+ switch (node.type) {
6556
+ case "function_declaration":
6557
+ case "generator_function_declaration": {
6558
+ const name = node.childForFieldName("name")?.text;
6559
+ if (name) push("function", name, node);
6560
+ break;
6561
+ }
6562
+ case "class_declaration":
6563
+ case "abstract_class_declaration":
6564
+ case "class": {
6565
+ const name = node.childForFieldName("name")?.text;
6566
+ if (name) push("class", name, node);
6567
+ const body = node.childForFieldName("body");
6568
+ if (body) {
6569
+ for (let i = 0; i < body.namedChildCount; i++) {
6570
+ const child = body.namedChild(i);
6571
+ if (child) visit(child, name ?? classCtx);
6572
+ }
6227
6573
  }
6574
+ return;
6228
6575
  }
6229
- if (isWithinServiceDir(base, serviceDir) && await fileExists2(base)) {
6230
- return toPosix(import_node_path17.default.relative(serviceDir, base));
6576
+ case "method_definition": {
6577
+ const name = methodName(node);
6578
+ if (name) {
6579
+ const kind = name === "constructor" ? "constructor" : "method";
6580
+ push(kind, classCtx ? `${classCtx}.${name}` : name, node);
6581
+ }
6582
+ break;
6231
6583
  }
6232
- return null;
6233
- }
6234
- return firstExistingCandidate(base, serviceDir);
6235
- }
6236
- if (tsPaths) return resolveTsAlias(specifier, tsPaths, serviceDir);
6237
- return null;
6238
- }
6239
- async function resolvePyImport(imp, importerPath, serviceDir) {
6240
- let baseDir;
6241
- if (imp.level > 0) {
6242
- baseDir = import_node_path17.default.dirname(importerPath);
6243
- for (let i = 1; i < imp.level; i++) baseDir = import_node_path17.default.dirname(baseDir);
6244
- } else {
6245
- baseDir = serviceDir;
6246
- }
6247
- const moduleBase = imp.modulePath ? import_node_path17.default.join(baseDir, imp.modulePath.split(".").join("/")) : baseDir;
6248
- const resolved = /* @__PURE__ */ new Set();
6249
- let needModuleFile = imp.names.length === 0;
6250
- for (const name of imp.names) {
6251
- const submoduleFile = import_node_path17.default.join(moduleBase, `${name}.py`);
6252
- const subpackageInit = import_node_path17.default.join(moduleBase, name, "__init__.py");
6253
- if (isWithinServiceDir(submoduleFile, serviceDir) && await fileExists2(submoduleFile)) {
6254
- resolved.add(toPosix(import_node_path17.default.relative(serviceDir, submoduleFile)));
6255
- } else if (isWithinServiceDir(subpackageInit, serviceDir) && await fileExists2(subpackageInit)) {
6256
- resolved.add(toPosix(import_node_path17.default.relative(serviceDir, subpackageInit)));
6257
- } else {
6258
- needModuleFile = true;
6259
- }
6260
- }
6261
- if (needModuleFile) {
6262
- const moduleFileCandidates = imp.modulePath ? [`${moduleBase}.py`, import_node_path17.default.join(moduleBase, "__init__.py")] : [import_node_path17.default.join(moduleBase, "__init__.py")];
6263
- for (const candidate of moduleFileCandidates) {
6264
- if (isWithinServiceDir(candidate, serviceDir) && await fileExists2(candidate)) {
6265
- resolved.add(toPosix(import_node_path17.default.relative(serviceDir, candidate)));
6584
+ case "variable_declarator": {
6585
+ const value = node.childForFieldName("value");
6586
+ if (value && (value.type === "arrow_function" || value.type === "function" || value.type === "function_expression" || value.type === "generator_function")) {
6587
+ const nameNode = node.childForFieldName("name");
6588
+ if (nameNode && nameNode.type === "identifier") {
6589
+ push("function", nameNode.text, node);
6590
+ }
6591
+ }
6266
6592
  break;
6267
6593
  }
6268
6594
  }
6269
- }
6270
- return [...resolved];
6595
+ for (let i = 0; i < node.namedChildCount; i++) {
6596
+ const child = node.namedChild(i);
6597
+ if (child) visit(child, classCtx);
6598
+ }
6599
+ };
6600
+ visit(root, void 0);
6601
+ return out;
6271
6602
  }
6272
- async function resolveGoImport(specifier, modulePath, serviceDir) {
6273
- if (specifier !== modulePath && !specifier.startsWith(`${modulePath}/`)) return null;
6274
- const suffix = specifier === modulePath ? "" : specifier.slice(modulePath.length + 1);
6275
- const dir = import_node_path17.default.join(serviceDir, suffix);
6276
- const entries = await import_node_fs14.promises.readdir(dir, { withFileTypes: true }).catch(() => []);
6277
- const candidates = entries.filter((entry) => entry.isFile() && entry.name.endsWith(".go") && !entry.name.endsWith("_test.go")).map((entry) => import_node_path17.default.join(dir, entry.name));
6278
- if (candidates.length !== 1) return null;
6279
- return toPosix(import_node_path17.default.relative(serviceDir, candidates[0]));
6603
+ function disambiguate(defs) {
6604
+ const counts = /* @__PURE__ */ new Map();
6605
+ for (const def of defs) counts.set(def.qualname, (counts.get(def.qualname) ?? 0) + 1);
6606
+ const seen = /* @__PURE__ */ new Map();
6607
+ return defs.map((def) => {
6608
+ if ((counts.get(def.qualname) ?? 0) <= 1) return { def };
6609
+ const ordinal = seen.get(def.qualname) ?? 0;
6610
+ seen.set(def.qualname, ordinal + 1);
6611
+ return { def, disambiguator: ordinal };
6612
+ });
6280
6613
  }
6281
- function emitImportEdge(graph, serviceName, importerFileId, importerRelPath, importeeRelPath, line, snippet2) {
6282
- const importeeFileId = (0, import_types13.fileId)(serviceName, importeeRelPath);
6283
- if (!graph.hasNode(importeeFileId)) return 0;
6284
- const edgeId = (0, import_types13.extractedEdgeId)(importerFileId, importeeFileId, import_types13.EdgeType.IMPORTS);
6285
- if (graph.hasEdge(edgeId)) return 0;
6286
- const edge = {
6287
- id: edgeId,
6288
- source: importerFileId,
6289
- target: importeeFileId,
6290
- type: import_types13.EdgeType.IMPORTS,
6291
- provenance: import_types13.Provenance.EXTRACTED,
6292
- confidence: (0, import_types13.confidenceForExtracted)("structural"),
6293
- evidence: { file: importerRelPath, line, snippet: snippet2 }
6614
+ async function addSymbols(graph, services) {
6615
+ const parsers = /* @__PURE__ */ new Map();
6616
+ const parserForExt2 = (ext) => {
6617
+ const grammar = GRAMMAR_BY_EXT[ext];
6618
+ if (!grammar) return null;
6619
+ let parser = parsers.get(ext);
6620
+ if (!parser) {
6621
+ parser = new import_tree_sitter3.default();
6622
+ parser.setLanguage(grammar);
6623
+ parsers.set(ext, parser);
6624
+ }
6625
+ return parser;
6294
6626
  };
6295
- graph.addEdgeWithKey(edgeId, importerFileId, importeeFileId, edge);
6296
- return 1;
6297
- }
6298
- async function addImports(graph, services) {
6299
- const jsParser = makeJsParser2();
6300
- const pyParser = makePyParser2();
6301
- const goParser = makeGoParser2();
6627
+ let nodesAdded = 0;
6302
6628
  let edgesAdded = 0;
6303
6629
  for (const service of services) {
6304
- const tsPaths = await loadTsPathConfig(service.dir);
6305
6630
  const files = await loadSourceFiles(service.dir);
6306
6631
  for (const file of files) {
6307
- if (isTestPath(file.path)) continue;
6308
- const relFile = toPosix(import_node_path17.default.relative(service.dir, file.path));
6309
- const importerFileId = (0, import_types13.fileId)(service.pkg.name, relFile);
6310
- const isPython = import_node_path17.default.extname(file.path) === ".py";
6311
- const isGo = import_node_path17.default.extname(file.path) === ".go";
6312
- if (isGo) {
6313
- let goImports = [];
6314
- try {
6315
- const tree = parseSource3(goParser, file.content);
6316
- collectGoImports(tree.rootNode, goImports);
6317
- } catch (err) {
6318
- recordExtractionError("import extraction", file.path, err);
6319
- continue;
6320
- }
6321
- const goMod = await import_node_fs14.promises.readFile(import_node_path17.default.join(service.dir, "go.mod"), "utf8").catch(() => "");
6322
- const modulePath = goMod.match(/^\s*module\s+(\S+)\s*$/m)?.[1];
6323
- if (!modulePath) continue;
6324
- for (const imp of goImports) {
6325
- const resolved = await resolveGoImport(imp.specifier, modulePath, service.dir);
6326
- if (!resolved) continue;
6327
- edgesAdded += emitImportEdge(graph, service.pkg.name, importerFileId, relFile, resolved, imp.line, imp.snippet);
6328
- }
6329
- continue;
6330
- }
6331
- if (isPython) {
6332
- let pyImports = [];
6333
- try {
6334
- const tree = parseSource3(pyParser, file.content);
6335
- collectPyImports(tree.rootNode, pyImports);
6336
- } catch (err) {
6337
- recordExtractionError("import extraction", file.path, err);
6338
- continue;
6339
- }
6340
- for (const imp of pyImports) {
6341
- const resolvedPaths = await resolvePyImport(imp, file.path, service.dir);
6342
- for (const resolved of resolvedPaths) {
6343
- edgesAdded += emitImportEdge(
6344
- graph,
6345
- service.pkg.name,
6346
- importerFileId,
6347
- relFile,
6348
- resolved,
6349
- imp.line,
6350
- imp.snippet
6351
- );
6352
- }
6353
- }
6354
- continue;
6355
- }
6356
- let jsImports = [];
6632
+ const parser = parserForExt2(import_node_path17.default.extname(file.path));
6633
+ if (!parser) continue;
6634
+ const relPath = toPosix(import_node_path17.default.relative(service.dir, file.path));
6635
+ let defs;
6357
6636
  try {
6358
- const tree = parseSource3(jsParser, file.content);
6359
- collectJsImports(tree.rootNode, jsImports);
6637
+ const tree = parseSource3(parser, file.content);
6638
+ defs = collectSymbolDefs(tree.rootNode);
6360
6639
  } catch (err) {
6361
- recordExtractionError("import extraction", file.path, err);
6640
+ recordExtractionError("symbol extraction", file.path, err);
6362
6641
  continue;
6363
6642
  }
6364
- for (const imp of jsImports) {
6365
- const resolved = await resolveJsImport(imp.specifier, import_node_path17.default.dirname(file.path), service.dir, tsPaths);
6366
- if (!resolved) continue;
6367
- edgesAdded += emitImportEdge(
6368
- graph,
6369
- service.pkg.name,
6370
- importerFileId,
6371
- relFile,
6372
- resolved,
6373
- imp.line,
6374
- imp.snippet
6375
- );
6643
+ if (defs.length === 0) continue;
6644
+ const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
6645
+ graph,
6646
+ service.pkg.name,
6647
+ service.node.id,
6648
+ relPath
6649
+ );
6650
+ nodesAdded += fn;
6651
+ edgesAdded += fe;
6652
+ for (const { def, disambiguator } of disambiguate(defs)) {
6653
+ const sid = (0, import_types13.symbolId)(service.pkg.name, relPath, def.qualname, disambiguator);
6654
+ if (!graph.hasNode(sid)) {
6655
+ const node = {
6656
+ id: sid,
6657
+ type: import_types13.NodeType.SymbolNode,
6658
+ kind: def.kind,
6659
+ qualname: def.qualname,
6660
+ span: { startLine: def.startLine, endLine: def.endLine },
6661
+ service: service.pkg.name,
6662
+ relPath,
6663
+ discoveredVia: "static"
6664
+ };
6665
+ graph.addNode(sid, node);
6666
+ nodesAdded++;
6667
+ }
6668
+ const containsId = (0, import_types13.extractedEdgeId)(fileNodeId, sid, import_types13.EdgeType.CONTAINS);
6669
+ if (!graph.hasEdge(containsId)) {
6670
+ const edge = {
6671
+ id: containsId,
6672
+ source: fileNodeId,
6673
+ target: sid,
6674
+ type: import_types13.EdgeType.CONTAINS,
6675
+ provenance: import_types13.Provenance.EXTRACTED,
6676
+ confidence: (0, import_types13.confidenceForExtracted)("structural"),
6677
+ evidence: {
6678
+ file: relPath,
6679
+ line: def.startLine,
6680
+ snippet: snippet(file.content, def.startLine)
6681
+ }
6682
+ };
6683
+ graph.addEdgeWithKey(containsId, fileNodeId, sid, edge);
6684
+ edgesAdded++;
6685
+ }
6376
6686
  }
6377
6687
  }
6378
6688
  }
6379
- return { nodesAdded: 0, edgesAdded };
6689
+ return { nodesAdded, edgesAdded };
6380
6690
  }
6381
6691
 
6382
6692
  // src/extract/symbol-edges.ts
6693
+ init_cjs_shims();
6694
+ var import_node_path18 = __toESM(require("path"), 1);
6695
+ var import_tree_sitter4 = __toESM(require("tree-sitter"), 1);
6696
+ var import_types14 = require("@neat.is/types");
6383
6697
  function extendsInfo(classHeritage) {
6384
6698
  for (let i = 0; i < classHeritage.namedChildCount; i++) {
6385
6699
  const child = classHeritage.namedChild(i);
@@ -6490,7 +6804,7 @@ async function addSymbolEdges(graph, services) {
6490
6804
  const fileDir = import_node_path18.default.dirname(file.path);
6491
6805
  let root;
6492
6806
  try {
6493
- root = parseSource2(parser, file.content).rootNode;
6807
+ root = parseSource3(parser, file.content).rootNode;
6494
6808
  } catch (err) {
6495
6809
  recordExtractionError("symbol edge extraction", file.path, err);
6496
6810
  continue;
@@ -8942,7 +9256,7 @@ function columnsFromObject(obj) {
8942
9256
  }
8943
9257
  function drizzleEndpointsFromFile(file, serviceDir) {
8944
9258
  if (!DRIZZLE_IMPORT_RE.test(file.content)) return [];
8945
- const tree = parseSource2(parserForExt(import_node_path39.default.extname(file.path)), file.content);
9259
+ const tree = parseSource3(parserForExt(import_node_path39.default.extname(file.path)), file.content);
8946
9260
  const out = [];
8947
9261
  const seen = /* @__PURE__ */ new Set();
8948
9262
  const walk6 = (node) => {