@neat.is/core 0.4.12 → 0.4.13

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.
@@ -454,19 +454,19 @@ function confidenceFromMix(edges, now = Date.now()) {
454
454
  function longestIncomingWalk(graph, start, maxDepth) {
455
455
  let best = { path: [start], edges: [] };
456
456
  const visited = /* @__PURE__ */ new Set([start]);
457
- function step(node, path36, edges) {
458
- if (path36.length > best.path.length) {
459
- best = { path: [...path36], edges: [...edges] };
457
+ function step(node, path38, edges) {
458
+ if (path38.length > best.path.length) {
459
+ best = { path: [...path38], edges: [...edges] };
460
460
  }
461
- if (path36.length - 1 >= maxDepth) return;
461
+ if (path38.length - 1 >= maxDepth) return;
462
462
  const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
463
463
  for (const [srcId, edge] of incoming) {
464
464
  if (visited.has(srcId)) continue;
465
465
  visited.add(srcId);
466
- path36.push(srcId);
466
+ path38.push(srcId);
467
467
  edges.push(edge);
468
- step(srcId, path36, edges);
469
- path36.pop();
468
+ step(srcId, path38, edges);
469
+ path38.pop();
470
470
  edges.pop();
471
471
  visited.delete(srcId);
472
472
  }
@@ -2519,16 +2519,6 @@ async function addServiceAliases(graph, scanPath, services) {
2519
2519
  await collectK8sAliases(graph, scanPath, byName);
2520
2520
  }
2521
2521
 
2522
- // src/extract/databases/index.ts
2523
- import path18 from "path";
2524
- import {
2525
- EdgeType as EdgeType5,
2526
- NodeType as NodeType7,
2527
- Provenance as Provenance3,
2528
- databaseId as databaseId2,
2529
- confidenceForExtracted as confidenceForExtracted2
2530
- } from "@neat.is/types";
2531
-
2532
2522
  // src/extract/calls/shared.ts
2533
2523
  import { promises as fs10 } from "fs";
2534
2524
  import path10 from "path";
@@ -2632,10 +2622,342 @@ function ensureFileNode(graph, serviceName, serviceNodeId, relPath) {
2632
2622
  return { fileNodeId, nodesAdded, edgesAdded };
2633
2623
  }
2634
2624
 
2635
- // src/extract/databases/db-config-yaml.ts
2625
+ // src/extract/files.ts
2636
2626
  import path11 from "path";
2627
+ async function addFiles(graph, services) {
2628
+ let nodesAdded = 0;
2629
+ let edgesAdded = 0;
2630
+ for (const service of services) {
2631
+ const filePaths = await walkSourceFiles(service.dir);
2632
+ for (const filePath of filePaths) {
2633
+ const relPath = toPosix2(path11.relative(service.dir, filePath));
2634
+ const { nodesAdded: n, edgesAdded: e } = ensureFileNode(
2635
+ graph,
2636
+ service.pkg.name,
2637
+ service.node.id,
2638
+ relPath
2639
+ );
2640
+ nodesAdded += n;
2641
+ edgesAdded += e;
2642
+ }
2643
+ }
2644
+ return { nodesAdded, edgesAdded };
2645
+ }
2646
+
2647
+ // src/extract/imports.ts
2648
+ import path12 from "path";
2649
+ import { promises as fs11 } from "fs";
2650
+ import Parser from "tree-sitter";
2651
+ import JavaScript from "tree-sitter-javascript";
2652
+ import Python from "tree-sitter-python";
2653
+ import {
2654
+ EdgeType as EdgeType5,
2655
+ Provenance as Provenance3,
2656
+ confidenceForExtracted as confidenceForExtracted2,
2657
+ extractedEdgeId as extractedEdgeId4,
2658
+ fileId as fileId3
2659
+ } from "@neat.is/types";
2660
+ var PARSE_CHUNK = 16384;
2661
+ function parseSource(parser, source) {
2662
+ return parser.parse(
2663
+ (index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK)
2664
+ );
2665
+ }
2666
+ function makeJsParser() {
2667
+ const p = new Parser();
2668
+ p.setLanguage(JavaScript);
2669
+ return p;
2670
+ }
2671
+ function makePyParser() {
2672
+ const p = new Parser();
2673
+ p.setLanguage(Python);
2674
+ return p;
2675
+ }
2676
+ function stringLiteralText(node) {
2677
+ for (let i = 0; i < node.childCount; i++) {
2678
+ const child = node.child(i);
2679
+ if (child?.type === "string_fragment") return child.text;
2680
+ }
2681
+ const raw = node.text;
2682
+ if (raw.length >= 2) return raw.slice(1, -1);
2683
+ return raw.length === 0 ? null : "";
2684
+ }
2685
+ function clipSnippet(text) {
2686
+ const oneLine = text.split("\n")[0] ?? text;
2687
+ return oneLine.length > 120 ? oneLine.slice(0, 120) : oneLine;
2688
+ }
2689
+ function collectJsImports(node, out) {
2690
+ if (node.type === "import_statement") {
2691
+ const source = node.childForFieldName("source");
2692
+ if (source) {
2693
+ const specifier = stringLiteralText(source);
2694
+ if (specifier) {
2695
+ out.push({ specifier, line: node.startPosition.row + 1, snippet: clipSnippet(node.text) });
2696
+ }
2697
+ }
2698
+ return;
2699
+ }
2700
+ if (node.type === "call_expression") {
2701
+ const fn = node.childForFieldName("function");
2702
+ if (fn?.type === "identifier" && fn.text === "require") {
2703
+ const args = node.childForFieldName("arguments");
2704
+ const firstArg = args?.namedChild(0);
2705
+ if (firstArg?.type === "string") {
2706
+ const specifier = stringLiteralText(firstArg);
2707
+ if (specifier) {
2708
+ out.push({ specifier, line: node.startPosition.row + 1, snippet: clipSnippet(node.text) });
2709
+ }
2710
+ }
2711
+ }
2712
+ }
2713
+ for (let i = 0; i < node.namedChildCount; i++) {
2714
+ const child = node.namedChild(i);
2715
+ if (child) collectJsImports(child, out);
2716
+ }
2717
+ }
2718
+ function collectPyImports(node, out) {
2719
+ if (node.type === "import_from_statement") {
2720
+ let level = 0;
2721
+ let modulePath = "";
2722
+ let pastFrom = false;
2723
+ for (let i = 0; i < node.childCount; i++) {
2724
+ const child = node.child(i);
2725
+ if (!child) continue;
2726
+ if (!pastFrom) {
2727
+ if (child.type === "from") pastFrom = true;
2728
+ continue;
2729
+ }
2730
+ if (child.type === "import") break;
2731
+ if (child.type === "relative_import") {
2732
+ for (let j = 0; j < child.childCount; j++) {
2733
+ const rc = child.child(j);
2734
+ if (!rc) continue;
2735
+ if (rc.type === "import_prefix") {
2736
+ for (let k = 0; k < rc.childCount; k++) {
2737
+ if (rc.child(k)?.type === ".") level++;
2738
+ }
2739
+ } else if (rc.type === "dotted_name") modulePath = rc.text;
2740
+ }
2741
+ break;
2742
+ }
2743
+ if (child.type === "dotted_name") {
2744
+ modulePath = child.text;
2745
+ break;
2746
+ }
2747
+ }
2748
+ if (level > 0 || modulePath) {
2749
+ out.push({ modulePath, level, line: node.startPosition.row + 1, snippet: clipSnippet(node.text) });
2750
+ }
2751
+ }
2752
+ for (let i = 0; i < node.namedChildCount; i++) {
2753
+ const child = node.namedChild(i);
2754
+ if (child) collectPyImports(child, out);
2755
+ }
2756
+ }
2757
+ async function fileExists(p) {
2758
+ try {
2759
+ await fs11.access(p);
2760
+ return true;
2761
+ } catch {
2762
+ return false;
2763
+ }
2764
+ }
2765
+ function isWithinServiceDir(candidate, serviceDir) {
2766
+ const rel = path12.relative(serviceDir, candidate);
2767
+ return rel !== "" && !rel.startsWith("..") && !path12.isAbsolute(rel);
2768
+ }
2769
+ var JS_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
2770
+ var JS_INDEX_FILES = JS_EXTENSIONS.map((ext) => `index${ext}`);
2771
+ async function firstExistingCandidate(base, serviceDir) {
2772
+ for (const ext of JS_EXTENSIONS) {
2773
+ const candidate = base + ext;
2774
+ if (isWithinServiceDir(candidate, serviceDir) && await fileExists(candidate)) {
2775
+ return toPosix2(path12.relative(serviceDir, candidate));
2776
+ }
2777
+ }
2778
+ for (const indexFile of JS_INDEX_FILES) {
2779
+ const candidate = path12.join(base, indexFile);
2780
+ if (isWithinServiceDir(candidate, serviceDir) && await fileExists(candidate)) {
2781
+ return toPosix2(path12.relative(serviceDir, candidate));
2782
+ }
2783
+ }
2784
+ return null;
2785
+ }
2786
+ async function loadTsPathConfig(serviceDir) {
2787
+ const tsconfigPath = path12.join(serviceDir, "tsconfig.json");
2788
+ let raw;
2789
+ try {
2790
+ raw = await fs11.readFile(tsconfigPath, "utf8");
2791
+ } catch {
2792
+ return null;
2793
+ }
2794
+ try {
2795
+ const parsed = JSON.parse(raw);
2796
+ const paths = parsed.compilerOptions?.paths;
2797
+ if (!paths || Object.keys(paths).length === 0) return null;
2798
+ const baseUrl = parsed.compilerOptions?.baseUrl;
2799
+ return { paths, baseDir: baseUrl ? path12.resolve(serviceDir, baseUrl) : serviceDir };
2800
+ } catch (err) {
2801
+ recordExtractionError("import alias resolution", tsconfigPath, err);
2802
+ return null;
2803
+ }
2804
+ }
2805
+ async function resolveTsAlias(specifier, config, serviceDir) {
2806
+ for (const [pattern, targets] of Object.entries(config.paths)) {
2807
+ let suffix = null;
2808
+ if (pattern === specifier) {
2809
+ suffix = "";
2810
+ } else if (pattern.endsWith("/*")) {
2811
+ const prefix = pattern.slice(0, -1);
2812
+ if (specifier.startsWith(prefix)) suffix = specifier.slice(prefix.length);
2813
+ }
2814
+ if (suffix === null) continue;
2815
+ for (const target of targets) {
2816
+ const targetBase = target.endsWith("/*") ? target.slice(0, -2) : target.replace(/\*$/, "");
2817
+ const resolvedBase = path12.resolve(config.baseDir, targetBase, suffix);
2818
+ const hit = await firstExistingCandidate(resolvedBase, serviceDir);
2819
+ if (hit) return hit;
2820
+ if (isWithinServiceDir(resolvedBase, serviceDir) && await fileExists(resolvedBase)) {
2821
+ return toPosix2(path12.relative(serviceDir, resolvedBase));
2822
+ }
2823
+ }
2824
+ }
2825
+ return null;
2826
+ }
2827
+ async function resolveJsImport(specifier, importerDir, serviceDir, tsPaths) {
2828
+ if (!specifier) return null;
2829
+ if (specifier.startsWith("./") || specifier.startsWith("../")) {
2830
+ const base = path12.resolve(importerDir, specifier);
2831
+ const ext = path12.extname(specifier);
2832
+ if (ext) {
2833
+ if (ext === ".js" || ext === ".jsx") {
2834
+ const tsExt = ext === ".jsx" ? ".tsx" : ".ts";
2835
+ const tsSibling = base.slice(0, -ext.length) + tsExt;
2836
+ if (isWithinServiceDir(tsSibling, serviceDir) && await fileExists(tsSibling)) {
2837
+ return toPosix2(path12.relative(serviceDir, tsSibling));
2838
+ }
2839
+ }
2840
+ if (isWithinServiceDir(base, serviceDir) && await fileExists(base)) {
2841
+ return toPosix2(path12.relative(serviceDir, base));
2842
+ }
2843
+ return null;
2844
+ }
2845
+ return firstExistingCandidate(base, serviceDir);
2846
+ }
2847
+ if (tsPaths) return resolveTsAlias(specifier, tsPaths, serviceDir);
2848
+ return null;
2849
+ }
2850
+ async function resolvePyImport(imp, importerPath, serviceDir) {
2851
+ if (!imp.modulePath) return null;
2852
+ const relPath = imp.modulePath.split(".").join("/");
2853
+ let baseDir;
2854
+ if (imp.level > 0) {
2855
+ baseDir = path12.dirname(importerPath);
2856
+ for (let i = 1; i < imp.level; i++) baseDir = path12.dirname(baseDir);
2857
+ } else {
2858
+ baseDir = serviceDir;
2859
+ }
2860
+ const candidates = [path12.join(baseDir, `${relPath}.py`), path12.join(baseDir, relPath, "__init__.py")];
2861
+ for (const candidate of candidates) {
2862
+ if (isWithinServiceDir(candidate, serviceDir) && await fileExists(candidate)) {
2863
+ return toPosix2(path12.relative(serviceDir, candidate));
2864
+ }
2865
+ }
2866
+ return null;
2867
+ }
2868
+ function emitImportEdge(graph, serviceName, importerFileId, importerRelPath, importeeRelPath, line, snippet2) {
2869
+ const importeeFileId = fileId3(serviceName, importeeRelPath);
2870
+ if (!graph.hasNode(importeeFileId)) return 0;
2871
+ const edgeId = extractedEdgeId4(importerFileId, importeeFileId, EdgeType5.IMPORTS);
2872
+ if (graph.hasEdge(edgeId)) return 0;
2873
+ const edge = {
2874
+ id: edgeId,
2875
+ source: importerFileId,
2876
+ target: importeeFileId,
2877
+ type: EdgeType5.IMPORTS,
2878
+ provenance: Provenance3.EXTRACTED,
2879
+ confidence: confidenceForExtracted2("structural"),
2880
+ evidence: { file: importerRelPath, line, snippet: snippet2 }
2881
+ };
2882
+ graph.addEdgeWithKey(edgeId, importerFileId, importeeFileId, edge);
2883
+ return 1;
2884
+ }
2885
+ async function addImports(graph, services) {
2886
+ const jsParser = makeJsParser();
2887
+ const pyParser = makePyParser();
2888
+ let edgesAdded = 0;
2889
+ for (const service of services) {
2890
+ const tsPaths = await loadTsPathConfig(service.dir);
2891
+ const files = await loadSourceFiles(service.dir);
2892
+ for (const file of files) {
2893
+ if (isTestPath(file.path)) continue;
2894
+ const relFile = toPosix2(path12.relative(service.dir, file.path));
2895
+ const importerFileId = fileId3(service.pkg.name, relFile);
2896
+ const isPython = path12.extname(file.path) === ".py";
2897
+ if (isPython) {
2898
+ let pyImports = [];
2899
+ try {
2900
+ const tree = parseSource(pyParser, file.content);
2901
+ collectPyImports(tree.rootNode, pyImports);
2902
+ } catch (err) {
2903
+ recordExtractionError("import extraction", file.path, err);
2904
+ continue;
2905
+ }
2906
+ for (const imp of pyImports) {
2907
+ const resolved = await resolvePyImport(imp, file.path, service.dir);
2908
+ if (!resolved) continue;
2909
+ edgesAdded += emitImportEdge(
2910
+ graph,
2911
+ service.pkg.name,
2912
+ importerFileId,
2913
+ relFile,
2914
+ resolved,
2915
+ imp.line,
2916
+ imp.snippet
2917
+ );
2918
+ }
2919
+ continue;
2920
+ }
2921
+ let jsImports = [];
2922
+ try {
2923
+ const tree = parseSource(jsParser, file.content);
2924
+ collectJsImports(tree.rootNode, jsImports);
2925
+ } catch (err) {
2926
+ recordExtractionError("import extraction", file.path, err);
2927
+ continue;
2928
+ }
2929
+ for (const imp of jsImports) {
2930
+ const resolved = await resolveJsImport(imp.specifier, path12.dirname(file.path), service.dir, tsPaths);
2931
+ if (!resolved) continue;
2932
+ edgesAdded += emitImportEdge(
2933
+ graph,
2934
+ service.pkg.name,
2935
+ importerFileId,
2936
+ relFile,
2937
+ resolved,
2938
+ imp.line,
2939
+ imp.snippet
2940
+ );
2941
+ }
2942
+ }
2943
+ }
2944
+ return { nodesAdded: 0, edgesAdded };
2945
+ }
2946
+
2947
+ // src/extract/databases/index.ts
2948
+ import path20 from "path";
2949
+ import {
2950
+ EdgeType as EdgeType6,
2951
+ NodeType as NodeType7,
2952
+ Provenance as Provenance4,
2953
+ databaseId as databaseId2,
2954
+ confidenceForExtracted as confidenceForExtracted3
2955
+ } from "@neat.is/types";
2956
+
2957
+ // src/extract/databases/db-config-yaml.ts
2958
+ import path13 from "path";
2637
2959
  async function parse(serviceDir) {
2638
- const yamlPath = path11.join(serviceDir, "db-config.yaml");
2960
+ const yamlPath = path13.join(serviceDir, "db-config.yaml");
2639
2961
  if (!await exists(yamlPath)) return [];
2640
2962
  const raw = await readYaml(yamlPath);
2641
2963
  return [
@@ -2652,12 +2974,12 @@ async function parse(serviceDir) {
2652
2974
  var dbConfigYamlParser = { name: "db-config.yaml", parse };
2653
2975
 
2654
2976
  // src/extract/databases/dotenv.ts
2655
- import { promises as fs12 } from "fs";
2656
- import path13 from "path";
2977
+ import { promises as fs13 } from "fs";
2978
+ import path15 from "path";
2657
2979
 
2658
2980
  // src/extract/databases/shared.ts
2659
- import { promises as fs11 } from "fs";
2660
- import path12 from "path";
2981
+ import { promises as fs12 } from "fs";
2982
+ import path14 from "path";
2661
2983
  function schemeToEngine(scheme) {
2662
2984
  const s = scheme.toLowerCase().split("+")[0];
2663
2985
  switch (s) {
@@ -2696,14 +3018,14 @@ function parseConnectionString(url) {
2696
3018
  }
2697
3019
  async function readIfExists(filePath) {
2698
3020
  try {
2699
- return await fs11.readFile(filePath, "utf8");
3021
+ return await fs12.readFile(filePath, "utf8");
2700
3022
  } catch {
2701
3023
  return null;
2702
3024
  }
2703
3025
  }
2704
3026
  async function findFirst(serviceDir, candidates) {
2705
3027
  for (const rel of candidates) {
2706
- const abs = path12.join(serviceDir, rel);
3028
+ const abs = path14.join(serviceDir, rel);
2707
3029
  const content = await readIfExists(abs);
2708
3030
  if (content !== null) return abs;
2709
3031
  }
@@ -2754,15 +3076,15 @@ function parseDotenvLine(line) {
2754
3076
  return { key, value };
2755
3077
  }
2756
3078
  async function parse2(serviceDir) {
2757
- const entries = await fs12.readdir(serviceDir, { withFileTypes: true }).catch(() => []);
3079
+ const entries = await fs13.readdir(serviceDir, { withFileTypes: true }).catch(() => []);
2758
3080
  const configs = [];
2759
3081
  const seen = /* @__PURE__ */ new Set();
2760
3082
  for (const entry of entries) {
2761
3083
  if (!entry.isFile()) continue;
2762
3084
  const match = isConfigFile(entry.name);
2763
3085
  if (!match.match || match.fileType !== "env") continue;
2764
- const filePath = path13.join(serviceDir, entry.name);
2765
- const content = await fs12.readFile(filePath, "utf8");
3086
+ const filePath = path15.join(serviceDir, entry.name);
3087
+ const content = await fs13.readFile(filePath, "utf8");
2766
3088
  for (const line of content.split("\n")) {
2767
3089
  const parsed = parseDotenvLine(line);
2768
3090
  if (!parsed) continue;
@@ -2780,9 +3102,9 @@ async function parse2(serviceDir) {
2780
3102
  var dotenvParser = { name: ".env", parse: parse2 };
2781
3103
 
2782
3104
  // src/extract/databases/prisma.ts
2783
- import path14 from "path";
3105
+ import path16 from "path";
2784
3106
  async function parse3(serviceDir) {
2785
- const schemaPath = path14.join(serviceDir, "prisma", "schema.prisma");
3107
+ const schemaPath = path16.join(serviceDir, "prisma", "schema.prisma");
2786
3108
  const content = await readIfExists(schemaPath);
2787
3109
  if (!content) return [];
2788
3110
  const block = content.match(/datasource\s+\w+\s*\{([^}]*)\}/s);
@@ -2911,10 +3233,10 @@ async function parse5(serviceDir) {
2911
3233
  var knexParser = { name: "knex", parse: parse5 };
2912
3234
 
2913
3235
  // src/extract/databases/ormconfig.ts
2914
- import path15 from "path";
3236
+ import path17 from "path";
2915
3237
  async function parse6(serviceDir) {
2916
3238
  for (const candidate of ["ormconfig.json", "ormconfig.yaml", "ormconfig.yml"]) {
2917
- const abs = path15.join(serviceDir, candidate);
3239
+ const abs = path17.join(serviceDir, candidate);
2918
3240
  if (!await exists(abs)) continue;
2919
3241
  const raw = candidate.endsWith(".json") ? await readJson(abs) : await readYaml(abs);
2920
3242
  const entries = Array.isArray(raw) ? raw : [raw];
@@ -2972,9 +3294,9 @@ async function parse7(serviceDir) {
2972
3294
  var typeormParser = { name: "typeorm", parse: parse7 };
2973
3295
 
2974
3296
  // src/extract/databases/sequelize.ts
2975
- import path16 from "path";
3297
+ import path18 from "path";
2976
3298
  async function parse8(serviceDir) {
2977
- const configPath = path16.join(serviceDir, "config", "config.json");
3299
+ const configPath = path18.join(serviceDir, "config", "config.json");
2978
3300
  if (!await exists(configPath)) return [];
2979
3301
  const raw = await readJson(configPath);
2980
3302
  const out = [];
@@ -3000,7 +3322,7 @@ async function parse8(serviceDir) {
3000
3322
  var sequelizeParser = { name: "sequelize", parse: parse8 };
3001
3323
 
3002
3324
  // src/extract/databases/docker-compose.ts
3003
- import path17 from "path";
3325
+ import path19 from "path";
3004
3326
  function portFromService(svc) {
3005
3327
  for (const raw of svc.ports ?? []) {
3006
3328
  const str = String(raw);
@@ -3027,7 +3349,7 @@ function databaseFromEnv(svc) {
3027
3349
  }
3028
3350
  async function parse9(serviceDir) {
3029
3351
  for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
3030
- const abs = path17.join(serviceDir, name);
3352
+ const abs = path19.join(serviceDir, name);
3031
3353
  if (!await exists(abs)) continue;
3032
3354
  const raw = await readYaml(abs);
3033
3355
  if (!raw?.services) return [];
@@ -3199,7 +3521,7 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
3199
3521
  discoveredVia: mergedDiscoveredVia
3200
3522
  });
3201
3523
  }
3202
- const relConfigFile = toPosix2(path18.relative(service.dir, config.sourceFile));
3524
+ const relConfigFile = toPosix2(path20.relative(service.dir, config.sourceFile));
3203
3525
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
3204
3526
  graph,
3205
3527
  service.pkg.name,
@@ -3208,14 +3530,14 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
3208
3530
  );
3209
3531
  nodesAdded += fn;
3210
3532
  edgesAdded += fe;
3211
- const evidenceFile = toPosix2(path18.relative(scanPath, config.sourceFile));
3533
+ const evidenceFile = toPosix2(path20.relative(scanPath, config.sourceFile));
3212
3534
  const edge = {
3213
- id: extractedEdgeId2(fileNodeId, dbNode.id, EdgeType5.CONNECTS_TO),
3535
+ id: extractedEdgeId2(fileNodeId, dbNode.id, EdgeType6.CONNECTS_TO),
3214
3536
  source: fileNodeId,
3215
3537
  target: dbNode.id,
3216
- type: EdgeType5.CONNECTS_TO,
3217
- provenance: Provenance3.EXTRACTED,
3218
- confidence: confidenceForExtracted2("structural"),
3538
+ type: EdgeType6.CONNECTS_TO,
3539
+ provenance: Provenance4.EXTRACTED,
3540
+ confidence: confidenceForExtracted3("structural"),
3219
3541
  evidence: { file: evidenceFile }
3220
3542
  };
3221
3543
  if (!graph.hasEdge(edge.id)) {
@@ -3241,21 +3563,21 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
3241
3563
  }
3242
3564
 
3243
3565
  // src/extract/configs.ts
3244
- import { promises as fs13 } from "fs";
3245
- import path19 from "path";
3566
+ import { promises as fs14 } from "fs";
3567
+ import path21 from "path";
3246
3568
  import {
3247
- EdgeType as EdgeType6,
3569
+ EdgeType as EdgeType7,
3248
3570
  NodeType as NodeType8,
3249
- Provenance as Provenance4,
3571
+ Provenance as Provenance5,
3250
3572
  configId,
3251
- confidenceForExtracted as confidenceForExtracted3
3573
+ confidenceForExtracted as confidenceForExtracted4
3252
3574
  } from "@neat.is/types";
3253
3575
  async function walkConfigFiles(dir) {
3254
3576
  const out = [];
3255
3577
  async function walk(current) {
3256
- const entries = await fs13.readdir(current, { withFileTypes: true });
3578
+ const entries = await fs14.readdir(current, { withFileTypes: true });
3257
3579
  for (const entry of entries) {
3258
- const full = path19.join(current, entry.name);
3580
+ const full = path21.join(current, entry.name);
3259
3581
  if (entry.isDirectory()) {
3260
3582
  if (IGNORED_DIRS.has(entry.name)) continue;
3261
3583
  if (await isPythonVenvDir(full)) continue;
@@ -3274,19 +3596,19 @@ async function addConfigNodes(graph, services, scanPath) {
3274
3596
  for (const service of services) {
3275
3597
  const configFiles = await walkConfigFiles(service.dir);
3276
3598
  for (const file of configFiles) {
3277
- const relPath = path19.relative(scanPath, file);
3599
+ const relPath = path21.relative(scanPath, file);
3278
3600
  const node = {
3279
3601
  id: configId(relPath),
3280
3602
  type: NodeType8.ConfigNode,
3281
- name: path19.basename(file),
3603
+ name: path21.basename(file),
3282
3604
  path: relPath,
3283
- fileType: isConfigFile(path19.basename(file)).fileType
3605
+ fileType: isConfigFile(path21.basename(file)).fileType
3284
3606
  };
3285
3607
  if (!graph.hasNode(node.id)) {
3286
3608
  graph.addNode(node.id, node);
3287
3609
  nodesAdded++;
3288
3610
  }
3289
- const relToService = toPosix2(path19.relative(service.dir, file));
3611
+ const relToService = toPosix2(path21.relative(service.dir, file));
3290
3612
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
3291
3613
  graph,
3292
3614
  service.pkg.name,
@@ -3296,13 +3618,13 @@ async function addConfigNodes(graph, services, scanPath) {
3296
3618
  nodesAdded += fn;
3297
3619
  edgesAdded += fe;
3298
3620
  const edge = {
3299
- id: extractedEdgeId2(fileNodeId, node.id, EdgeType6.CONFIGURED_BY),
3621
+ id: extractedEdgeId2(fileNodeId, node.id, EdgeType7.CONFIGURED_BY),
3300
3622
  source: fileNodeId,
3301
3623
  target: node.id,
3302
- type: EdgeType6.CONFIGURED_BY,
3303
- provenance: Provenance4.EXTRACTED,
3304
- confidence: confidenceForExtracted3("structural"),
3305
- evidence: { file: relPath.split(path19.sep).join("/") }
3624
+ type: EdgeType7.CONFIGURED_BY,
3625
+ provenance: Provenance5.EXTRACTED,
3626
+ confidence: confidenceForExtracted4("structural"),
3627
+ evidence: { file: relPath.split(path21.sep).join("/") }
3306
3628
  };
3307
3629
  if (!graph.hasEdge(edge.id)) {
3308
3630
  graph.addEdgeWithKey(edge.id, edge.source, edge.target, edge);
@@ -3315,22 +3637,22 @@ async function addConfigNodes(graph, services, scanPath) {
3315
3637
 
3316
3638
  // src/extract/calls/index.ts
3317
3639
  import {
3318
- EdgeType as EdgeType8,
3640
+ EdgeType as EdgeType9,
3319
3641
  NodeType as NodeType9,
3320
- Provenance as Provenance6,
3321
- confidenceForExtracted as confidenceForExtracted5,
3642
+ Provenance as Provenance7,
3643
+ confidenceForExtracted as confidenceForExtracted6,
3322
3644
  passesExtractedFloor as passesExtractedFloor2
3323
3645
  } from "@neat.is/types";
3324
3646
 
3325
3647
  // src/extract/calls/http.ts
3326
- import path20 from "path";
3327
- import Parser from "tree-sitter";
3328
- import JavaScript from "tree-sitter-javascript";
3329
- import Python from "tree-sitter-python";
3648
+ import path22 from "path";
3649
+ import Parser2 from "tree-sitter";
3650
+ import JavaScript2 from "tree-sitter-javascript";
3651
+ import Python2 from "tree-sitter-python";
3330
3652
  import {
3331
- EdgeType as EdgeType7,
3332
- Provenance as Provenance5,
3333
- confidenceForExtracted as confidenceForExtracted4,
3653
+ EdgeType as EdgeType8,
3654
+ Provenance as Provenance6,
3655
+ confidenceForExtracted as confidenceForExtracted5,
3334
3656
  passesExtractedFloor
3335
3657
  } from "@neat.is/types";
3336
3658
  var STRING_LITERAL_NODE_TYPES = /* @__PURE__ */ new Set(["string_fragment", "string_content"]);
@@ -3360,14 +3682,14 @@ function collectStringLiterals(node, out) {
3360
3682
  if (child) collectStringLiterals(child, out);
3361
3683
  }
3362
3684
  }
3363
- var PARSE_CHUNK = 16384;
3364
- function parseSource(parser, source) {
3685
+ var PARSE_CHUNK2 = 16384;
3686
+ function parseSource2(parser, source) {
3365
3687
  return parser.parse(
3366
- (index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK)
3688
+ (index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK2)
3367
3689
  );
3368
3690
  }
3369
3691
  function callsFromSource(source, parser, knownHosts) {
3370
- const tree = parseSource(parser, source);
3692
+ const tree = parseSource2(parser, source);
3371
3693
  const literals = [];
3372
3694
  collectStringLiterals(tree.rootNode, literals);
3373
3695
  const out = [];
@@ -3381,25 +3703,25 @@ function callsFromSource(source, parser, knownHosts) {
3381
3703
  }
3382
3704
  return out;
3383
3705
  }
3384
- function makeJsParser() {
3385
- const p = new Parser();
3386
- p.setLanguage(JavaScript);
3706
+ function makeJsParser2() {
3707
+ const p = new Parser2();
3708
+ p.setLanguage(JavaScript2);
3387
3709
  return p;
3388
3710
  }
3389
- function makePyParser() {
3390
- const p = new Parser();
3391
- p.setLanguage(Python);
3711
+ function makePyParser2() {
3712
+ const p = new Parser2();
3713
+ p.setLanguage(Python2);
3392
3714
  return p;
3393
3715
  }
3394
3716
  async function addHttpCallEdges(graph, services) {
3395
- const jsParser = makeJsParser();
3396
- const pyParser = makePyParser();
3717
+ const jsParser = makeJsParser2();
3718
+ const pyParser = makePyParser2();
3397
3719
  const knownHosts = /* @__PURE__ */ new Set();
3398
3720
  const hostToNodeId = /* @__PURE__ */ new Map();
3399
3721
  for (const service of services) {
3400
- knownHosts.add(path20.basename(service.dir));
3722
+ knownHosts.add(path22.basename(service.dir));
3401
3723
  knownHosts.add(service.pkg.name);
3402
- hostToNodeId.set(path20.basename(service.dir), service.node.id);
3724
+ hostToNodeId.set(path22.basename(service.dir), service.node.id);
3403
3725
  hostToNodeId.set(service.pkg.name, service.node.id);
3404
3726
  }
3405
3727
  let nodesAdded = 0;
@@ -3409,7 +3731,7 @@ async function addHttpCallEdges(graph, services) {
3409
3731
  const seen = /* @__PURE__ */ new Set();
3410
3732
  for (const file of files) {
3411
3733
  if (isTestPath(file.path)) continue;
3412
- const parser = path20.extname(file.path) === ".py" ? pyParser : jsParser;
3734
+ const parser = path22.extname(file.path) === ".py" ? pyParser : jsParser;
3413
3735
  let sites;
3414
3736
  try {
3415
3737
  sites = callsFromSource(file.content, parser, knownHosts);
@@ -3418,14 +3740,14 @@ async function addHttpCallEdges(graph, services) {
3418
3740
  continue;
3419
3741
  }
3420
3742
  if (sites.length === 0) continue;
3421
- const relFile = toPosix2(path20.relative(service.dir, file.path));
3743
+ const relFile = toPosix2(path22.relative(service.dir, file.path));
3422
3744
  for (const site of sites) {
3423
3745
  const targetId = hostToNodeId.get(site.host);
3424
3746
  if (!targetId || targetId === service.node.id) continue;
3425
3747
  const dedupKey = `${relFile}|${targetId}`;
3426
3748
  if (seen.has(dedupKey)) continue;
3427
3749
  seen.add(dedupKey);
3428
- const confidence = confidenceForExtracted4("hostname-shape-match");
3750
+ const confidence = confidenceForExtracted5("hostname-shape-match");
3429
3751
  const ev = {
3430
3752
  file: relFile,
3431
3753
  line: site.line,
@@ -3443,21 +3765,21 @@ async function addHttpCallEdges(graph, services) {
3443
3765
  noteExtractedDropped({
3444
3766
  source: fileNodeId,
3445
3767
  target: targetId,
3446
- type: EdgeType7.CALLS,
3768
+ type: EdgeType8.CALLS,
3447
3769
  confidence,
3448
3770
  confidenceKind: "hostname-shape-match",
3449
3771
  evidence: ev
3450
3772
  });
3451
3773
  continue;
3452
3774
  }
3453
- const edgeId = extractedEdgeId2(fileNodeId, targetId, EdgeType7.CALLS);
3775
+ const edgeId = extractedEdgeId2(fileNodeId, targetId, EdgeType8.CALLS);
3454
3776
  if (!graph.hasEdge(edgeId)) {
3455
3777
  const edge = {
3456
3778
  id: edgeId,
3457
3779
  source: fileNodeId,
3458
3780
  target: targetId,
3459
- type: EdgeType7.CALLS,
3460
- provenance: Provenance5.EXTRACTED,
3781
+ type: EdgeType8.CALLS,
3782
+ provenance: Provenance6.EXTRACTED,
3461
3783
  confidence,
3462
3784
  evidence: ev
3463
3785
  };
@@ -3471,7 +3793,7 @@ async function addHttpCallEdges(graph, services) {
3471
3793
  }
3472
3794
 
3473
3795
  // src/extract/calls/kafka.ts
3474
- import path21 from "path";
3796
+ import path23 from "path";
3475
3797
  import { infraId } from "@neat.is/types";
3476
3798
  var PRODUCER_TOPIC_RE = /(?:producer|kafkaProducer)[\s\S]{0,40}?\.send\s*\(\s*\{[\s\S]{0,200}?topic\s*:\s*['"`]([^'"`]+)['"`]/g;
3477
3799
  var CONSUMER_TOPIC_RE = /(?:consumer|kafkaConsumer)[\s\S]{0,40}?\.(?:subscribe|run)\s*\(\s*\{[\s\S]{0,200}?topic[s]?\s*:\s*(?:\[\s*)?['"`]([^'"`]+)['"`]/g;
@@ -3502,7 +3824,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
3502
3824
  // tier (ADR-066).
3503
3825
  confidenceKind: "verified-call-site",
3504
3826
  evidence: {
3505
- file: path21.relative(serviceDir, file.path),
3827
+ file: path23.relative(serviceDir, file.path),
3506
3828
  line,
3507
3829
  snippet: snippet(file.content, line)
3508
3830
  }
@@ -3514,7 +3836,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
3514
3836
  }
3515
3837
 
3516
3838
  // src/extract/calls/redis.ts
3517
- import path22 from "path";
3839
+ import path24 from "path";
3518
3840
  import { infraId as infraId2 } from "@neat.is/types";
3519
3841
  var REDIS_URL_RE = /redis(?:s)?:\/\/(?:[^@'"`\s]+@)?([^:/'"`\s]+)(?::(\d+))?/g;
3520
3842
  function redisEndpointsFromFile(file, serviceDir) {
@@ -3537,7 +3859,7 @@ function redisEndpointsFromFile(file, serviceDir) {
3537
3859
  // support tier (ADR-066).
3538
3860
  confidenceKind: "url-with-structural-support",
3539
3861
  evidence: {
3540
- file: path22.relative(serviceDir, file.path),
3862
+ file: path24.relative(serviceDir, file.path),
3541
3863
  line,
3542
3864
  snippet: snippet(file.content, line)
3543
3865
  }
@@ -3547,7 +3869,7 @@ function redisEndpointsFromFile(file, serviceDir) {
3547
3869
  }
3548
3870
 
3549
3871
  // src/extract/calls/aws.ts
3550
- import path23 from "path";
3872
+ import path25 from "path";
3551
3873
  import { infraId as infraId3 } from "@neat.is/types";
3552
3874
  var S3_BUCKET_RE = /Bucket\s*:\s*['"`]([^'"`]+)['"`]/g;
3553
3875
  var DYNAMO_TABLE_RE = /TableName\s*:\s*['"`]([^'"`]+)['"`]/g;
@@ -3581,7 +3903,7 @@ function awsEndpointsFromFile(file, serviceDir) {
3581
3903
  // (ADR-066).
3582
3904
  confidenceKind: "verified-call-site",
3583
3905
  evidence: {
3584
- file: path23.relative(serviceDir, file.path),
3906
+ file: path25.relative(serviceDir, file.path),
3585
3907
  line,
3586
3908
  snippet: snippet(file.content, line)
3587
3909
  }
@@ -3605,7 +3927,7 @@ function awsEndpointsFromFile(file, serviceDir) {
3605
3927
  }
3606
3928
 
3607
3929
  // src/extract/calls/grpc.ts
3608
- import path24 from "path";
3930
+ import path26 from "path";
3609
3931
  import { infraId as infraId4 } from "@neat.is/types";
3610
3932
  var GRPC_CLIENT_RE = /new\s+([A-Z][A-Za-z0-9_]*)Client\s*\(\s*['"`]?([^,'"`)]+)?/g;
3611
3933
  var AWS_SDK_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@aws-sdk\/client-([a-z0-9-]+)['"`]/g;
@@ -3664,7 +3986,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
3664
3986
  // tier (ADR-066).
3665
3987
  confidenceKind: "verified-call-site",
3666
3988
  evidence: {
3667
- file: path24.relative(serviceDir, file.path),
3989
+ file: path26.relative(serviceDir, file.path),
3668
3990
  line,
3669
3991
  snippet: snippet(file.content, line)
3670
3992
  }
@@ -3677,11 +3999,11 @@ function grpcEndpointsFromFile(file, serviceDir) {
3677
3999
  function edgeTypeFromEndpoint(ep) {
3678
4000
  switch (ep.edgeType) {
3679
4001
  case "PUBLISHES_TO":
3680
- return EdgeType8.PUBLISHES_TO;
4002
+ return EdgeType9.PUBLISHES_TO;
3681
4003
  case "CONSUMES_FROM":
3682
- return EdgeType8.CONSUMES_FROM;
4004
+ return EdgeType9.CONSUMES_FROM;
3683
4005
  default:
3684
- return EdgeType8.CALLS;
4006
+ return EdgeType9.CALLS;
3685
4007
  }
3686
4008
  }
3687
4009
  function isAwsKind(kind) {
@@ -3720,7 +4042,7 @@ async function addExternalEndpointEdges(graph, services) {
3720
4042
  nodesAdded++;
3721
4043
  }
3722
4044
  const edgeType = edgeTypeFromEndpoint(ep);
3723
- const confidence = confidenceForExtracted5(ep.confidenceKind);
4045
+ const confidence = confidenceForExtracted6(ep.confidenceKind);
3724
4046
  const relFile = toPosix2(ep.evidence.file);
3725
4047
  const { fileNodeId, nodesAdded: n, edgesAdded: e } = ensureFileNode(
3726
4048
  graph,
@@ -3750,7 +4072,7 @@ async function addExternalEndpointEdges(graph, services) {
3750
4072
  source: fileNodeId,
3751
4073
  target: ep.infraId,
3752
4074
  type: edgeType,
3753
- provenance: Provenance6.EXTRACTED,
4075
+ provenance: Provenance7.EXTRACTED,
3754
4076
  confidence,
3755
4077
  evidence: ep.evidence
3756
4078
  };
@@ -3771,8 +4093,8 @@ async function addCallEdges(graph, services) {
3771
4093
  }
3772
4094
 
3773
4095
  // src/extract/infra/docker-compose.ts
3774
- import path25 from "path";
3775
- import { EdgeType as EdgeType9, Provenance as Provenance7, confidenceForExtracted as confidenceForExtracted6 } from "@neat.is/types";
4096
+ import path27 from "path";
4097
+ import { EdgeType as EdgeType10, Provenance as Provenance8, confidenceForExtracted as confidenceForExtracted7 } from "@neat.is/types";
3776
4098
 
3777
4099
  // src/extract/infra/shared.ts
3778
4100
  import { NodeType as NodeType10, infraId as infraId5 } from "@neat.is/types";
@@ -3808,7 +4130,7 @@ function dependsOnList(value) {
3808
4130
  }
3809
4131
  function serviceNameToServiceNode(name, services) {
3810
4132
  for (const s of services) {
3811
- if (s.node.name === name || path25.basename(s.dir) === name) return s.node.id;
4133
+ if (s.node.name === name || path27.basename(s.dir) === name) return s.node.id;
3812
4134
  }
3813
4135
  return null;
3814
4136
  }
@@ -3817,7 +4139,7 @@ async function addComposeInfra(graph, scanPath, services) {
3817
4139
  let edgesAdded = 0;
3818
4140
  let composePath = null;
3819
4141
  for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
3820
- const abs = path25.join(scanPath, name);
4142
+ const abs = path27.join(scanPath, name);
3821
4143
  if (await exists(abs)) {
3822
4144
  composePath = abs;
3823
4145
  break;
@@ -3830,13 +4152,13 @@ async function addComposeInfra(graph, scanPath, services) {
3830
4152
  } catch (err) {
3831
4153
  recordExtractionError(
3832
4154
  "infra docker-compose",
3833
- path25.relative(scanPath, composePath),
4155
+ path27.relative(scanPath, composePath),
3834
4156
  err
3835
4157
  );
3836
4158
  return { nodesAdded, edgesAdded };
3837
4159
  }
3838
4160
  if (!compose?.services) return { nodesAdded, edgesAdded };
3839
- const evidenceFile = path25.relative(scanPath, composePath).split(path25.sep).join("/");
4161
+ const evidenceFile = path27.relative(scanPath, composePath).split(path27.sep).join("/");
3840
4162
  const composeNameToNodeId = /* @__PURE__ */ new Map();
3841
4163
  for (const [composeName, svc] of Object.entries(compose.services)) {
3842
4164
  const matchedServiceId = serviceNameToServiceNode(composeName, services);
@@ -3858,15 +4180,15 @@ async function addComposeInfra(graph, scanPath, services) {
3858
4180
  for (const dep of dependsOnList(svc.depends_on)) {
3859
4181
  const targetId = composeNameToNodeId.get(dep);
3860
4182
  if (!targetId) continue;
3861
- const edgeId = extractedEdgeId2(sourceId, targetId, EdgeType9.DEPENDS_ON);
4183
+ const edgeId = extractedEdgeId2(sourceId, targetId, EdgeType10.DEPENDS_ON);
3862
4184
  if (graph.hasEdge(edgeId)) continue;
3863
4185
  const edge = {
3864
4186
  id: edgeId,
3865
4187
  source: sourceId,
3866
4188
  target: targetId,
3867
- type: EdgeType9.DEPENDS_ON,
3868
- provenance: Provenance7.EXTRACTED,
3869
- confidence: confidenceForExtracted6("structural"),
4189
+ type: EdgeType10.DEPENDS_ON,
4190
+ provenance: Provenance8.EXTRACTED,
4191
+ confidence: confidenceForExtracted7("structural"),
3870
4192
  evidence: { file: evidenceFile }
3871
4193
  };
3872
4194
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -3877,9 +4199,9 @@ async function addComposeInfra(graph, scanPath, services) {
3877
4199
  }
3878
4200
 
3879
4201
  // src/extract/infra/dockerfile.ts
3880
- import path26 from "path";
3881
- import { promises as fs14 } from "fs";
3882
- import { EdgeType as EdgeType10, Provenance as Provenance8, confidenceForExtracted as confidenceForExtracted7 } from "@neat.is/types";
4202
+ import path28 from "path";
4203
+ import { promises as fs15 } from "fs";
4204
+ import { EdgeType as EdgeType11, Provenance as Provenance9, confidenceForExtracted as confidenceForExtracted8 } from "@neat.is/types";
3883
4205
  function runtimeImage(content) {
3884
4206
  const lines = content.split("\n");
3885
4207
  let last = null;
@@ -3898,15 +4220,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
3898
4220
  let nodesAdded = 0;
3899
4221
  let edgesAdded = 0;
3900
4222
  for (const service of services) {
3901
- const dockerfilePath = path26.join(service.dir, "Dockerfile");
4223
+ const dockerfilePath = path28.join(service.dir, "Dockerfile");
3902
4224
  if (!await exists(dockerfilePath)) continue;
3903
4225
  let content;
3904
4226
  try {
3905
- content = await fs14.readFile(dockerfilePath, "utf8");
4227
+ content = await fs15.readFile(dockerfilePath, "utf8");
3906
4228
  } catch (err) {
3907
4229
  recordExtractionError(
3908
4230
  "infra dockerfile",
3909
- path26.relative(scanPath, dockerfilePath),
4231
+ path28.relative(scanPath, dockerfilePath),
3910
4232
  err
3911
4233
  );
3912
4234
  continue;
@@ -3918,7 +4240,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
3918
4240
  graph.addNode(node.id, node);
3919
4241
  nodesAdded++;
3920
4242
  }
3921
- const relDockerfile = toPosix2(path26.relative(service.dir, dockerfilePath));
4243
+ const relDockerfile = toPosix2(path28.relative(service.dir, dockerfilePath));
3922
4244
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
3923
4245
  graph,
3924
4246
  service.pkg.name,
@@ -3927,17 +4249,17 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
3927
4249
  );
3928
4250
  nodesAdded += fn;
3929
4251
  edgesAdded += fe;
3930
- const edgeId = extractedEdgeId2(fileNodeId, node.id, EdgeType10.RUNS_ON);
4252
+ const edgeId = extractedEdgeId2(fileNodeId, node.id, EdgeType11.RUNS_ON);
3931
4253
  if (!graph.hasEdge(edgeId)) {
3932
4254
  const edge = {
3933
4255
  id: edgeId,
3934
4256
  source: fileNodeId,
3935
4257
  target: node.id,
3936
- type: EdgeType10.RUNS_ON,
3937
- provenance: Provenance8.EXTRACTED,
3938
- confidence: confidenceForExtracted7("structural"),
4258
+ type: EdgeType11.RUNS_ON,
4259
+ provenance: Provenance9.EXTRACTED,
4260
+ confidence: confidenceForExtracted8("structural"),
3939
4261
  evidence: {
3940
- file: toPosix2(path26.relative(scanPath, dockerfilePath))
4262
+ file: toPosix2(path28.relative(scanPath, dockerfilePath))
3941
4263
  }
3942
4264
  };
3943
4265
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -3948,21 +4270,21 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
3948
4270
  }
3949
4271
 
3950
4272
  // src/extract/infra/terraform.ts
3951
- import { promises as fs15 } from "fs";
3952
- import path27 from "path";
4273
+ import { promises as fs16 } from "fs";
4274
+ import path29 from "path";
3953
4275
  var RESOURCE_RE = /resource\s+"(aws_[A-Za-z0-9_]+)"\s+"([A-Za-z0-9_-]+)"/g;
3954
4276
  async function walkTfFiles(start, depth = 0, max = 5) {
3955
4277
  if (depth > max) return [];
3956
4278
  const out = [];
3957
- const entries = await fs15.readdir(start, { withFileTypes: true }).catch(() => []);
4279
+ const entries = await fs16.readdir(start, { withFileTypes: true }).catch(() => []);
3958
4280
  for (const entry of entries) {
3959
4281
  if (entry.isDirectory()) {
3960
4282
  if (IGNORED_DIRS.has(entry.name) || entry.name === ".terraform") continue;
3961
- const child = path27.join(start, entry.name);
4283
+ const child = path29.join(start, entry.name);
3962
4284
  if (await isPythonVenvDir(child)) continue;
3963
4285
  out.push(...await walkTfFiles(child, depth + 1, max));
3964
4286
  } else if (entry.isFile() && entry.name.endsWith(".tf")) {
3965
- out.push(path27.join(start, entry.name));
4287
+ out.push(path29.join(start, entry.name));
3966
4288
  }
3967
4289
  }
3968
4290
  return out;
@@ -3971,7 +4293,7 @@ async function addTerraformResources(graph, scanPath) {
3971
4293
  let nodesAdded = 0;
3972
4294
  const files = await walkTfFiles(scanPath);
3973
4295
  for (const file of files) {
3974
- const content = await fs15.readFile(file, "utf8");
4296
+ const content = await fs16.readFile(file, "utf8");
3975
4297
  RESOURCE_RE.lastIndex = 0;
3976
4298
  let m;
3977
4299
  while ((m = RESOURCE_RE.exec(content)) !== null) {
@@ -3988,8 +4310,8 @@ async function addTerraformResources(graph, scanPath) {
3988
4310
  }
3989
4311
 
3990
4312
  // src/extract/infra/k8s.ts
3991
- import { promises as fs16 } from "fs";
3992
- import path28 from "path";
4313
+ import { promises as fs17 } from "fs";
4314
+ import path30 from "path";
3993
4315
  import { parseAllDocuments as parseAllDocuments2 } from "yaml";
3994
4316
  var K8S_KIND_TO_INFRA_KIND = {
3995
4317
  Service: "k8s-service",
@@ -4003,15 +4325,15 @@ var K8S_KIND_TO_INFRA_KIND = {
4003
4325
  async function walkYamlFiles2(start, depth = 0, max = 5) {
4004
4326
  if (depth > max) return [];
4005
4327
  const out = [];
4006
- const entries = await fs16.readdir(start, { withFileTypes: true }).catch(() => []);
4328
+ const entries = await fs17.readdir(start, { withFileTypes: true }).catch(() => []);
4007
4329
  for (const entry of entries) {
4008
4330
  if (entry.isDirectory()) {
4009
4331
  if (IGNORED_DIRS.has(entry.name)) continue;
4010
- const child = path28.join(start, entry.name);
4332
+ const child = path30.join(start, entry.name);
4011
4333
  if (await isPythonVenvDir(child)) continue;
4012
4334
  out.push(...await walkYamlFiles2(child, depth + 1, max));
4013
- } else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(path28.extname(entry.name))) {
4014
- out.push(path28.join(start, entry.name));
4335
+ } else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(path30.extname(entry.name))) {
4336
+ out.push(path30.join(start, entry.name));
4015
4337
  }
4016
4338
  }
4017
4339
  return out;
@@ -4020,7 +4342,7 @@ async function addK8sResources(graph, scanPath) {
4020
4342
  let nodesAdded = 0;
4021
4343
  const files = await walkYamlFiles2(scanPath);
4022
4344
  for (const file of files) {
4023
- const content = await fs16.readFile(file, "utf8");
4345
+ const content = await fs17.readFile(file, "utf8");
4024
4346
  let docs;
4025
4347
  try {
4026
4348
  docs = parseAllDocuments2(content).map((d) => d.toJSON());
@@ -4055,12 +4377,12 @@ async function addInfra(graph, scanPath, services) {
4055
4377
  }
4056
4378
 
4057
4379
  // src/extract/index.ts
4058
- import path30 from "path";
4380
+ import path32 from "path";
4059
4381
 
4060
4382
  // src/extract/retire.ts
4061
4383
  import { existsSync as existsSync2 } from "fs";
4062
- import path29 from "path";
4063
- import { NodeType as NodeType11, Provenance as Provenance9 } from "@neat.is/types";
4384
+ import path31 from "path";
4385
+ import { NodeType as NodeType11, Provenance as Provenance10 } from "@neat.is/types";
4064
4386
  function dropOrphanedFileNodes(graph) {
4065
4387
  const orphans = [];
4066
4388
  graph.forEachNode((id, attrs) => {
@@ -4077,7 +4399,7 @@ function retireEdgesByFile(graph, file) {
4077
4399
  const toDrop = [];
4078
4400
  graph.forEachEdge((id, attrs) => {
4079
4401
  const edge = attrs;
4080
- if (edge.provenance !== Provenance9.EXTRACTED) return;
4402
+ if (edge.provenance !== Provenance10.EXTRACTED) return;
4081
4403
  if (!edge.evidence?.file) return;
4082
4404
  if (edge.evidence.file === normalized) toDrop.push(id);
4083
4405
  });
@@ -4090,14 +4412,14 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
4090
4412
  const bases = [scanPath, ...serviceDirs];
4091
4413
  graph.forEachEdge((id, attrs) => {
4092
4414
  const edge = attrs;
4093
- if (edge.provenance !== Provenance9.EXTRACTED) return;
4415
+ if (edge.provenance !== Provenance10.EXTRACTED) return;
4094
4416
  const evidenceFile = edge.evidence?.file;
4095
4417
  if (!evidenceFile) return;
4096
- if (path29.isAbsolute(evidenceFile)) {
4418
+ if (path31.isAbsolute(evidenceFile)) {
4097
4419
  if (!existsSync2(evidenceFile)) toDrop.push(id);
4098
4420
  return;
4099
4421
  }
4100
- const found = bases.some((base) => existsSync2(path29.join(base, evidenceFile)));
4422
+ const found = bases.some((base) => existsSync2(path31.join(base, evidenceFile)));
4101
4423
  if (!found) toDrop.push(id);
4102
4424
  });
4103
4425
  for (const id of toDrop) graph.dropEdge(id);
@@ -4112,6 +4434,8 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
4112
4434
  const services = await discoverServices(scanPath);
4113
4435
  const phase1Nodes = addServiceNodes(graph, services);
4114
4436
  await addServiceAliases(graph, scanPath, services);
4437
+ const fileEnum = await addFiles(graph, services);
4438
+ const importGraph = await addImports(graph, services);
4115
4439
  const phase2 = await addDatabasesAndCompat(graph, services, scanPath);
4116
4440
  const phase3 = await addConfigNodes(graph, services, scanPath);
4117
4441
  const phase4 = await addCallEdges(graph, services);
@@ -4135,7 +4459,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
4135
4459
  }
4136
4460
  const droppedEntries = drainDroppedExtracted();
4137
4461
  if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
4138
- const rejectedPath = path30.join(path30.dirname(opts.errorsPath), "rejected.ndjson");
4462
+ const rejectedPath = path32.join(path32.dirname(opts.errorsPath), "rejected.ndjson");
4139
4463
  try {
4140
4464
  await writeRejectedExtracted(droppedEntries, rejectedPath);
4141
4465
  } catch (err) {
@@ -4145,8 +4469,8 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
4145
4469
  }
4146
4470
  }
4147
4471
  const result = {
4148
- nodesAdded: phase1Nodes + phase2.nodesAdded + phase3.nodesAdded + phase4.nodesAdded + phase5.nodesAdded,
4149
- edgesAdded: phase2.edgesAdded + phase3.edgesAdded + phase4.edgesAdded + phase5.edgesAdded,
4472
+ nodesAdded: phase1Nodes + fileEnum.nodesAdded + importGraph.nodesAdded + phase2.nodesAdded + phase3.nodesAdded + phase4.nodesAdded + phase5.nodesAdded,
4473
+ edgesAdded: fileEnum.edgesAdded + importGraph.edgesAdded + phase2.edgesAdded + phase3.edgesAdded + phase4.edgesAdded + phase5.edgesAdded,
4150
4474
  frontiersPromoted,
4151
4475
  extractionErrors: errorEntries.length,
4152
4476
  errorEntries,
@@ -4170,10 +4494,10 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
4170
4494
  // src/divergences.ts
4171
4495
  import {
4172
4496
  DivergenceResultSchema,
4173
- EdgeType as EdgeType11,
4497
+ EdgeType as EdgeType12,
4174
4498
  NodeType as NodeType12,
4175
4499
  parseEdgeId,
4176
- Provenance as Provenance10
4500
+ Provenance as Provenance11
4177
4501
  } from "@neat.is/types";
4178
4502
  function bucketKey(source, target, type) {
4179
4503
  return `${type}|${source}|${target}`;
@@ -4187,17 +4511,17 @@ function bucketEdges(graph) {
4187
4511
  const key = bucketKey(e.source, e.target, e.type);
4188
4512
  const cur = buckets.get(key) ?? { source: e.source, target: e.target, type: e.type };
4189
4513
  switch (provenance) {
4190
- case Provenance10.EXTRACTED:
4514
+ case Provenance11.EXTRACTED:
4191
4515
  cur.extracted = e;
4192
4516
  break;
4193
- case Provenance10.OBSERVED:
4517
+ case Provenance11.OBSERVED:
4194
4518
  cur.observed = e;
4195
4519
  break;
4196
- case Provenance10.INFERRED:
4520
+ case Provenance11.INFERRED:
4197
4521
  cur.inferred = e;
4198
4522
  break;
4199
4523
  default:
4200
- if (e.provenance === Provenance10.STALE) cur.stale = e;
4524
+ if (e.provenance === Provenance11.STALE) cur.stale = e;
4201
4525
  }
4202
4526
  buckets.set(key, cur);
4203
4527
  });
@@ -4227,7 +4551,7 @@ function gradedConfidence(edge) {
4227
4551
  }
4228
4552
  function detectMissingDivergences(graph, bucket) {
4229
4553
  const out = [];
4230
- if (bucket.type === EdgeType11.CONTAINS) return out;
4554
+ if (bucket.type === EdgeType12.CONTAINS) return out;
4231
4555
  if (bucket.extracted && !bucket.observed) {
4232
4556
  if (!nodeIsFrontier(graph, bucket.target)) {
4233
4557
  out.push({
@@ -4268,7 +4592,7 @@ function declaredHostFor(svc) {
4268
4592
  function hasExtractedConfiguredBy(graph, svcId) {
4269
4593
  for (const edgeId of graph.outboundEdges(svcId)) {
4270
4594
  const e = graph.getEdgeAttributes(edgeId);
4271
- if (e.type === EdgeType11.CONFIGURED_BY && e.provenance === Provenance10.EXTRACTED) {
4595
+ if (e.type === EdgeType12.CONFIGURED_BY && e.provenance === Provenance11.EXTRACTED) {
4272
4596
  return true;
4273
4597
  }
4274
4598
  }
@@ -4281,8 +4605,8 @@ function detectHostMismatch(graph, svcId, svc) {
4281
4605
  const out = [];
4282
4606
  for (const edgeId of graph.outboundEdges(svcId)) {
4283
4607
  const edge = graph.getEdgeAttributes(edgeId);
4284
- if (edge.type !== EdgeType11.CONNECTS_TO) continue;
4285
- if (edge.provenance !== Provenance10.OBSERVED) continue;
4608
+ if (edge.type !== EdgeType12.CONNECTS_TO) continue;
4609
+ if (edge.provenance !== Provenance11.OBSERVED) continue;
4286
4610
  const target = graph.getNodeAttributes(edge.target);
4287
4611
  if (target.type !== NodeType12.DatabaseNode) continue;
4288
4612
  const observedHost = target.host?.trim();
@@ -4306,8 +4630,8 @@ function detectCompatDivergences(graph, svcId, svc) {
4306
4630
  const deps = svc.dependencies ?? {};
4307
4631
  for (const edgeId of graph.outboundEdges(svcId)) {
4308
4632
  const edge = graph.getEdgeAttributes(edgeId);
4309
- if (edge.type !== EdgeType11.CONNECTS_TO) continue;
4310
- if (edge.provenance !== Provenance10.OBSERVED) continue;
4633
+ if (edge.type !== EdgeType12.CONNECTS_TO) continue;
4634
+ if (edge.provenance !== Provenance11.OBSERVED) continue;
4311
4635
  const target = graph.getNodeAttributes(edge.target);
4312
4636
  if (target.type !== NodeType12.DatabaseNode) continue;
4313
4637
  for (const pair of compatPairs()) {
@@ -4411,9 +4735,9 @@ function computeDivergences(graph, opts = {}) {
4411
4735
  }
4412
4736
 
4413
4737
  // src/persist.ts
4414
- import { promises as fs17 } from "fs";
4415
- import path31 from "path";
4416
- import { Provenance as Provenance11, observedEdgeId as observedEdgeId2 } from "@neat.is/types";
4738
+ import { promises as fs18 } from "fs";
4739
+ import path33 from "path";
4740
+ import { Provenance as Provenance12, observedEdgeId as observedEdgeId2 } from "@neat.is/types";
4417
4741
  var SCHEMA_VERSION = 4;
4418
4742
  function migrateV1ToV2(payload) {
4419
4743
  const nodes = payload.graph.nodes;
@@ -4435,7 +4759,7 @@ function migrateV2ToV3(payload) {
4435
4759
  for (const edge of edges) {
4436
4760
  const attrs = edge.attributes;
4437
4761
  if (!attrs || attrs.provenance !== "FRONTIER") continue;
4438
- attrs.provenance = Provenance11.OBSERVED;
4762
+ attrs.provenance = Provenance12.OBSERVED;
4439
4763
  const type = typeof attrs.type === "string" ? attrs.type : void 0;
4440
4764
  const source = typeof attrs.source === "string" ? attrs.source : void 0;
4441
4765
  const target = typeof attrs.target === "string" ? attrs.target : void 0;
@@ -4449,7 +4773,7 @@ function migrateV2ToV3(payload) {
4449
4773
  return { ...payload, schemaVersion: 3 };
4450
4774
  }
4451
4775
  async function ensureDir(filePath) {
4452
- await fs17.mkdir(path31.dirname(filePath), { recursive: true });
4776
+ await fs18.mkdir(path33.dirname(filePath), { recursive: true });
4453
4777
  }
4454
4778
  async function saveGraphToDisk(graph, outPath) {
4455
4779
  await ensureDir(outPath);
@@ -4459,13 +4783,13 @@ async function saveGraphToDisk(graph, outPath) {
4459
4783
  graph: graph.export()
4460
4784
  };
4461
4785
  const tmp = `${outPath}.tmp`;
4462
- await fs17.writeFile(tmp, JSON.stringify(payload), "utf8");
4463
- await fs17.rename(tmp, outPath);
4786
+ await fs18.writeFile(tmp, JSON.stringify(payload), "utf8");
4787
+ await fs18.rename(tmp, outPath);
4464
4788
  }
4465
4789
  async function loadGraphFromDisk(graph, outPath) {
4466
4790
  let raw;
4467
4791
  try {
4468
- raw = await fs17.readFile(outPath, "utf8");
4792
+ raw = await fs18.readFile(outPath, "utf8");
4469
4793
  } catch (err) {
4470
4794
  if (err.code === "ENOENT") return;
4471
4795
  throw err;
@@ -4523,7 +4847,7 @@ function startPersistLoop(graph, outPath, intervalMs = 6e4) {
4523
4847
  }
4524
4848
 
4525
4849
  // src/diff.ts
4526
- import { promises as fs18 } from "fs";
4850
+ import { promises as fs19 } from "fs";
4527
4851
  async function loadSnapshotForDiff(target) {
4528
4852
  if (/^https?:\/\//i.test(target)) {
4529
4853
  const res = await fetch(target);
@@ -4532,7 +4856,7 @@ async function loadSnapshotForDiff(target) {
4532
4856
  }
4533
4857
  return await res.json();
4534
4858
  }
4535
- const raw = await fs18.readFile(target, "utf8");
4859
+ const raw = await fs19.readFile(target, "utf8");
4536
4860
  return JSON.parse(raw);
4537
4861
  }
4538
4862
  function indexEntries(entries) {
@@ -4599,23 +4923,23 @@ function canonicalJson(value) {
4599
4923
  }
4600
4924
 
4601
4925
  // src/projects.ts
4602
- import path32 from "path";
4926
+ import path34 from "path";
4603
4927
  function pathsForProject(project, baseDir) {
4604
4928
  if (project === DEFAULT_PROJECT) {
4605
4929
  return {
4606
- snapshotPath: path32.join(baseDir, "graph.json"),
4607
- errorsPath: path32.join(baseDir, "errors.ndjson"),
4608
- staleEventsPath: path32.join(baseDir, "stale-events.ndjson"),
4609
- embeddingsCachePath: path32.join(baseDir, "embeddings.json"),
4610
- policyViolationsPath: path32.join(baseDir, "policy-violations.ndjson")
4930
+ snapshotPath: path34.join(baseDir, "graph.json"),
4931
+ errorsPath: path34.join(baseDir, "errors.ndjson"),
4932
+ staleEventsPath: path34.join(baseDir, "stale-events.ndjson"),
4933
+ embeddingsCachePath: path34.join(baseDir, "embeddings.json"),
4934
+ policyViolationsPath: path34.join(baseDir, "policy-violations.ndjson")
4611
4935
  };
4612
4936
  }
4613
4937
  return {
4614
- snapshotPath: path32.join(baseDir, `${project}.json`),
4615
- errorsPath: path32.join(baseDir, `errors.${project}.ndjson`),
4616
- staleEventsPath: path32.join(baseDir, `stale-events.${project}.ndjson`),
4617
- embeddingsCachePath: path32.join(baseDir, `embeddings.${project}.json`),
4618
- policyViolationsPath: path32.join(baseDir, `policy-violations.${project}.ndjson`)
4938
+ snapshotPath: path34.join(baseDir, `${project}.json`),
4939
+ errorsPath: path34.join(baseDir, `errors.${project}.ndjson`),
4940
+ staleEventsPath: path34.join(baseDir, `stale-events.${project}.ndjson`),
4941
+ embeddingsCachePath: path34.join(baseDir, `embeddings.${project}.json`),
4942
+ policyViolationsPath: path34.join(baseDir, `policy-violations.${project}.ndjson`)
4619
4943
  };
4620
4944
  }
4621
4945
  var Projects = class {
@@ -4654,9 +4978,9 @@ function parseExtraProjects(raw) {
4654
4978
  }
4655
4979
 
4656
4980
  // src/registry.ts
4657
- import { promises as fs19 } from "fs";
4981
+ import { promises as fs20 } from "fs";
4658
4982
  import os2 from "os";
4659
- import path33 from "path";
4983
+ import path35 from "path";
4660
4984
  import {
4661
4985
  RegistryFileSchema
4662
4986
  } from "@neat.is/types";
@@ -4664,17 +4988,17 @@ var LOCK_TIMEOUT_MS = 5e3;
4664
4988
  var LOCK_RETRY_MS = 50;
4665
4989
  function neatHome() {
4666
4990
  const override = process.env.NEAT_HOME;
4667
- if (override && override.length > 0) return path33.resolve(override);
4668
- return path33.join(os2.homedir(), ".neat");
4991
+ if (override && override.length > 0) return path35.resolve(override);
4992
+ return path35.join(os2.homedir(), ".neat");
4669
4993
  }
4670
4994
  function registryPath() {
4671
- return path33.join(neatHome(), "projects.json");
4995
+ return path35.join(neatHome(), "projects.json");
4672
4996
  }
4673
4997
  function registryLockPath() {
4674
- return path33.join(neatHome(), "projects.json.lock");
4998
+ return path35.join(neatHome(), "projects.json.lock");
4675
4999
  }
4676
5000
  function daemonPidPath() {
4677
- return path33.join(neatHome(), "neatd.pid");
5001
+ return path35.join(neatHome(), "neatd.pid");
4678
5002
  }
4679
5003
  function isPidAliveDefault(pid) {
4680
5004
  try {
@@ -4686,7 +5010,7 @@ function isPidAliveDefault(pid) {
4686
5010
  }
4687
5011
  async function readPidFile(file) {
4688
5012
  try {
4689
- const raw = await fs19.readFile(file, "utf8");
5013
+ const raw = await fs20.readFile(file, "utf8");
4690
5014
  const pid = Number.parseInt(raw.trim(), 10);
4691
5015
  return Number.isInteger(pid) && pid > 0 ? pid : void 0;
4692
5016
  } catch {
@@ -4734,32 +5058,32 @@ function lockHolderMessage(holder, lockPath, timeoutMs) {
4734
5058
  }
4735
5059
  }
4736
5060
  async function normalizeProjectPath(input) {
4737
- const resolved = path33.resolve(input);
5061
+ const resolved = path35.resolve(input);
4738
5062
  try {
4739
- return await fs19.realpath(resolved);
5063
+ return await fs20.realpath(resolved);
4740
5064
  } catch {
4741
5065
  return resolved;
4742
5066
  }
4743
5067
  }
4744
5068
  async function writeAtomically(target, contents) {
4745
- await fs19.mkdir(path33.dirname(target), { recursive: true });
5069
+ await fs20.mkdir(path35.dirname(target), { recursive: true });
4746
5070
  const tmp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
4747
- const fd = await fs19.open(tmp, "w");
5071
+ const fd = await fs20.open(tmp, "w");
4748
5072
  try {
4749
5073
  await fd.writeFile(contents, "utf8");
4750
5074
  await fd.sync();
4751
5075
  } finally {
4752
5076
  await fd.close();
4753
5077
  }
4754
- await fs19.rename(tmp, target);
5078
+ await fs20.rename(tmp, target);
4755
5079
  }
4756
5080
  async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaultLockHolderProbe) {
4757
5081
  const deadline = Date.now() + timeoutMs;
4758
- await fs19.mkdir(path33.dirname(lockPath), { recursive: true });
5082
+ await fs20.mkdir(path35.dirname(lockPath), { recursive: true });
4759
5083
  let probedHolder = false;
4760
5084
  while (true) {
4761
5085
  try {
4762
- const fd = await fs19.open(lockPath, "wx");
5086
+ const fd = await fs20.open(lockPath, "wx");
4763
5087
  try {
4764
5088
  await fd.writeFile(`${process.pid}
4765
5089
  `, "utf8");
@@ -4784,7 +5108,7 @@ async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaul
4784
5108
  }
4785
5109
  }
4786
5110
  async function releaseLock(lockPath) {
4787
- await fs19.unlink(lockPath).catch(() => {
5111
+ await fs20.unlink(lockPath).catch(() => {
4788
5112
  });
4789
5113
  }
4790
5114
  async function withLock(fn) {
@@ -4800,7 +5124,7 @@ async function readRegistry() {
4800
5124
  const file = registryPath();
4801
5125
  let raw;
4802
5126
  try {
4803
- raw = await fs19.readFile(file, "utf8");
5127
+ raw = await fs20.readFile(file, "utf8");
4804
5128
  } catch (err) {
4805
5129
  if (err.code === "ENOENT") {
4806
5130
  return { version: 1, projects: [] };
@@ -4898,14 +5222,14 @@ import cors from "@fastify/cors";
4898
5222
  import { DivergenceTypeSchema, PoliciesCheckBodySchema, PolicySeveritySchema } from "@neat.is/types";
4899
5223
 
4900
5224
  // src/extend/index.ts
4901
- import { promises as fs21 } from "fs";
4902
- import path35 from "path";
5225
+ import { promises as fs22 } from "fs";
5226
+ import path37 from "path";
4903
5227
  import os3 from "os";
4904
5228
  import { resolve as registryResolve, list as registryList } from "@neat.is/instrumentation-registry";
4905
5229
 
4906
5230
  // src/installers/package-manager.ts
4907
- import { promises as fs20 } from "fs";
4908
- import path34 from "path";
5231
+ import { promises as fs21 } from "fs";
5232
+ import path36 from "path";
4909
5233
  import { spawn } from "child_process";
4910
5234
  var LOCKFILE_PRIORITY = [
4911
5235
  { lockfile: "bun.lockb", pm: "bun", args: ["install", "--no-summary"] },
@@ -4920,29 +5244,29 @@ var LOCKFILE_PRIORITY = [
4920
5244
  var NPM_FALLBACK_ARGS = ["install", "--no-audit", "--no-fund", "--prefer-offline"];
4921
5245
  async function exists2(p) {
4922
5246
  try {
4923
- await fs20.access(p);
5247
+ await fs21.access(p);
4924
5248
  return true;
4925
5249
  } catch {
4926
5250
  return false;
4927
5251
  }
4928
5252
  }
4929
5253
  async function detectPackageManager(serviceDir) {
4930
- let dir = path34.resolve(serviceDir);
5254
+ let dir = path36.resolve(serviceDir);
4931
5255
  const stops = /* @__PURE__ */ new Set();
4932
5256
  for (let i = 0; i < 64; i++) {
4933
5257
  if (stops.has(dir)) break;
4934
5258
  stops.add(dir);
4935
5259
  for (const candidate of LOCKFILE_PRIORITY) {
4936
- const lockPath = path34.join(dir, candidate.lockfile);
5260
+ const lockPath = path36.join(dir, candidate.lockfile);
4937
5261
  if (await exists2(lockPath)) {
4938
5262
  return { pm: candidate.pm, cwd: dir, args: [...candidate.args] };
4939
5263
  }
4940
5264
  }
4941
- const parent = path34.dirname(dir);
5265
+ const parent = path36.dirname(dir);
4942
5266
  if (parent === dir) break;
4943
5267
  dir = parent;
4944
5268
  }
4945
- return { pm: "npm", cwd: path34.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
5269
+ return { pm: "npm", cwd: path36.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
4946
5270
  }
4947
5271
  async function runPackageManagerInstall(cmd) {
4948
5272
  return new Promise((resolve) => {
@@ -4982,32 +5306,32 @@ ${err.message}`
4982
5306
  }
4983
5307
 
4984
5308
  // src/extend/index.ts
4985
- async function fileExists(p) {
5309
+ async function fileExists2(p) {
4986
5310
  try {
4987
- await fs21.access(p);
5311
+ await fs22.access(p);
4988
5312
  return true;
4989
5313
  } catch {
4990
5314
  return false;
4991
5315
  }
4992
5316
  }
4993
5317
  async function readPackageJson(scanPath) {
4994
- const pkgPath = path35.join(scanPath, "package.json");
4995
- const raw = await fs21.readFile(pkgPath, "utf8");
5318
+ const pkgPath = path37.join(scanPath, "package.json");
5319
+ const raw = await fs22.readFile(pkgPath, "utf8");
4996
5320
  return JSON.parse(raw);
4997
5321
  }
4998
5322
  async function findHookFiles(scanPath) {
4999
- const entries = await fs21.readdir(scanPath);
5323
+ const entries = await fs22.readdir(scanPath);
5000
5324
  return entries.filter(
5001
5325
  (e) => (e.startsWith("instrumentation") || e.startsWith("otel-init")) && /\.(ts|js)$/.test(e)
5002
5326
  ).sort();
5003
5327
  }
5004
5328
  function extendLogPath() {
5005
- return process.env.NEAT_EXTEND_LOG ?? path35.join(os3.homedir(), ".neat", "extend-log.ndjson");
5329
+ return process.env.NEAT_EXTEND_LOG ?? path37.join(os3.homedir(), ".neat", "extend-log.ndjson");
5006
5330
  }
5007
5331
  async function appendExtendLog(entry) {
5008
5332
  const logPath = extendLogPath();
5009
- await fs21.mkdir(path35.dirname(logPath), { recursive: true });
5010
- await fs21.appendFile(logPath, JSON.stringify(entry) + "\n", "utf8");
5333
+ await fs22.mkdir(path37.dirname(logPath), { recursive: true });
5334
+ await fs22.appendFile(logPath, JSON.stringify(entry) + "\n", "utf8");
5011
5335
  }
5012
5336
  function splicedContent(fileContent, snippet2) {
5013
5337
  if (fileContent.includes("__INSTRUMENTATION_BLOCK__")) {
@@ -5065,7 +5389,7 @@ function lookupInstrumentation(library, installedVersion) {
5065
5389
  }
5066
5390
  async function describeProjectInstrumentation(ctx) {
5067
5391
  const hookFiles = await findHookFiles(ctx.scanPath);
5068
- const envNeat = await fileExists(path35.join(ctx.scanPath, ".env.neat"));
5392
+ const envNeat = await fileExists2(path37.join(ctx.scanPath, ".env.neat"));
5069
5393
  const registryInstrPackages = new Set(
5070
5394
  registryList().map((e) => e.instrumentation_package).filter((p) => !!p)
5071
5395
  );
@@ -5087,31 +5411,31 @@ async function applyExtension(ctx, args, options) {
5087
5411
  );
5088
5412
  }
5089
5413
  for (const file of hookFiles) {
5090
- const content = await fs21.readFile(path35.join(ctx.scanPath, file), "utf8");
5414
+ const content = await fs22.readFile(path37.join(ctx.scanPath, file), "utf8");
5091
5415
  if (content.includes(args.registration_snippet)) {
5092
5416
  return { library: args.library, filesTouched: [], depsAdded: [], installOutput: "", alreadyApplied: true };
5093
5417
  }
5094
5418
  }
5095
5419
  const primaryFile = hookFiles[0];
5096
- const primaryPath = path35.join(ctx.scanPath, primaryFile);
5420
+ const primaryPath = path37.join(ctx.scanPath, primaryFile);
5097
5421
  const filesTouched = [];
5098
5422
  const depsAdded = [];
5099
- const pkgPath = path35.join(ctx.scanPath, "package.json");
5423
+ const pkgPath = path37.join(ctx.scanPath, "package.json");
5100
5424
  const pkg = await readPackageJson(ctx.scanPath);
5101
5425
  if (!(pkg.dependencies ?? {})[args.instrumentation_package]) {
5102
5426
  pkg.dependencies = { ...pkg.dependencies ?? {}, [args.instrumentation_package]: args.version };
5103
- await fs21.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
5427
+ await fs22.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
5104
5428
  filesTouched.push("package.json");
5105
5429
  depsAdded.push(`${args.instrumentation_package}@${args.version}`);
5106
5430
  }
5107
- const hookContent = await fs21.readFile(primaryPath, "utf8");
5431
+ const hookContent = await fs22.readFile(primaryPath, "utf8");
5108
5432
  const patched = splicedContent(hookContent, args.registration_snippet);
5109
5433
  if (!patched) {
5110
5434
  throw new Error(
5111
5435
  `Could not find instrumentation insertion point in ${primaryFile}. Expected __INSTRUMENTATION_BLOCK__, instrumentations.push(, or new NodeSDK(.`
5112
5436
  );
5113
5437
  }
5114
- await fs21.writeFile(primaryPath, patched, "utf8");
5438
+ await fs22.writeFile(primaryPath, patched, "utf8");
5115
5439
  filesTouched.push(primaryFile);
5116
5440
  const cmd = await detectPackageManager(ctx.scanPath);
5117
5441
  const installer = options?.runInstall ?? runPackageManagerInstall;
@@ -5142,7 +5466,7 @@ async function dryRunExtension(ctx, args) {
5142
5466
  };
5143
5467
  }
5144
5468
  for (const file of hookFiles) {
5145
- const content = await fs21.readFile(path35.join(ctx.scanPath, file), "utf8");
5469
+ const content = await fs22.readFile(path37.join(ctx.scanPath, file), "utf8");
5146
5470
  if (content.includes(args.registration_snippet)) {
5147
5471
  return {
5148
5472
  library: args.library,
@@ -5164,7 +5488,7 @@ async function dryRunExtension(ctx, args) {
5164
5488
  depsToAdd.push(`${args.instrumentation_package}@${args.version}`);
5165
5489
  filesTouched.push("package.json");
5166
5490
  }
5167
- const hookContent = await fs21.readFile(path35.join(ctx.scanPath, primaryFile), "utf8");
5491
+ const hookContent = await fs22.readFile(path37.join(ctx.scanPath, primaryFile), "utf8");
5168
5492
  const patched = splicedContent(hookContent, args.registration_snippet);
5169
5493
  if (patched) {
5170
5494
  filesTouched.push(primaryFile);
@@ -5176,31 +5500,31 @@ async function dryRunExtension(ctx, args) {
5176
5500
  }
5177
5501
  async function rollbackExtension(ctx, args) {
5178
5502
  const logPath = extendLogPath();
5179
- if (!await fileExists(logPath)) {
5503
+ if (!await fileExists2(logPath)) {
5180
5504
  return { undone: false, message: "no apply found for library" };
5181
5505
  }
5182
- const raw = await fs21.readFile(logPath, "utf8");
5506
+ const raw = await fs22.readFile(logPath, "utf8");
5183
5507
  const entries = raw.trim().split("\n").filter(Boolean).map((line) => JSON.parse(line));
5184
5508
  const match = [...entries].reverse().find((e) => e.project === ctx.project && e.library === args.library);
5185
5509
  if (!match) {
5186
5510
  return { undone: false, message: "no apply found for library" };
5187
5511
  }
5188
- const pkgPath = path35.join(ctx.scanPath, "package.json");
5189
- if (await fileExists(pkgPath)) {
5512
+ const pkgPath = path37.join(ctx.scanPath, "package.json");
5513
+ if (await fileExists2(pkgPath)) {
5190
5514
  const pkg = await readPackageJson(ctx.scanPath);
5191
5515
  if (pkg.dependencies?.[match.instrumentation_package]) {
5192
5516
  const { [match.instrumentation_package]: _removed, ...rest } = pkg.dependencies;
5193
5517
  pkg.dependencies = rest;
5194
- await fs21.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
5518
+ await fs22.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
5195
5519
  }
5196
5520
  }
5197
5521
  const hookFiles = await findHookFiles(ctx.scanPath);
5198
5522
  for (const file of hookFiles) {
5199
- const filePath = path35.join(ctx.scanPath, file);
5200
- const content = await fs21.readFile(filePath, "utf8");
5523
+ const filePath = path37.join(ctx.scanPath, file);
5524
+ const content = await fs22.readFile(filePath, "utf8");
5201
5525
  if (content.includes(match.registration_snippet)) {
5202
5526
  const filtered = content.split("\n").filter((line) => !line.includes(match.registration_snippet)).join("\n");
5203
- await fs21.writeFile(filePath, filtered, "utf8");
5527
+ await fs22.writeFile(filePath, filtered, "utf8");
5204
5528
  break;
5205
5529
  }
5206
5530
  }
@@ -5900,6 +6224,7 @@ export {
5900
6224
  discoverServices,
5901
6225
  addServiceNodes,
5902
6226
  addServiceAliases,
6227
+ addImports,
5903
6228
  addDatabasesAndCompat,
5904
6229
  addConfigNodes,
5905
6230
  addCallEdges,
@@ -5931,4 +6256,4 @@ export {
5931
6256
  removeProject,
5932
6257
  buildApi
5933
6258
  };
5934
- //# sourceMappingURL=chunk-WDG4QEFO.js.map
6259
+ //# sourceMappingURL=chunk-5W7H35JJ.js.map