@neat.is/core 0.9.1-dev.20260819 → 0.9.1

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.
@@ -947,6 +947,7 @@ import Python2 from "tree-sitter-python";
947
947
  import Go2 from "tree-sitter-go";
948
948
  import Ruby from "tree-sitter-ruby";
949
949
  import Php from "tree-sitter-php";
950
+ import Rust from "tree-sitter-rust";
950
951
  import {
951
952
  EdgeType as EdgeType4,
952
953
  NodeType as NodeType3,
@@ -1250,7 +1251,7 @@ function buildServiceHostIndex(services) {
1250
1251
  async function walkSourceFiles(dir, excludeDirs = []) {
1251
1252
  const excluded = new Set(excludeDirs.map((d) => path5.resolve(d)));
1252
1253
  const out = [];
1253
- async function walk9(current) {
1254
+ async function walk10(current) {
1254
1255
  const entries = await fs5.readdir(current, { withFileTypes: true }).catch(() => []);
1255
1256
  for (const entry of entries) {
1256
1257
  const full = path5.join(current, entry.name);
@@ -1258,7 +1259,7 @@ async function walkSourceFiles(dir, excludeDirs = []) {
1258
1259
  if (IGNORED_DIRS.has(entry.name)) continue;
1259
1260
  if (excluded.has(path5.resolve(full))) continue;
1260
1261
  if (await isPythonVenvDir(full)) continue;
1261
- await walk9(full);
1262
+ await walk10(full);
1262
1263
  } else if (entry.isFile() && SERVICE_FILE_EXTENSIONS.has(path5.extname(entry.name)) && // Skip NEAT's own generated `otel-init.*` bootstrap — extracting it
1263
1264
  // would attribute our instrumentation imports to the user's service.
1264
1265
  !isNeatAuthoredSourceFile(entry.name)) {
@@ -1266,7 +1267,7 @@ async function walkSourceFiles(dir, excludeDirs = []) {
1266
1267
  }
1267
1268
  }
1268
1269
  }
1269
- await walk9(dir);
1270
+ await walk10(dir);
1270
1271
  return out;
1271
1272
  }
1272
1273
  async function loadSourceFiles(dir, excludeDirs = []) {
@@ -1791,6 +1792,11 @@ function makePhpParser() {
1791
1792
  p.setLanguage(Php.php_only);
1792
1793
  return p;
1793
1794
  }
1795
+ function makeRustParser() {
1796
+ const p = new Parser2();
1797
+ p.setLanguage(Rust);
1798
+ return p;
1799
+ }
1794
1800
  var ROUTER_METHODS = /* @__PURE__ */ new Set([
1795
1801
  "get",
1796
1802
  "post",
@@ -1862,8 +1868,8 @@ function chiRoutesFromSource(source, parser) {
1862
1868
  chiWalk(tree.rootNode, "", out);
1863
1869
  return out;
1864
1870
  }
1865
- function stripChiRegex(path70) {
1866
- return path70.replace(/\{([^{}:]+):[^{}]*\}/g, "{$1}");
1871
+ function stripChiRegex(path72) {
1872
+ return path72.replace(/\{([^{}:]+):[^{}]*\}/g, "{$1}");
1867
1873
  }
1868
1874
  function chiWalk(node, prefix, out) {
1869
1875
  for (let i = 0; i < node.namedChildCount; i++) {
@@ -2535,9 +2541,9 @@ function rubyRocketRoute(args) {
2535
2541
  if (!pair || pair.type !== "pair") continue;
2536
2542
  const k = pair.childForFieldName("key");
2537
2543
  if (k?.type !== "string") continue;
2538
- const path70 = rubyLiteral(k);
2539
- if (path70 === null) continue;
2540
- return { path: path70, target: rubyLiteral(pair.childForFieldName("value")) };
2544
+ const path72 = rubyLiteral(k);
2545
+ if (path72 === null) continue;
2546
+ return { path: path72, target: rubyLiteral(pair.childForFieldName("value")) };
2541
2547
  }
2542
2548
  return null;
2543
2549
  }
@@ -2707,6 +2713,48 @@ function railsRoutesFromSource(source, parser) {
2707
2713
  });
2708
2714
  return out;
2709
2715
  }
2716
+ var SINATRA_VERBS = /* @__PURE__ */ new Set(["get", "post", "put", "patch", "delete", "options", "head"]);
2717
+ function sinatraRoutesFromSource(source, parser) {
2718
+ const tree = parseSource2(parser, source);
2719
+ if (!fileReferencesSinatra(tree.rootNode)) return [];
2720
+ const out = [];
2721
+ walk(tree.rootNode, (node) => {
2722
+ if (node.type !== "call") return;
2723
+ if (node.childForFieldName("receiver")) return;
2724
+ const method = node.childForFieldName("method")?.text;
2725
+ if (!method || !SINATRA_VERBS.has(method)) return;
2726
+ if (!node.childForFieldName("block")) return;
2727
+ const first = node.childForFieldName("arguments")?.namedChild(0);
2728
+ if (first?.type !== "string") return;
2729
+ const p = rubyLiteral(first);
2730
+ if (p === null || !p.startsWith("/")) return;
2731
+ out.push({
2732
+ method: method.toUpperCase(),
2733
+ pathTemplate: canonicalizeTemplate(p),
2734
+ line: node.startPosition.row + 1,
2735
+ framework: "sinatra"
2736
+ });
2737
+ });
2738
+ return out;
2739
+ }
2740
+ function fileReferencesSinatra(root) {
2741
+ let found = false;
2742
+ walk(root, (node) => {
2743
+ if (found) return;
2744
+ if (node.type === "call") {
2745
+ const m = node.childForFieldName("method")?.text;
2746
+ if (m === "require" || m === "require_relative") {
2747
+ const s = rubyLiteral(node.childForFieldName("arguments")?.namedChild(0));
2748
+ if (s !== null && /^sinatra\b/.test(s)) found = true;
2749
+ }
2750
+ return;
2751
+ }
2752
+ if (node.type === "constant" && node.text === "Sinatra" || node.type === "scope_resolution" && node.text.startsWith("Sinatra")) {
2753
+ found = true;
2754
+ }
2755
+ });
2756
+ return found;
2757
+ }
2710
2758
  var LARAVEL_VERBS = /* @__PURE__ */ new Set(["get", "post", "put", "patch", "delete", "options"]);
2711
2759
  var LARAVEL_RESOURCE_ROWS = [
2712
2760
  { action: "index", methods: ["GET"], suffix: "" },
@@ -2882,6 +2930,223 @@ function laravelRoutesFromSource(source, parser, basePrefix = "") {
2882
2930
  }
2883
2931
  return out;
2884
2932
  }
2933
+ var SLIM_VERBS = /* @__PURE__ */ new Set(["get", "post", "put", "patch", "delete", "options", "head"]);
2934
+ function isSlimAppCtor(node) {
2935
+ if (!node) return false;
2936
+ if (node.type === "scoped_call_expression") {
2937
+ const scope = node.childForFieldName("scope")?.text ?? "";
2938
+ const name = node.childForFieldName("name")?.text;
2939
+ return name === "create" && (scope === "AppFactory" || scope.endsWith("\\AppFactory"));
2940
+ }
2941
+ if (node.type === "object_creation_expression") {
2942
+ const cls = node.namedChild(0)?.text ?? "";
2943
+ return cls === "App" || cls.endsWith("\\App") || cls.includes("Slim");
2944
+ }
2945
+ return false;
2946
+ }
2947
+ function isSlimAppType(node) {
2948
+ if (!node || node.type !== "named_type") return false;
2949
+ const text = node.text;
2950
+ return text === "App" || text.endsWith("\\App");
2951
+ }
2952
+ function collectSlimAppVars(root) {
2953
+ const vars = /* @__PURE__ */ new Set();
2954
+ walk(root, (node) => {
2955
+ if (node.type === "assignment_expression") {
2956
+ const left = node.childForFieldName("left");
2957
+ if (left?.type === "variable_name" && isSlimAppCtor(node.childForFieldName("right"))) {
2958
+ vars.add(left.text);
2959
+ }
2960
+ return;
2961
+ }
2962
+ if (node.type === "simple_parameter" && isSlimAppType(node.childForFieldName("type"))) {
2963
+ const name = node.childForFieldName("name");
2964
+ if (name?.type === "variable_name") vars.add(name.text);
2965
+ }
2966
+ });
2967
+ return vars;
2968
+ }
2969
+ function slimClosureParamVars(closure) {
2970
+ const out = ["$this"];
2971
+ for (let i = 0; i < closure.namedChildCount; i++) {
2972
+ const params = closure.namedChild(i);
2973
+ if (params?.type !== "formal_parameters") continue;
2974
+ for (let j = 0; j < params.namedChildCount; j++) {
2975
+ const param = params.namedChild(j);
2976
+ if (param?.type !== "simple_parameter") continue;
2977
+ for (let k = 0; k < param.namedChildCount; k++) {
2978
+ const v = param.namedChild(k);
2979
+ if (v?.type === "variable_name") {
2980
+ out.push(v.text);
2981
+ break;
2982
+ }
2983
+ }
2984
+ }
2985
+ }
2986
+ return out;
2987
+ }
2988
+ function phpStringArray(node) {
2989
+ const out = [];
2990
+ if (node?.type !== "array_creation_expression") return out;
2991
+ for (let i = 0; i < node.namedChildCount; i++) {
2992
+ const el = node.namedChild(i);
2993
+ if (el?.type !== "array_element_initializer") continue;
2994
+ const s = phpStaticString(el.namedChild(0));
2995
+ if (s !== null) out.push(s);
2996
+ }
2997
+ return out;
2998
+ }
2999
+ function slimRoutesFromSource(source, parser) {
3000
+ const tree = parseSource2(parser, source);
3001
+ const appVars = collectSlimAppVars(tree.rootNode);
3002
+ if (appVars.size === 0) return [];
3003
+ const out = [];
3004
+ slimWalk(tree.rootNode, "", appVars, out);
3005
+ return out;
3006
+ }
3007
+ function slimWalk(node, prefix, appVars, out) {
3008
+ for (let i = 0; i < node.namedChildCount; i++) {
3009
+ const child = node.namedChild(i);
3010
+ if (child) slimHandle(child, prefix, appVars, out);
3011
+ }
3012
+ }
3013
+ function slimHandle(node, prefix, appVars, out) {
3014
+ if (node.type === "member_call_expression") {
3015
+ const obj = node.childForFieldName("object");
3016
+ const method = node.childForFieldName("name")?.text;
3017
+ const args = node.childForFieldName("arguments");
3018
+ if (obj?.type === "variable_name" && method && appVars.has(obj.text)) {
3019
+ const line = node.startPosition.row + 1;
3020
+ if (method === "group") {
3021
+ const groupPrefix = phpFirstString(args);
3022
+ const closure = laravelGroupClosure(args);
3023
+ if (groupPrefix !== null && closure) {
3024
+ const inner = new Set(appVars);
3025
+ for (const v of slimClosureParamVars(closure)) inner.add(v);
3026
+ const body = closure.childForFieldName("body");
3027
+ if (body) slimWalk(body, laravelJoinPath(prefix, groupPrefix), inner, out);
3028
+ }
3029
+ return;
3030
+ }
3031
+ if (SLIM_VERBS.has(method)) {
3032
+ const p = phpFirstString(args);
3033
+ if (p !== null) {
3034
+ out.push({
3035
+ method: method.toUpperCase(),
3036
+ pathTemplate: laravelJoinPath(prefix, p),
3037
+ line,
3038
+ framework: "slim"
3039
+ });
3040
+ }
3041
+ return;
3042
+ }
3043
+ if (method === "any") {
3044
+ const p = phpFirstString(args);
3045
+ if (p !== null) {
3046
+ out.push({ method: "ALL", pathTemplate: laravelJoinPath(prefix, p), line, framework: "slim" });
3047
+ }
3048
+ return;
3049
+ }
3050
+ if (method === "map") {
3051
+ const vals = phpArgumentValues(args);
3052
+ const methods = phpStringArray(vals[0]);
3053
+ const p = vals.length > 1 ? phpStaticString(vals[1]) : null;
3054
+ if (p !== null) {
3055
+ for (const m of methods) {
3056
+ out.push({
3057
+ method: m.toUpperCase(),
3058
+ pathTemplate: laravelJoinPath(prefix, p),
3059
+ line,
3060
+ framework: "slim"
3061
+ });
3062
+ }
3063
+ }
3064
+ return;
3065
+ }
3066
+ }
3067
+ }
3068
+ slimWalk(node, prefix, appVars, out);
3069
+ }
3070
+ var ACTIX_METHODS = /* @__PURE__ */ new Set(["get", "post", "put", "patch", "delete", "head", "options", "trace"]);
3071
+ function rustStringContent(node) {
3072
+ if (!node || node.type !== "string_literal") return null;
3073
+ for (let i = 0; i < node.namedChildCount; i++) {
3074
+ if (node.namedChild(i)?.type === "string_content") return node.namedChild(i).text;
3075
+ }
3076
+ return "";
3077
+ }
3078
+ function actixRoutesFromSource(source, parser) {
3079
+ const tree = parseSource2(parser, source);
3080
+ const out = [];
3081
+ walk(tree.rootNode, (node) => {
3082
+ if (node.type === "attribute_item") {
3083
+ actixAttributeRoute(node, out);
3084
+ return;
3085
+ }
3086
+ if (node.type === "call_expression") {
3087
+ actixBuilderRoute(node, out);
3088
+ }
3089
+ });
3090
+ return out;
3091
+ }
3092
+ function actixAttributeRoute(attrItem, out) {
3093
+ const attr = attrItem.namedChild(0);
3094
+ if (!attr || attr.type !== "attribute") return;
3095
+ const nameNode = attr.namedChild(0);
3096
+ if (!nameNode) return;
3097
+ const macro = nameNode.type === "identifier" ? nameNode.text : nameNode.type === "scoped_identifier" ? nameNode.childForFieldName("name")?.text ?? null : null;
3098
+ if (!macro) return;
3099
+ const tokens = attr.childForFieldName("arguments");
3100
+ if (!tokens || tokens.type !== "token_tree") return;
3101
+ const strings = [];
3102
+ for (let i = 0; i < tokens.namedChildCount; i++) {
3103
+ const s = rustStringContent(tokens.namedChild(i));
3104
+ if (s !== null) strings.push(s);
3105
+ }
3106
+ const pathStr = strings[0];
3107
+ if (pathStr === void 0 || !pathStr.startsWith("/")) return;
3108
+ const line = attrItem.startPosition.row + 1;
3109
+ const template = canonicalizeTemplate(pathStr);
3110
+ if (ACTIX_METHODS.has(macro)) {
3111
+ out.push({ method: macro.toUpperCase(), pathTemplate: template, line, framework: "actix-web" });
3112
+ return;
3113
+ }
3114
+ if (macro === "route") {
3115
+ const methods = strings.slice(1).filter((m) => ACTIX_METHODS.has(m.toLowerCase()));
3116
+ const list = methods.length > 0 ? methods.map((m) => m.toUpperCase()) : ["ALL"];
3117
+ for (const m of list) {
3118
+ out.push({ method: m, pathTemplate: template, line, framework: "actix-web" });
3119
+ }
3120
+ }
3121
+ }
3122
+ function actixBuilderRoute(call, out) {
3123
+ const fn = call.childForFieldName("function");
3124
+ if (fn?.type !== "field_expression") return;
3125
+ if (fn.childForFieldName("field")?.text !== "route") return;
3126
+ const args = call.childForFieldName("arguments");
3127
+ const pathStr = rustStringContent(args?.namedChild(0));
3128
+ if (pathStr === null || !pathStr.startsWith("/")) return;
3129
+ const second = args?.namedChild(1);
3130
+ if (!second) return;
3131
+ const method = actixBuilderMethod(second);
3132
+ if (!method) return;
3133
+ out.push({
3134
+ method,
3135
+ pathTemplate: canonicalizeTemplate(pathStr),
3136
+ line: call.startPosition.row + 1,
3137
+ framework: "actix-web"
3138
+ });
3139
+ }
3140
+ function actixBuilderMethod(node) {
3141
+ let method = null;
3142
+ walk(node, (n) => {
3143
+ if (method || n.type !== "scoped_identifier") return;
3144
+ const verb = n.childForFieldName("name")?.text;
3145
+ const scopeLeaf = n.childForFieldName("path")?.text?.split("::").pop();
3146
+ if (scopeLeaf === "web" && verb && ACTIX_METHODS.has(verb)) method = verb.toUpperCase();
3147
+ });
3148
+ return method;
3149
+ }
2885
3150
  function namedArgs(argsNode) {
2886
3151
  const out = [];
2887
3152
  if (!argsNode) return out;
@@ -3198,6 +3463,7 @@ async function addRoutes(graph, services) {
3198
3463
  const goParser = makeGoParser2();
3199
3464
  const rubyParser = makeRubyParser();
3200
3465
  const phpParser = makePhpParser();
3466
+ const rustParser = makeRustParser();
3201
3467
  let nodesAdded = 0;
3202
3468
  let edgesAdded = 0;
3203
3469
  for (const service of services) {
@@ -3220,7 +3486,10 @@ async function addRoutes(graph, services) {
3220
3486
  const isGoService = service.node.language === "go";
3221
3487
  const hasRails = deps["rails"] !== void 0;
3222
3488
  const hasLaravel = deps["laravel/framework"] !== void 0;
3223
- if (!hasExpress && !hasFastify && !hasHono && !hasNext && !hasNestjs && !hasFastapi && !hasFlask && !hasDjango && !hasGin && !hasEcho && !hasFiber && !hasChi && !isGoService && !hasRails && !hasLaravel)
3489
+ const hasSlim = deps["slim/slim"] !== void 0;
3490
+ const hasSinatra = deps["sinatra"] !== void 0;
3491
+ const hasActix = deps["actix-web"] !== void 0;
3492
+ if (!hasExpress && !hasFastify && !hasHono && !hasNext && !hasNestjs && !hasFastapi && !hasFlask && !hasDjango && !hasGin && !hasEcho && !hasFiber && !hasChi && !isGoService && !hasRails && !hasLaravel && !hasSlim && !hasSinatra && !hasActix)
3224
3493
  continue;
3225
3494
  const files = await loadSourceFiles(service.dir, service.excludeDirs);
3226
3495
  const mountPrefixes = hasExpress ? await expressMountPrefixes(files, service.dir, await loadTsPathConfig(service.dir)) : /* @__PURE__ */ new Map();
@@ -3231,7 +3500,8 @@ async function addRoutes(graph, services) {
3231
3500
  const isGo = ext === ".go";
3232
3501
  const isRb = ext === ".rb";
3233
3502
  const isPhp = ext === ".php";
3234
- if (!JS_ROUTE_EXTENSIONS.has(ext) && !isPy && !isGo && !isRb && !isPhp) continue;
3503
+ const isRs = ext === ".rs";
3504
+ if (!JS_ROUTE_EXTENSIONS.has(ext) && !isPy && !isGo && !isRb && !isPhp && !isRs) continue;
3235
3505
  const relFile = toPosix(path7.relative(service.dir, file.path));
3236
3506
  let routes;
3237
3507
  try {
@@ -3241,8 +3511,12 @@ async function addRoutes(graph, services) {
3241
3511
  phpParser,
3242
3512
  relFile === "routes/api.php" ? "/api" : ""
3243
3513
  ) : [];
3514
+ if (hasSlim) routes = routes.concat(slimRoutesFromSource(file.content, phpParser));
3244
3515
  } else if (isRb) {
3245
3516
  routes = hasRails && relFile === "config/routes.rb" ? railsRoutesFromSource(file.content, rubyParser) : [];
3517
+ if (hasSinatra) routes = routes.concat(sinatraRoutesFromSource(file.content, rubyParser));
3518
+ } else if (isRs) {
3519
+ routes = hasActix ? actixRoutesFromSource(file.content, rustParser) : [];
3246
3520
  } else if (isGo) {
3247
3521
  if (hasGin) routes = ginRoutesFromSource(file.content, goParser);
3248
3522
  else if (hasEcho) routes = echoRoutesFromSource(file.content, goParser);
@@ -3475,6 +3749,37 @@ function loadIncidentThresholdsFromEnv() {
3475
3749
  return DEFAULT_INCIDENT_THRESHOLDS;
3476
3750
  }
3477
3751
  }
3752
+ var DEFAULT_LATENCY_STREAM_CEILING_MS = 6e4;
3753
+ function latencyStreamCeilingMs() {
3754
+ const raw = process.env.NEAT_LATENCY_STREAM_CEILING_MS;
3755
+ if (!raw) return DEFAULT_LATENCY_STREAM_CEILING_MS;
3756
+ const n = Number(raw);
3757
+ if (Number.isFinite(n) && n > 0) return n;
3758
+ console.warn(
3759
+ `[neat] NEAT_LATENCY_STREAM_CEILING_MS could not be parsed (${raw}); using default`
3760
+ );
3761
+ return DEFAULT_LATENCY_STREAM_CEILING_MS;
3762
+ }
3763
+ function spanServesEventStream(attrs) {
3764
+ for (const key of [
3765
+ "http.response.header.content-type",
3766
+ "http.response.header.content_type"
3767
+ ]) {
3768
+ const v = attrs[key];
3769
+ const values = Array.isArray(v) ? v : v !== void 0 && v !== null ? [v] : [];
3770
+ for (const item of values) {
3771
+ if (typeof item === "string" && item.toLowerCase().includes("text/event-stream")) {
3772
+ return true;
3773
+ }
3774
+ }
3775
+ }
3776
+ return false;
3777
+ }
3778
+ function spanIsStreaming(span, ceilingMs = latencyStreamCeilingMs()) {
3779
+ if (span.websocketChannel !== void 0) return true;
3780
+ if (spanServesEventStream(span.attributes)) return true;
3781
+ return span.durationNanos > BigInt(Math.round(ceilingMs)) * 1000000n;
3782
+ }
3478
3783
  function httpResponseStatusFromAttrs(attrs) {
3479
3784
  for (const key of ["http.response.status_code", "http.status_code"]) {
3480
3785
  const v = attrs[key];
@@ -4367,6 +4672,12 @@ async function advance4xxBurst(ctx, span, affectedNode, ts, nowMs, status) {
4367
4672
  );
4368
4673
  ctx.burstState.delete(key);
4369
4674
  }
4675
+ var NEXT_API_ROUTE_SPAN_NAME = /^executing api route \((?:pages|app)\) (\/\S*)$/;
4676
+ function nextApiRouteTemplate(span) {
4677
+ const raw = pickAttr(span, "next.span_name") ?? span.name;
4678
+ const match = raw ? NEXT_API_ROUTE_SPAN_NAME.exec(raw) : null;
4679
+ return match ? match[1] : void 0;
4680
+ }
4370
4681
  function findRouteNodeByHttpRoute(graph, serviceName, method, httpRoute) {
4371
4682
  const target = normalizePathTemplate(httpRoute);
4372
4683
  const m = method?.toUpperCase();
@@ -4389,7 +4700,7 @@ async function handleSpan(ctx, span) {
4389
4700
  }
4390
4701
  const sourceId = ensureServiceNode(ctx.graph, span.service, env);
4391
4702
  const isError = span.statusCode === 2;
4392
- const durationMs = span.durationNanos > 0n ? Number(span.durationNanos) / 1e6 : void 0;
4703
+ const durationMs = span.durationNanos > 0n && !spanIsStreaming(span) ? Number(span.durationNanos) / 1e6 : void 0;
4393
4704
  const sourceServiceNode = ctx.graph.getNodeAttributes(sourceId);
4394
4705
  const callSite = callSiteFromSpan(span, sourceServiceNode, ctx.scanPath);
4395
4706
  cacheSpanService(span, nowMs, callSite);
@@ -4586,12 +4897,13 @@ async function handleSpan(ctx, span) {
4586
4897
  }
4587
4898
  }
4588
4899
  }
4589
- if (span.httpRoute && (span.kind === 2 || span.kind === 1 || span.kind === 0 || span.kind === void 0)) {
4900
+ const fusionRoute = nextApiRouteTemplate(span) ?? span.httpRoute;
4901
+ if (fusionRoute && (span.kind === 2 || span.kind === 1 || span.kind === 0 || span.kind === void 0)) {
4590
4902
  const routeNodeId = findRouteNodeByHttpRoute(
4591
4903
  ctx.graph,
4592
4904
  span.service,
4593
4905
  span.httpMethod,
4594
- span.httpRoute
4906
+ fusionRoute
4595
4907
  );
4596
4908
  if (routeNodeId) {
4597
4909
  const routeSvc = ctx.graph.getNodeAttributes(routeNodeId).service;
@@ -5008,19 +5320,19 @@ function confidenceFromMix(edges, now = Date.now()) {
5008
5320
  function longestIncomingWalk(graph, start, maxDepth) {
5009
5321
  let best = { path: [start], edges: [] };
5010
5322
  const visited = /* @__PURE__ */ new Set([start]);
5011
- function step(node, path70, edges) {
5012
- if (path70.length > best.path.length) {
5013
- best = { path: [...path70], edges: [...edges] };
5323
+ function step(node, path72, edges) {
5324
+ if (path72.length > best.path.length) {
5325
+ best = { path: [...path72], edges: [...edges] };
5014
5326
  }
5015
- if (path70.length - 1 >= maxDepth) return;
5327
+ if (path72.length - 1 >= maxDepth) return;
5016
5328
  const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
5017
5329
  for (const [srcId, edge] of incoming) {
5018
5330
  if (visited.has(srcId)) continue;
5019
5331
  visited.add(srcId);
5020
- path70.push(srcId);
5332
+ path72.push(srcId);
5021
5333
  edges.push(edge);
5022
- step(srcId, path70, edges);
5023
- path70.pop();
5334
+ step(srcId, path72, edges);
5335
+ path72.pop();
5024
5336
  edges.pop();
5025
5337
  visited.delete(srcId);
5026
5338
  }
@@ -5028,11 +5340,11 @@ function longestIncomingWalk(graph, start, maxDepth) {
5028
5340
  step(start, [start], []);
5029
5341
  return best;
5030
5342
  }
5031
- function databaseRootCauseShape(graph, origin, walk9) {
5343
+ function databaseRootCauseShape(graph, origin, walk10) {
5032
5344
  const targetDb = origin;
5033
5345
  const candidatePairs = compatPairs().filter((p) => p.engine === targetDb.engine);
5034
5346
  if (candidatePairs.length === 0) return null;
5035
- for (const id of walk9.path) {
5347
+ for (const id of walk10.path) {
5036
5348
  const owner = resolveOwningService(graph, id);
5037
5349
  if (!owner) continue;
5038
5350
  const { id: serviceId15, svc } = owner;
@@ -5059,8 +5371,8 @@ function databaseRootCauseShape(graph, origin, walk9) {
5059
5371
  }
5060
5372
  return null;
5061
5373
  }
5062
- function serviceRootCauseShape(graph, _origin, walk9) {
5063
- for (const id of walk9.path) {
5374
+ function serviceRootCauseShape(graph, _origin, walk10) {
5375
+ for (const id of walk10.path) {
5064
5376
  const owner = resolveOwningService(graph, id);
5065
5377
  if (!owner) continue;
5066
5378
  const { id: serviceId15, svc } = owner;
@@ -5096,15 +5408,15 @@ function serviceRootCauseShape(graph, _origin, walk9) {
5096
5408
  }
5097
5409
  return null;
5098
5410
  }
5099
- function fileRootCauseShape(graph, origin, walk9) {
5411
+ function fileRootCauseShape(graph, origin, walk10) {
5100
5412
  const owner = resolveOwningService(graph, origin.id);
5101
5413
  if (!owner) return null;
5102
- return serviceRootCauseShape(graph, owner.svc, walk9);
5414
+ return serviceRootCauseShape(graph, owner.svc, walk10);
5103
5415
  }
5104
- function symbolRootCauseShape(graph, origin, walk9) {
5416
+ function symbolRootCauseShape(graph, origin, walk10) {
5105
5417
  const owner = resolveOwningService(graph, origin.id);
5106
5418
  if (!owner) return null;
5107
- return serviceRootCauseShape(graph, owner.svc, walk9);
5419
+ return serviceRootCauseShape(graph, owner.svc, walk10);
5108
5420
  }
5109
5421
  var rootCauseShapes = {
5110
5422
  [NodeType5.DatabaseNode]: databaseRootCauseShape,
@@ -5117,25 +5429,29 @@ function legacyRootCause(graph, errorNodeId, errorEvent, incidents) {
5117
5429
  const origin = graph.getNodeAttributes(errorNodeId);
5118
5430
  const shape = rootCauseShapes[origin.type];
5119
5431
  if (shape) {
5120
- const walk9 = longestIncomingWalk(graph, errorNodeId, ROOT_CAUSE_MAX_DEPTH);
5121
- const match = shape(graph, origin, walk9);
5432
+ const walk10 = longestIncomingWalk(graph, errorNodeId, ROOT_CAUSE_MAX_DEPTH);
5433
+ const match = shape(graph, origin, walk10);
5122
5434
  if (match) {
5123
5435
  const reason = errorEvent ? `${match.rootCauseReason} (observed error: ${errorEvent.errorMessage})` : match.rootCauseReason;
5124
- return RootCauseResultSchema.parse({
5125
- rootCauseNode: match.rootCauseNode,
5126
- rootCauseReason: reason,
5127
- traversalPath: walk9.path,
5128
- edgeProvenances: walk9.edges.map((e) => e.provenance),
5129
- confidence: confidenceFromMix(walk9.edges),
5130
- fixRecommendation: match.fixRecommendation
5131
- });
5436
+ return {
5437
+ source: "compat",
5438
+ result: RootCauseResultSchema.parse({
5439
+ rootCauseNode: match.rootCauseNode,
5440
+ rootCauseReason: reason,
5441
+ traversalPath: walk10.path,
5442
+ edgeProvenances: walk10.edges.map((e) => e.provenance),
5443
+ confidence: confidenceFromMix(walk10.edges),
5444
+ fixRecommendation: match.fixRecommendation
5445
+ })
5446
+ };
5132
5447
  }
5133
5448
  }
5134
5449
  if (origin.type === NodeType5.ServiceNode) {
5135
5450
  const crossService = crossServiceRootCause(graph, errorNodeId, incidents, errorEvent);
5136
- if (crossService) return crossService;
5451
+ if (crossService) return { result: crossService, source: "cross-service" };
5137
5452
  }
5138
- return rootCauseFromIncidents(errorNodeId, incidents, errorEvent);
5453
+ const incident = rootCauseFromIncidents(errorNodeId, incidents, errorEvent);
5454
+ return incident ? { result: incident, source: "incident" } : null;
5139
5455
  }
5140
5456
  var INCIDENT_ROOT_CAUSE_CONFIDENCE = 0.6;
5141
5457
  function incidentMatchesNode(ev, nodeId) {
@@ -5231,26 +5547,75 @@ function dominantFailingCall(graph, serviceId15, visited) {
5231
5547
  return best;
5232
5548
  }
5233
5549
  function followFailingCallChain(graph, originServiceId, maxDepth) {
5234
- const path70 = [originServiceId];
5550
+ const path72 = [originServiceId];
5235
5551
  const edges = [];
5236
5552
  const visited = /* @__PURE__ */ new Set([originServiceId]);
5237
5553
  let current = originServiceId;
5238
5554
  for (let depth = 0; depth < maxDepth; depth++) {
5239
5555
  const hop = dominantFailingCall(graph, current, visited);
5240
5556
  if (!hop) break;
5241
- path70.push(hop.nextService);
5557
+ path72.push(hop.nextService);
5242
5558
  edges.push(hop.edge);
5243
5559
  visited.add(hop.nextService);
5244
5560
  current = hop.nextService;
5245
5561
  }
5246
5562
  if (edges.length === 0) return null;
5247
- return { path: path70, edges, culprit: current };
5563
+ return { path: path72, edges, culprit: current };
5564
+ }
5565
+ function isStaleCallEdge(e) {
5566
+ return e.type === EdgeType6.CALLS && e.provenance === Provenance6.STALE;
5567
+ }
5568
+ function staleCallDominates(e, id, curEdge, curId) {
5569
+ const ev = e.signal?.spanCount ?? e.callCount ?? 0;
5570
+ const cv = curEdge.signal?.spanCount ?? curEdge.callCount ?? 0;
5571
+ if (ev !== cv) return ev > cv;
5572
+ return id < curId;
5573
+ }
5574
+ function dominantStaleCall(graph, serviceId15, visited) {
5575
+ const bestByCallee = /* @__PURE__ */ new Map();
5576
+ for (const src of callSourcesForService(graph, serviceId15)) {
5577
+ for (const edgeId of graph.outboundEdges(src)) {
5578
+ const e = graph.getEdgeAttributes(edgeId);
5579
+ if (e.type !== EdgeType6.CALLS) continue;
5580
+ if (isFrontierNode(graph, e.target)) continue;
5581
+ const owner = resolveOwningService(graph, e.target);
5582
+ if (!owner || visited.has(owner.id)) continue;
5583
+ const cur = bestByCallee.get(owner.id);
5584
+ if (!cur || PROV_RANK[e.provenance] > PROV_RANK[cur.provenance]) {
5585
+ bestByCallee.set(owner.id, e);
5586
+ }
5587
+ }
5588
+ }
5589
+ let best = null;
5590
+ for (const [id, edge] of bestByCallee) {
5591
+ if (!isStaleCallEdge(edge)) continue;
5592
+ if (!best || staleCallDominates(edge, id, best.edge, best.nextService)) {
5593
+ best = { nextService: id, edge };
5594
+ }
5595
+ }
5596
+ return best;
5597
+ }
5598
+ function followStaleCallChain(graph, originServiceId, maxDepth) {
5599
+ const path72 = [originServiceId];
5600
+ const edges = [];
5601
+ const visited = /* @__PURE__ */ new Set([originServiceId]);
5602
+ let current = originServiceId;
5603
+ for (let depth = 0; depth < maxDepth; depth++) {
5604
+ const hop = dominantStaleCall(graph, current, visited);
5605
+ if (!hop) break;
5606
+ path72.push(hop.nextService);
5607
+ edges.push(hop.edge);
5608
+ visited.add(hop.nextService);
5609
+ current = hop.nextService;
5610
+ }
5611
+ if (edges.length === 0) return null;
5612
+ return { path: path72, edges, culprit: current };
5248
5613
  }
5249
5614
  function crossServiceRootCause(graph, originId, incidents, errorEvent) {
5250
5615
  const chain = followFailingCallChain(graph, originId, ROOT_CAUSE_MAX_DEPTH);
5251
5616
  if (!chain) return null;
5252
5617
  const culprit = chain.culprit;
5253
- const path70 = [...chain.path];
5618
+ const path72 = [...chain.path];
5254
5619
  const edgeProvenances = chain.edges.map((e) => e.provenance);
5255
5620
  const baseConfidence = confidenceFromMix(chain.edges);
5256
5621
  const confidence = Math.max(0, Math.min(1, baseConfidence * INCIDENT_ROOT_CAUSE_CONFIDENCE));
@@ -5258,14 +5623,14 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
5258
5623
  if (loc) {
5259
5624
  let rootCauseNode = culprit;
5260
5625
  if (loc.fileNode) {
5261
- path70.push(loc.fileNode);
5626
+ path72.push(loc.fileNode);
5262
5627
  edgeProvenances.push(Provenance6.OBSERVED);
5263
5628
  rootCauseNode = loc.fileNode;
5264
5629
  }
5265
5630
  return RootCauseResultSchema.parse({
5266
5631
  rootCauseNode,
5267
5632
  rootCauseReason: loc.rootCauseReason,
5268
- traversalPath: path70,
5633
+ traversalPath: path72,
5269
5634
  edgeProvenances,
5270
5635
  confidence,
5271
5636
  ...loc.fixRecommendation ? { fixRecommendation: loc.fixRecommendation } : {}
@@ -5277,7 +5642,7 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
5277
5642
  return RootCauseResultSchema.parse({
5278
5643
  rootCauseNode: culprit,
5279
5644
  rootCauseReason: `${culpritName} is failing downstream calls (${errs} observed error${errs === 1 ? "" : "s"})`,
5280
- traversalPath: path70,
5645
+ traversalPath: path72,
5281
5646
  edgeProvenances,
5282
5647
  confidence,
5283
5648
  fixRecommendation: `Inspect ${culpritName}'s failing handler`
@@ -5664,17 +6029,20 @@ function displayNameOf(nodeId) {
5664
6029
  return nodeId.replace(/^[a-z]+:/, "");
5665
6030
  }
5666
6031
  function getRootCause(graph, errorNodeId, errorEvent, incidents, opts) {
5667
- const legacy = legacyRootCause(graph, errorNodeId, errorEvent, incidents);
5668
- if (!legacy) return null;
6032
+ const tagged = legacyRootCause(graph, errorNodeId, errorEvent, incidents);
6033
+ if (!tagged) return null;
5669
6034
  const navigation = opts?.navigation ?? process.env.NEAT_RCA_NAVIGATION !== "0";
5670
- if (!navigation) return legacy;
5671
- return enrichWithNavigation(graph, errorNodeId, legacy, incidents, opts?.now ?? Date.now());
6035
+ if (!navigation) return tagged.result;
6036
+ return enrichWithNavigation(graph, errorNodeId, tagged, incidents, opts?.now ?? Date.now());
5672
6037
  }
5673
- function enrichWithNavigation(graph, errorNodeId, legacy, incidents, now) {
6038
+ function enrichWithNavigation(graph, errorNodeId, tagged, incidents, now) {
6039
+ const legacy = tagged.result;
5674
6040
  const seedNode = legacy.rootCauseNode;
5675
6041
  const seedCtx = graph.hasNode(seedNode) ? nodeContext(graph, seedNode, incidents, now) : null;
5676
6042
  const lastProv = legacy.edgeProvenances[legacy.edgeProvenances.length - 1];
5677
6043
  const candidates = [];
6044
+ const deadEndOnSymptom = tagged.source === "incident" && seedNode === errorNodeId && legacy.traversalPath.length === 1;
6045
+ const staleChain = deadEndOnSymptom && !(seedCtx && isVictimSeed(seedCtx)) ? followStaleCallChain(graph, errorNodeId, ROOT_CAUSE_MAX_DEPTH) : null;
5678
6046
  if (seedCtx && isVictimSeed(seedCtx)) {
5679
6047
  const origin = findLoadOrigin(graph, errorNodeId, incidents, now);
5680
6048
  const staleNote = seedCtx.stale ? "; it has gone STALE under load" : "";
@@ -5699,6 +6067,27 @@ function enrichWithNavigation(graph, errorNodeId, legacy, incidents, now) {
5699
6067
  confidence: Math.min(legacy.confidence, 0.4),
5700
6068
  ...lastProv ? { provenance: lastProv } : {}
5701
6069
  });
6070
+ } else if (staleChain) {
6071
+ const culprit = staleChain.culprit;
6072
+ const culpritName = displayNameOf(culprit);
6073
+ const seedName = displayNameOf(seedNode);
6074
+ const staleConfidence = confidenceFromMix(staleChain.edges, now);
6075
+ candidates.push({
6076
+ node: culprit,
6077
+ classification: "primary-failure",
6078
+ reason: `${culpritName} is the stale-derived root cause (low confidence): live telemetry for this subgraph has gone quiet, but the last-observed topology traces the failure surfacing at ${seedName} downstream through a STALE call chain to ${culpritName}. Provenance is STALE, so confidence is capped low \u2014 restore instrumentation and re-run to confirm before acting.`,
6079
+ context: nodeContext(graph, culprit, incidents, now),
6080
+ confidence: staleConfidence,
6081
+ provenance: Provenance6.STALE
6082
+ });
6083
+ candidates.push({
6084
+ node: seedNode,
6085
+ classification: "symptom-only",
6086
+ reason: `The failure surfaced here, but the only causal chain the graph still holds is STALE and runs downstream \u2014 ${seedName} is the surface of a stale-traced failure, not a proven origin.`,
6087
+ context: seedCtx ?? EMPTY_CONTEXT,
6088
+ confidence: Math.min(legacy.confidence, PROVENANCE_CEILING.STALE),
6089
+ ...lastProv ? { provenance: lastProv } : {}
6090
+ });
5702
6091
  } else {
5703
6092
  candidates.push({
5704
6093
  node: seedNode,
@@ -5712,11 +6101,14 @@ function enrichWithNavigation(graph, errorNodeId, legacy, incidents, now) {
5712
6101
  const top = candidates[0];
5713
6102
  let traversalPath = legacy.traversalPath;
5714
6103
  let edgeProvenances = legacy.edgeProvenances;
5715
- if (top.node !== seedNode) {
5716
- const path70 = findPath(graph, errorNodeId, top.node, "up", ROOT_CAUSE_MAX_DEPTH);
5717
- if (path70) {
5718
- traversalPath = path70.nodes;
5719
- edgeProvenances = path70.edges.map((e) => e.provenance);
6104
+ if (staleChain && top.node === staleChain.culprit) {
6105
+ traversalPath = staleChain.path;
6106
+ edgeProvenances = staleChain.edges.map((e) => e.provenance);
6107
+ } else if (top.node !== seedNode) {
6108
+ const path72 = findPath(graph, errorNodeId, top.node, "up", ROOT_CAUSE_MAX_DEPTH);
6109
+ if (path72) {
6110
+ traversalPath = path72.nodes;
6111
+ edgeProvenances = path72.edges.map((e) => e.provenance);
5720
6112
  } else {
5721
6113
  traversalPath = [errorNodeId, top.node];
5722
6114
  edgeProvenances = [top.provenance ?? Provenance6.OBSERVED];
@@ -5738,6 +6130,9 @@ function fixRecommendationForTop(top, seedNode, legacy) {
5738
6130
  return legacy.fixRecommendation;
5739
6131
  }
5740
6132
  const name = top.node.replace(/^service:/, "");
6133
+ if (top.provenance === Provenance6.STALE) {
6134
+ return `Live telemetry for this path has gone quiet; the last-observed topology traces the failure downstream to ${name}. Restore instrumentation (or re-run with live traces) to confirm, then inspect ${name}.`;
6135
+ }
5741
6136
  if (top.classification === "primary-failure") {
5742
6137
  return `Reduce or throttle the load from ${name} (or scale the saturated downstream capacity it drives) \u2014 the failure originates at this overloading source, not the starved callee.`;
5743
6138
  }
@@ -6827,7 +7222,7 @@ import Php2 from "tree-sitter-php";
6827
7222
  import CSharp from "tree-sitter-c-sharp";
6828
7223
  import Java from "tree-sitter-java";
6829
7224
  import Kotlin from "tree-sitter-kotlin";
6830
- import Rust from "tree-sitter-rust";
7225
+ import Rust2 from "tree-sitter-rust";
6831
7226
  import Cpp from "tree-sitter-cpp";
6832
7227
  import {
6833
7228
  EdgeType as EdgeType7,
@@ -6855,7 +7250,7 @@ var SYMBOL_GRAMMAR_BY_EXT = {
6855
7250
  ".cs": CSharp,
6856
7251
  ".java": Java,
6857
7252
  ".kt": Kotlin,
6858
- ".rs": Rust,
7253
+ ".rs": Rust2,
6859
7254
  // C++ (ADR-202) — only the UNAMBIGUOUS extensions. `.cpp` / `.cc` / `.cxx` /
6860
7255
  // `.c++` are implementation files; `.hpp` / `.hh` / `.hxx` / `.h++` are C++-only
6861
7256
  // headers. `.h` and `.c` are deliberately absent: they are shared with C (a
@@ -7295,15 +7690,15 @@ function collectKotlinSymbolDefs(root) {
7295
7690
  });
7296
7691
  };
7297
7692
  const join = (prefix, name) => prefix ? `${prefix}.${name}` : name;
7298
- const firstChildOfType = (node, types) => {
7693
+ const firstChildOfType2 = (node, types) => {
7299
7694
  for (let i = 0; i < node.namedChildCount; i++) {
7300
7695
  const child = node.namedChild(i);
7301
7696
  if (child && types.includes(child.type)) return child;
7302
7697
  }
7303
7698
  return void 0;
7304
7699
  };
7305
- const nameOf = (node, ...types) => firstChildOfType(node, types)?.text;
7306
- const bodyOf = (node) => firstChildOfType(node, ["class_body", "enum_class_body"]);
7700
+ const nameOf = (node, ...types) => firstChildOfType2(node, types)?.text;
7701
+ const bodyOf = (node) => firstChildOfType2(node, ["class_body", "enum_class_body"]);
7307
7702
  let pkg;
7308
7703
  for (let i = 0; i < root.namedChildCount; i++) {
7309
7704
  const child = root.namedChild(i);
@@ -7810,7 +8205,7 @@ async function addSymbolEdges(graph, services) {
7810
8205
  return best;
7811
8206
  };
7812
8207
  const requests = [];
7813
- const walk9 = (node) => {
8208
+ const walk10 = (node) => {
7814
8209
  if (node.type === "class_declaration" || node.type === "abstract_class_declaration" || node.type === "class") {
7815
8210
  const self = localBySpan.get(`${node.startPosition.row + 1}:${node.endPosition.row + 1}`);
7816
8211
  if (self && self.kind === "class") {
@@ -7856,10 +8251,10 @@ async function addSymbolEdges(graph, services) {
7856
8251
  }
7857
8252
  for (let i = 0; i < node.namedChildCount; i++) {
7858
8253
  const child = node.namedChild(i);
7859
- if (child) walk9(child);
8254
+ if (child) walk10(child);
7860
8255
  }
7861
8256
  };
7862
- walk9(root);
8257
+ walk10(root);
7863
8258
  for (const req of requests) {
7864
8259
  const targetSid = resolveTarget(req.targetName, req.wantKind);
7865
8260
  if (!targetSid) continue;
@@ -8160,7 +8555,7 @@ async function addServerActions(graph, services) {
8160
8555
  }
8161
8556
 
8162
8557
  // src/extract/databases/index.ts
8163
- import path33 from "path";
8558
+ import path34 from "path";
8164
8559
  import {
8165
8560
  EdgeType as EdgeType10,
8166
8561
  NodeType as NodeType20,
@@ -8593,8 +8988,161 @@ async function parse8(serviceDir) {
8593
8988
  }
8594
8989
  var sequelizeParser = { name: "sequelize", parse: parse8 };
8595
8990
 
8596
- // src/extract/databases/docker-compose.ts
8991
+ // src/extract/databases/csharp.ts
8992
+ import { promises as fs22 } from "fs";
8597
8993
  import path32 from "path";
8994
+ var CS_EXT = ".cs";
8995
+ var NPGSQL_GATE = /\bUseNpgsql\b|\bNpgsql\b/;
8996
+ var REDIS_GATE = /\bConnectionMultiplexer\b|\bStackExchange\.Redis\b|\bConfigurationOptions\.Parse\b|\bAddStackExchangeRedisCache\b/;
8997
+ var ENV_READ_RE = /(?:GetEnvironmentVariable|GetConnectionString)\(\s*"([^"]+)"\s*\)|Configuration\s*\[\s*"([^"]+)"\s*\]/g;
8998
+ var STRING_LITERAL_RE = /@?"([^"\\]*(?:\\.[^"\\]*)*)"/g;
8999
+ function hostIsUnresolved(host) {
9000
+ return host === "" || /[${}]/.test(host);
9001
+ }
9002
+ function looksLikePostgres(s) {
9003
+ return /(?:^|;)\s*(?:host|server|data\s*source)\s*=/i.test(s) || /^postgres(?:ql)?:\/\//i.test(s);
9004
+ }
9005
+ function looksLikeRedis(s) {
9006
+ return /^rediss?:\/\//i.test(s) || /^[A-Za-z0-9_.-]+:\d+(?:$|,)/.test(s) || /,\s*(?:ssl|abortconnect|allowadmin|connecttimeout|password|user)\s*=/i.test(s);
9007
+ }
9008
+ function parsePostgresConnection(raw) {
9009
+ const s = raw.trim();
9010
+ if (/^postgres(?:ql)?:\/\//i.test(s)) return parseConnectionString(s);
9011
+ const fields = /* @__PURE__ */ new Map();
9012
+ for (const part of s.split(";")) {
9013
+ const eq = part.indexOf("=");
9014
+ if (eq < 0) continue;
9015
+ const key = part.slice(0, eq).trim().toLowerCase().replace(/\s+/g, " ");
9016
+ const value = part.slice(eq + 1).trim();
9017
+ if (value && !fields.has(key)) fields.set(key, value);
9018
+ }
9019
+ const hostRaw = fields.get("host") ?? fields.get("server") ?? fields.get("data source");
9020
+ if (!hostRaw) return null;
9021
+ const host = hostRaw.split(",")[0].trim();
9022
+ if (hostIsUnresolved(host)) return null;
9023
+ const portRaw = fields.get("port");
9024
+ const port = portRaw && /^\d+$/.test(portRaw) ? Number(portRaw) : void 0;
9025
+ const database = fields.get("database") ?? fields.get("db") ?? "";
9026
+ return { host, port, database, engine: "postgresql", engineVersion: "unknown" };
9027
+ }
9028
+ function parseRedisEndpoint(raw) {
9029
+ const s = raw.trim();
9030
+ if (/^rediss?:\/\//i.test(s)) return parseConnectionString(s);
9031
+ const first = s.split(",")[0].trim();
9032
+ const m = first.match(/^([A-Za-z0-9_.-]+)(?::(\d+))?$/);
9033
+ if (!m) return null;
9034
+ const host = m[1];
9035
+ if (hostIsUnresolved(host) || host.includes("=")) return null;
9036
+ const port = m[2] ? Number(m[2]) : void 0;
9037
+ return { host, port, database: "", engine: "redis", engineVersion: "unknown" };
9038
+ }
9039
+ async function resolveEnvUpTree(startDir, name) {
9040
+ let dir = path32.resolve(startDir);
9041
+ for (let depth = 0; depth < 12; depth++) {
9042
+ const value = await resolveEnvVar(dir, name);
9043
+ if (value !== null) return value;
9044
+ const atRepoRoot = await fs22.access(path32.join(dir, ".git")).then(() => true).catch(() => false);
9045
+ const parent = path32.dirname(dir);
9046
+ if (atRepoRoot || parent === dir) break;
9047
+ dir = parent;
9048
+ }
9049
+ return null;
9050
+ }
9051
+ async function interpolateEnvRefs(value, dir) {
9052
+ const refs = /* @__PURE__ */ new Set();
9053
+ for (const m of value.matchAll(/\$\{([A-Za-z_][A-Za-z0-9_]*)\}|\$([A-Za-z_][A-Za-z0-9_]*)/g)) {
9054
+ refs.add(m[1] ?? m[2]);
9055
+ }
9056
+ let out = value;
9057
+ for (const name of refs) {
9058
+ const resolved = await resolveEnvUpTree(dir, name);
9059
+ if (resolved === null) continue;
9060
+ out = out.split(`\${${name}}`).join(resolved).replace(new RegExp(`\\$${name}\\b`, "g"), resolved);
9061
+ }
9062
+ return out;
9063
+ }
9064
+ function stringLiterals(masked) {
9065
+ const out = [];
9066
+ STRING_LITERAL_RE.lastIndex = 0;
9067
+ let m;
9068
+ while ((m = STRING_LITERAL_RE.exec(masked)) !== null) out.push(m[1]);
9069
+ return out;
9070
+ }
9071
+ function envKeys(masked) {
9072
+ const out = [];
9073
+ ENV_READ_RE.lastIndex = 0;
9074
+ let m;
9075
+ while ((m = ENV_READ_RE.exec(masked)) !== null) {
9076
+ const key = m[1] ?? m[2];
9077
+ if (key) out.push(key);
9078
+ }
9079
+ return out;
9080
+ }
9081
+ async function resolveConfigs(literals, keys, serviceDir, looksLike, parse11) {
9082
+ const out = [];
9083
+ for (const key of keys) {
9084
+ const raw = await resolveEnvUpTree(serviceDir, key);
9085
+ if (raw === null) continue;
9086
+ const value = await interpolateEnvRefs(raw, serviceDir);
9087
+ if (!looksLike(value)) continue;
9088
+ const parsed = parse11(value);
9089
+ if (parsed) out.push(parsed);
9090
+ }
9091
+ for (const lit of literals) {
9092
+ if (!looksLike(lit)) continue;
9093
+ const parsed = parse11(lit);
9094
+ if (parsed) out.push(parsed);
9095
+ }
9096
+ return out;
9097
+ }
9098
+ async function parse9(serviceDir) {
9099
+ const files = (await walkSourceFiles(serviceDir).catch(() => [])).filter(
9100
+ (f) => path32.extname(f) === CS_EXT
9101
+ );
9102
+ if (files.length === 0) return [];
9103
+ const sources = [];
9104
+ for (const file of files) {
9105
+ const content = await fs22.readFile(file, "utf8").catch(() => null);
9106
+ if (content !== null) sources.push({ file, content });
9107
+ }
9108
+ let pgGateFile = null;
9109
+ let redisGateFile = null;
9110
+ for (const { file, content } of sources) {
9111
+ if (pgGateFile === null && NPGSQL_GATE.test(content)) pgGateFile = file;
9112
+ if (redisGateFile === null && REDIS_GATE.test(content)) redisGateFile = file;
9113
+ }
9114
+ if (!pgGateFile && !redisGateFile) return [];
9115
+ const literals = [];
9116
+ const keys = [];
9117
+ for (const { content } of sources) {
9118
+ const masked = maskCommentsInSource(content);
9119
+ literals.push(...stringLiterals(masked));
9120
+ keys.push(...envKeys(masked));
9121
+ }
9122
+ const out = [];
9123
+ const seenHosts = /* @__PURE__ */ new Set();
9124
+ const push = (config, sourceFile) => {
9125
+ const dedupe = `${config.engine}:${config.host}`;
9126
+ if (seenHosts.has(dedupe)) return;
9127
+ seenHosts.add(dedupe);
9128
+ out.push({ ...config, sourceFile });
9129
+ };
9130
+ if (pgGateFile) {
9131
+ for (const pg3 of await resolveConfigs(literals, keys, serviceDir, looksLikePostgres, parsePostgresConnection)) {
9132
+ push(pg3, pgGateFile);
9133
+ }
9134
+ }
9135
+ if (redisGateFile) {
9136
+ for (const redis of await resolveConfigs(literals, keys, serviceDir, looksLikeRedis, parseRedisEndpoint)) {
9137
+ push(redis, redisGateFile);
9138
+ }
9139
+ }
9140
+ return out;
9141
+ }
9142
+ var csharpParser = { name: "csharp", parse: parse9 };
9143
+
9144
+ // src/extract/databases/docker-compose.ts
9145
+ import path33 from "path";
8598
9146
  function portFromService(svc) {
8599
9147
  for (const raw of svc.ports ?? []) {
8600
9148
  const str = String(raw);
@@ -8619,9 +9167,9 @@ function databaseFromEnv(svc) {
8619
9167
  };
8620
9168
  return get("POSTGRES_DB") ?? get("MYSQL_DATABASE") ?? get("MONGO_INITDB_DATABASE") ?? "";
8621
9169
  }
8622
- async function parse9(serviceDir) {
9170
+ async function parse10(serviceDir) {
8623
9171
  for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
8624
- const abs = path32.join(serviceDir, name);
9172
+ const abs = path33.join(serviceDir, name);
8625
9173
  if (!await exists(abs)) continue;
8626
9174
  const raw = await readYaml(abs);
8627
9175
  if (!raw?.services) return [];
@@ -8643,7 +9191,7 @@ async function parse9(serviceDir) {
8643
9191
  }
8644
9192
  return [];
8645
9193
  }
8646
- var dockerComposeParser = { name: "docker-compose", parse: parse9 };
9194
+ var dockerComposeParser = { name: "docker-compose", parse: parse10 };
8647
9195
 
8648
9196
  // src/extract/databases/index.ts
8649
9197
  var DB_PARSERS = [
@@ -8655,6 +9203,7 @@ var DB_PARSERS = [
8655
9203
  ormconfigParser,
8656
9204
  typeormParser,
8657
9205
  sequelizeParser,
9206
+ csharpParser,
8658
9207
  dockerComposeParser
8659
9208
  ];
8660
9209
  function compatibleDriversFor(engine) {
@@ -8793,7 +9342,7 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
8793
9342
  discoveredVia: mergedDiscoveredVia
8794
9343
  });
8795
9344
  }
8796
- const relConfigFile = toPosix(path33.relative(service.dir, config.sourceFile));
9345
+ const relConfigFile = toPosix(path34.relative(service.dir, config.sourceFile));
8797
9346
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
8798
9347
  graph,
8799
9348
  service.pkg.name,
@@ -8802,7 +9351,7 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
8802
9351
  );
8803
9352
  nodesAdded += fn;
8804
9353
  edgesAdded += fe;
8805
- const evidenceFile = toPosix(path33.relative(scanPath, config.sourceFile));
9354
+ const evidenceFile = toPosix(path34.relative(scanPath, config.sourceFile));
8806
9355
  const edge = {
8807
9356
  id: extractedEdgeId(fileNodeId, dbNode.id, EdgeType10.CONNECTS_TO),
8808
9357
  source: fileNodeId,
@@ -8820,15 +9369,15 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
8820
9369
  if (allConfigs.length === 1) {
8821
9370
  const primary = allConfigs[0];
8822
9371
  service.node.dbConnectionTarget = primary.port ? `${primary.host}:${primary.port}` : primary.host;
8823
- const relPath = path33.relative(scanPath, primary.sourceFile);
9372
+ const relPath = path34.relative(scanPath, primary.sourceFile);
8824
9373
  const cfgId = configId(relPath);
8825
9374
  if (!graph.hasNode(cfgId)) {
8826
9375
  const cfgNode = {
8827
9376
  id: cfgId,
8828
9377
  type: NodeType20.ConfigNode,
8829
- name: path33.basename(primary.sourceFile),
9378
+ name: path34.basename(primary.sourceFile),
8830
9379
  path: relPath,
8831
- fileType: isConfigFile(path33.basename(primary.sourceFile)).fileType || "config"
9380
+ fileType: isConfigFile(path34.basename(primary.sourceFile)).fileType || "config"
8832
9381
  };
8833
9382
  graph.addNode(cfgId, cfgNode);
8834
9383
  nodesAdded++;
@@ -8868,8 +9417,8 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
8868
9417
  }
8869
9418
 
8870
9419
  // src/extract/configs.ts
8871
- import { promises as fs22 } from "fs";
8872
- import path34 from "path";
9420
+ import { promises as fs23 } from "fs";
9421
+ import path35 from "path";
8873
9422
  import {
8874
9423
  EdgeType as EdgeType11,
8875
9424
  NodeType as NodeType21,
@@ -8878,23 +9427,23 @@ import {
8878
9427
  confidenceForExtracted as confidenceForExtracted8
8879
9428
  } from "@neat.is/types";
8880
9429
  async function walkConfigFiles(dir, excludeDirs = []) {
8881
- const excluded = new Set(excludeDirs.map((d) => path34.resolve(d)));
9430
+ const excluded = new Set(excludeDirs.map((d) => path35.resolve(d)));
8882
9431
  const out = [];
8883
- async function walk9(current) {
8884
- const entries = await fs22.readdir(current, { withFileTypes: true });
9432
+ async function walk10(current) {
9433
+ const entries = await fs23.readdir(current, { withFileTypes: true });
8885
9434
  for (const entry of entries) {
8886
- const full = path34.join(current, entry.name);
9435
+ const full = path35.join(current, entry.name);
8887
9436
  if (entry.isDirectory()) {
8888
9437
  if (IGNORED_DIRS.has(entry.name)) continue;
8889
- if (excluded.has(path34.resolve(full))) continue;
9438
+ if (excluded.has(path35.resolve(full))) continue;
8890
9439
  if (await isPythonVenvDir(full)) continue;
8891
- await walk9(full);
9440
+ await walk10(full);
8892
9441
  } else if (entry.isFile() && isConfigFile(entry.name).match) {
8893
9442
  out.push(full);
8894
9443
  }
8895
9444
  }
8896
9445
  }
8897
- await walk9(dir);
9446
+ await walk10(dir);
8898
9447
  return out;
8899
9448
  }
8900
9449
  async function addConfigNodes(graph, services, scanPath) {
@@ -8903,19 +9452,19 @@ async function addConfigNodes(graph, services, scanPath) {
8903
9452
  for (const service of services) {
8904
9453
  const configFiles = await walkConfigFiles(service.dir, service.excludeDirs);
8905
9454
  for (const file of configFiles) {
8906
- const relPath = path34.relative(scanPath, file);
9455
+ const relPath = path35.relative(scanPath, file);
8907
9456
  const node = {
8908
9457
  id: configId2(relPath),
8909
9458
  type: NodeType21.ConfigNode,
8910
- name: path34.basename(file),
9459
+ name: path35.basename(file),
8911
9460
  path: relPath,
8912
- fileType: isConfigFile(path34.basename(file)).fileType
9461
+ fileType: isConfigFile(path35.basename(file)).fileType
8913
9462
  };
8914
9463
  if (!graph.hasNode(node.id)) {
8915
9464
  graph.addNode(node.id, node);
8916
9465
  nodesAdded++;
8917
9466
  }
8918
- const relToService = toPosix(path34.relative(service.dir, file));
9467
+ const relToService = toPosix(path35.relative(service.dir, file));
8919
9468
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
8920
9469
  graph,
8921
9470
  service.pkg.name,
@@ -8931,7 +9480,7 @@ async function addConfigNodes(graph, services, scanPath) {
8931
9480
  type: EdgeType11.CONFIGURED_BY,
8932
9481
  provenance: Provenance11.EXTRACTED,
8933
9482
  confidence: confidenceForExtracted8("structural"),
8934
- evidence: { file: relPath.split(path34.sep).join("/") }
9483
+ evidence: { file: relPath.split(path35.sep).join("/") }
8935
9484
  };
8936
9485
  if (!graph.hasEdge(edge.id)) {
8937
9486
  graph.addEdgeWithKey(edge.id, edge.source, edge.target, edge);
@@ -8943,8 +9492,8 @@ async function addConfigNodes(graph, services, scanPath) {
8943
9492
  }
8944
9493
 
8945
9494
  // src/extract/proto.ts
8946
- import { promises as fs23 } from "fs";
8947
- import path35 from "path";
9495
+ import { promises as fs24 } from "fs";
9496
+ import path36 from "path";
8948
9497
  import {
8949
9498
  EdgeType as EdgeType12,
8950
9499
  NodeType as NodeType22,
@@ -8989,23 +9538,23 @@ function grpcMethodsFromProto(content, fqPackage) {
8989
9538
  return out;
8990
9539
  }
8991
9540
  async function walkProtoFiles(dir, excludeDirs = []) {
8992
- const excluded = new Set(excludeDirs.map((d) => path35.resolve(d)));
9541
+ const excluded = new Set(excludeDirs.map((d) => path36.resolve(d)));
8993
9542
  const out = [];
8994
- async function walk9(current) {
8995
- const entries = await fs23.readdir(current, { withFileTypes: true }).catch(() => []);
9543
+ async function walk10(current) {
9544
+ const entries = await fs24.readdir(current, { withFileTypes: true }).catch(() => []);
8996
9545
  for (const entry of entries) {
8997
- const full = path35.join(current, entry.name);
9546
+ const full = path36.join(current, entry.name);
8998
9547
  if (entry.isDirectory()) {
8999
9548
  if (IGNORED_DIRS.has(entry.name)) continue;
9000
- if (excluded.has(path35.resolve(full))) continue;
9549
+ if (excluded.has(path36.resolve(full))) continue;
9001
9550
  if (await isPythonVenvDir(full)) continue;
9002
- await walk9(full);
9003
- } else if (entry.isFile() && path35.extname(entry.name) === PROTO_EXTENSION) {
9551
+ await walk10(full);
9552
+ } else if (entry.isFile() && path36.extname(entry.name) === PROTO_EXTENSION) {
9004
9553
  out.push(full);
9005
9554
  }
9006
9555
  }
9007
9556
  }
9008
- await walk9(dir);
9557
+ await walk10(dir);
9009
9558
  return out;
9010
9559
  }
9011
9560
  async function addGrpcMethods(graph, services) {
@@ -9015,10 +9564,10 @@ async function addGrpcMethods(graph, services) {
9015
9564
  const protoPaths = await walkProtoFiles(service.dir, service.excludeDirs);
9016
9565
  for (const protoPath of protoPaths) {
9017
9566
  if (isTestPath(protoPath)) continue;
9018
- const relFile = toPosix(path35.relative(service.dir, protoPath));
9567
+ const relFile = toPosix(path36.relative(service.dir, protoPath));
9019
9568
  let content;
9020
9569
  try {
9021
- content = await fs23.readFile(protoPath, "utf8");
9570
+ content = await fs24.readFile(protoPath, "utf8");
9022
9571
  } catch (err) {
9023
9572
  recordExtractionError("proto extraction", protoPath, err);
9024
9573
  continue;
@@ -9081,7 +9630,7 @@ import {
9081
9630
  } from "@neat.is/types";
9082
9631
 
9083
9632
  // src/extract/calls/http.ts
9084
- import path36 from "path";
9633
+ import path37 from "path";
9085
9634
  import Parser6 from "tree-sitter";
9086
9635
  import JavaScript4 from "tree-sitter-javascript";
9087
9636
  import TypeScript2 from "tree-sitter-typescript";
@@ -9169,7 +9718,7 @@ async function addHttpCallEdges(graph, services) {
9169
9718
  const seen = /* @__PURE__ */ new Set();
9170
9719
  for (const file of files) {
9171
9720
  if (isTestPath(file.path)) continue;
9172
- const parser = parserForExt(path36.extname(file.path), parserCache);
9721
+ const parser = parserForExt(path37.extname(file.path), parserCache);
9173
9722
  let sites;
9174
9723
  try {
9175
9724
  sites = callsFromSource(file.content, parser, knownHosts);
@@ -9178,7 +9727,7 @@ async function addHttpCallEdges(graph, services) {
9178
9727
  continue;
9179
9728
  }
9180
9729
  if (sites.length === 0) continue;
9181
- const relFile = toPosix(path36.relative(service.dir, file.path));
9730
+ const relFile = toPosix(path37.relative(service.dir, file.path));
9182
9731
  for (const site of sites) {
9183
9732
  const targetId = hostToNodeId.get(site.host);
9184
9733
  if (!targetId || targetId === service.node.id) continue;
@@ -9231,7 +9780,7 @@ async function addHttpCallEdges(graph, services) {
9231
9780
  }
9232
9781
 
9233
9782
  // src/extract/calls/route-match.ts
9234
- import path37 from "path";
9783
+ import path38 from "path";
9235
9784
  import Parser7 from "tree-sitter";
9236
9785
  import JavaScript5 from "tree-sitter-javascript";
9237
9786
  import {
@@ -9437,7 +9986,7 @@ async function addRouteCallEdges(graph, services) {
9437
9986
  const seen = /* @__PURE__ */ new Set();
9438
9987
  for (const file of files) {
9439
9988
  if (isTestPath(file.path)) continue;
9440
- if (!JS_CLIENT_EXTENSIONS.has(path37.extname(file.path))) continue;
9989
+ if (!JS_CLIENT_EXTENSIONS.has(path38.extname(file.path))) continue;
9441
9990
  let sites;
9442
9991
  try {
9443
9992
  sites = clientCallSitesFromSource(file.content, jsParser, knownHosts);
@@ -9446,7 +9995,7 @@ async function addRouteCallEdges(graph, services) {
9446
9995
  continue;
9447
9996
  }
9448
9997
  if (sites.length === 0) continue;
9449
- const relFile = toPosix(path37.relative(service.dir, file.path));
9998
+ const relFile = toPosix(path38.relative(service.dir, file.path));
9450
9999
  for (const site of sites) {
9451
10000
  const serverServiceId = hostToNodeId.get(site.host);
9452
10001
  if (!serverServiceId || serverServiceId === service.node.id) continue;
@@ -9506,7 +10055,7 @@ async function addRouteCallEdges(graph, services) {
9506
10055
  }
9507
10056
 
9508
10057
  // src/extract/calls/kafka.ts
9509
- import path38 from "path";
10058
+ import path39 from "path";
9510
10059
  import { infraId as infraId2 } from "@neat.is/types";
9511
10060
  var PRODUCER_TOPIC_RE = /(?:producer|kafkaProducer)[\s\S]{0,40}?\.send\s*\(\s*\{[\s\S]{0,200}?topic\s*:\s*['"`]([^'"`]+)['"`]/g;
9512
10061
  var CONSUMER_TOPIC_RE = /(?:consumer|kafkaConsumer)[\s\S]{0,40}?\.(?:subscribe|run)\s*\(\s*\{[\s\S]{0,200}?topic[s]?\s*:\s*(?:\[\s*)?['"`]([^'"`]+)['"`]/g;
@@ -9594,13 +10143,13 @@ function kafkaEndpointsFromFile(file, serviceDir) {
9594
10143
  // call sites — verified-call-site tier (ADR-066).
9595
10144
  confidenceKind: "verified-call-site",
9596
10145
  evidence: {
9597
- file: path38.relative(serviceDir, file.path),
10146
+ file: path39.relative(serviceDir, file.path),
9598
10147
  line,
9599
10148
  snippet: snippet(file.content, line)
9600
10149
  }
9601
10150
  });
9602
10151
  };
9603
- if (path38.extname(file.path) === ".go") {
10152
+ if (path39.extname(file.path) === ".go") {
9604
10153
  goSaramaEndpoints(file.content, make);
9605
10154
  } else {
9606
10155
  for (const { topic } of findAll(PRODUCER_TOPIC_RE, file.content)) make(topic, "PUBLISHES_TO");
@@ -9610,7 +10159,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
9610
10159
  }
9611
10160
 
9612
10161
  // src/extract/calls/redis.ts
9613
- import path39 from "path";
10162
+ import path40 from "path";
9614
10163
  import { infraId as infraId3 } from "@neat.is/types";
9615
10164
  var REDIS_URL_RE = /redis(?:s)?:\/\/(?:[^@'"`\s]+@)?([^:/'"`\s]+)(?::(\d+))?/g;
9616
10165
  function redisEndpointsFromFile(file, serviceDir) {
@@ -9633,7 +10182,7 @@ function redisEndpointsFromFile(file, serviceDir) {
9633
10182
  // support tier (ADR-066).
9634
10183
  confidenceKind: "url-with-structural-support",
9635
10184
  evidence: {
9636
- file: path39.relative(serviceDir, file.path),
10185
+ file: path40.relative(serviceDir, file.path),
9637
10186
  line,
9638
10187
  snippet: snippet(file.content, line)
9639
10188
  }
@@ -9643,7 +10192,7 @@ function redisEndpointsFromFile(file, serviceDir) {
9643
10192
  }
9644
10193
 
9645
10194
  // src/extract/calls/aws.ts
9646
- import path40 from "path";
10195
+ import path41 from "path";
9647
10196
  import { infraId as infraId4 } from "@neat.is/types";
9648
10197
  var S3_BUCKET_RE = /Bucket\s*:\s*['"`]([^'"`]+)['"`]/g;
9649
10198
  var DYNAMO_TABLE_RE = /TableName\s*:\s*['"`]([^'"`]+)['"`]/g;
@@ -9677,7 +10226,7 @@ function awsEndpointsFromFile(file, serviceDir) {
9677
10226
  // (ADR-066).
9678
10227
  confidenceKind: "verified-call-site",
9679
10228
  evidence: {
9680
- file: path40.relative(serviceDir, file.path),
10229
+ file: path41.relative(serviceDir, file.path),
9681
10230
  line,
9682
10231
  snippet: snippet(file.content, line)
9683
10232
  }
@@ -9701,7 +10250,7 @@ function awsEndpointsFromFile(file, serviceDir) {
9701
10250
  }
9702
10251
 
9703
10252
  // src/extract/calls/grpc.ts
9704
- import path41 from "path";
10253
+ import path42 from "path";
9705
10254
  import { infraId as infraId5 } from "@neat.is/types";
9706
10255
  var GRPC_CLIENT_RE = /new\s+([A-Z][A-Za-z0-9_]*)Client\s*\(\s*['"`]?([^,'"`)]+)?/g;
9707
10256
  var AWS_SDK_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@aws-sdk\/client-([a-z0-9-]+)['"`]/g;
@@ -9760,7 +10309,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
9760
10309
  // tier (ADR-066).
9761
10310
  confidenceKind: "verified-call-site",
9762
10311
  evidence: {
9763
- file: path41.relative(serviceDir, file.path),
10312
+ file: path42.relative(serviceDir, file.path),
9764
10313
  line,
9765
10314
  snippet: snippet(file.content, line)
9766
10315
  }
@@ -9770,7 +10319,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
9770
10319
  }
9771
10320
 
9772
10321
  // src/extract/calls/supabase.ts
9773
- import path42 from "path";
10322
+ import path43 from "path";
9774
10323
  import { infraId as infraId6 } from "@neat.is/types";
9775
10324
  var SUPABASE_JS_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@supabase\/supabase-js['"`]/;
9776
10325
  var SUPABASE_SSR_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@supabase\/ssr['"`]/;
@@ -9829,7 +10378,7 @@ function supabaseEndpointsFromFile(file, serviceDir) {
9829
10378
  // tier (ADR-066), the same grade aws.ts / grpc.ts emit at.
9830
10379
  confidenceKind: "verified-call-site",
9831
10380
  evidence: {
9832
- file: path42.relative(serviceDir, file.path),
10381
+ file: path43.relative(serviceDir, file.path),
9833
10382
  line,
9834
10383
  snippet: snippet(file.content, line)
9835
10384
  }
@@ -9856,7 +10405,7 @@ function supabaseEndpointsFromFile(file, serviceDir) {
9856
10405
  edgeType: "CALLS",
9857
10406
  confidenceKind: "verified-call-site",
9858
10407
  evidence: {
9859
- file: path42.relative(serviceDir, file.path),
10408
+ file: path43.relative(serviceDir, file.path),
9860
10409
  line,
9861
10410
  snippet: snippet(file.content, line)
9862
10411
  }
@@ -9867,7 +10416,7 @@ function supabaseEndpointsFromFile(file, serviceDir) {
9867
10416
  }
9868
10417
 
9869
10418
  // src/extract/calls/firestore.ts
9870
- import path43 from "path";
10419
+ import path44 from "path";
9871
10420
  import Parser8 from "tree-sitter";
9872
10421
  import JavaScript6 from "tree-sitter-javascript";
9873
10422
  import { infraId as infraId7 } from "@neat.is/types";
@@ -9906,7 +10455,7 @@ function isFirestoreClientFactory(node) {
9906
10455
  }
9907
10456
  function firestoreClientVars(root) {
9908
10457
  const vars = /* @__PURE__ */ new Set();
9909
- const walk9 = (node) => {
10458
+ const walk10 = (node) => {
9910
10459
  if (node.type === "variable_declarator") {
9911
10460
  const name = node.childForFieldName("name");
9912
10461
  let value = node.childForFieldName("value");
@@ -9915,9 +10464,9 @@ function firestoreClientVars(root) {
9915
10464
  vars.add(name.text);
9916
10465
  }
9917
10466
  }
9918
- for (const c of namedChildren(node)) walk9(c);
10467
+ for (const c of namedChildren(node)) walk10(c);
9919
10468
  };
9920
- walk9(root);
10469
+ walk10(root);
9921
10470
  return vars;
9922
10471
  }
9923
10472
  function isClientExpr(node, clientVars) {
@@ -10038,7 +10587,7 @@ function firestoreEndpointsFromFile(file, serviceDir) {
10038
10587
  const hasAdmin = FIRESTORE_ADMIN_IMPORT_RE.test(file.content);
10039
10588
  if (!hasClient && !hasAdmin) return [];
10040
10589
  const fileSdk = hasClient && !hasAdmin ? "client" : hasAdmin && !hasClient ? "admin" : null;
10041
- const tree = parseSource3(parserForExt2(path43.extname(file.path)), file.content);
10590
+ const tree = parseSource3(parserForExt2(path44.extname(file.path)), file.content);
10042
10591
  const clientVars = firestoreClientVars(tree.rootNode);
10043
10592
  const collLine = /* @__PURE__ */ new Map();
10044
10593
  const writes = /* @__PURE__ */ new Map();
@@ -10072,7 +10621,7 @@ function firestoreEndpointsFromFile(file, serviceDir) {
10072
10621
  }
10073
10622
  s.add(field);
10074
10623
  };
10075
- const walk9 = (node) => {
10624
+ const walk10 = (node) => {
10076
10625
  if (node.type === "call_expression") {
10077
10626
  const fn = node.childForFieldName("function");
10078
10627
  const line = node.startPosition.row + 1;
@@ -10112,9 +10661,9 @@ function firestoreEndpointsFromFile(file, serviceDir) {
10112
10661
  }
10113
10662
  }
10114
10663
  }
10115
- for (const c of namedChildren(node)) walk9(c);
10664
+ for (const c of namedChildren(node)) walk10(c);
10116
10665
  };
10117
- walk9(tree.rootNode);
10666
+ walk10(tree.rootNode);
10118
10667
  const out = [];
10119
10668
  for (const [collPath, line] of collLine) {
10120
10669
  const byField = writes.get(collPath);
@@ -10142,7 +10691,7 @@ function firestoreEndpointsFromFile(file, serviceDir) {
10142
10691
  ...columnSet.size > 0 ? { columns: [...columnSet] } : {},
10143
10692
  ...sdkWrites ? { sdkWrites } : {},
10144
10693
  evidence: {
10145
- file: path43.relative(serviceDir, file.path),
10694
+ file: path44.relative(serviceDir, file.path),
10146
10695
  line,
10147
10696
  snippet: snippet(file.content, line)
10148
10697
  }
@@ -10152,7 +10701,7 @@ function firestoreEndpointsFromFile(file, serviceDir) {
10152
10701
  }
10153
10702
 
10154
10703
  // src/extract/calls/mongoose.ts
10155
- import path44 from "path";
10704
+ import path45 from "path";
10156
10705
  import { infraId as infraId8 } from "@neat.is/types";
10157
10706
  var MONGOOSE_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])mongoose['"`]/;
10158
10707
  var MONGODB_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])mongodb['"`]/;
@@ -10303,7 +10852,7 @@ function endpoint(r, file, serviceDir, matchText) {
10303
10852
  kind: r.kind,
10304
10853
  edgeType: "CALLS",
10305
10854
  confidenceKind: "verified-call-site",
10306
- evidence: { file: path44.relative(serviceDir, file.path), line, snippet: snippet(file.content, line) }
10855
+ evidence: { file: path45.relative(serviceDir, file.path), line, snippet: snippet(file.content, line) }
10307
10856
  };
10308
10857
  }
10309
10858
  function mongooseEndpointsFromFile(file, serviceDir) {
@@ -10393,7 +10942,7 @@ async function mongooseCrossFileEndpoints(files, serviceDir) {
10393
10942
  const registry = /* @__PURE__ */ new Map();
10394
10943
  for (const f of mongooseFiles) {
10395
10944
  const fx = fileExportsOf(f.content, pluralizeOn);
10396
- if (fx) registry.set(toPosix(path44.relative(serviceDir, f.path)), fx);
10945
+ if (fx) registry.set(toPosix(path45.relative(serviceDir, f.path)), fx);
10397
10946
  }
10398
10947
  if (registry.size === 0) return [];
10399
10948
  const out = [];
@@ -10404,7 +10953,7 @@ async function mongooseCrossFileEndpoints(files, serviceDir) {
10404
10953
  const directColl = /* @__PURE__ */ new Map();
10405
10954
  const nsExports = /* @__PURE__ */ new Map();
10406
10955
  for (const b of bindings) {
10407
- const resolvedRel = await resolveJsImport(b.specifier, path44.dirname(f.path), serviceDir, null);
10956
+ const resolvedRel = await resolveJsImport(b.specifier, path45.dirname(f.path), serviceDir, null);
10408
10957
  if (!resolvedRel) continue;
10409
10958
  const fx = registry.get(resolvedRel);
10410
10959
  if (!fx) continue;
@@ -10447,7 +10996,7 @@ async function mongooseCrossFileEndpoints(files, serviceDir) {
10447
10996
  }
10448
10997
 
10449
10998
  // src/extract/calls/sqlalchemy.ts
10450
- import path45 from "path";
10999
+ import path46 from "path";
10451
11000
  import Parser9 from "tree-sitter";
10452
11001
  import Python5 from "tree-sitter-python";
10453
11002
  import { infraId as infraId9 } from "@neat.is/types";
@@ -10586,7 +11135,7 @@ function sqlalchemyForeignKeys(file, serviceDir) {
10586
11135
  childTable,
10587
11136
  parentTable,
10588
11137
  evidence: {
10589
- file: path45.relative(serviceDir, file.path),
11138
+ file: path46.relative(serviceDir, file.path),
10590
11139
  line,
10591
11140
  snippet: snippet(file.content, line)
10592
11141
  }
@@ -10611,7 +11160,7 @@ function sqlalchemyEndpointsFromFile(file, serviceDir) {
10611
11160
  confidenceKind: "verified-call-site",
10612
11161
  ...columns && columns.length > 0 ? { columns } : {},
10613
11162
  evidence: {
10614
- file: path45.relative(serviceDir, file.path),
11163
+ file: path46.relative(serviceDir, file.path),
10615
11164
  line,
10616
11165
  snippet: snippet(file.content, line)
10617
11166
  }
@@ -10718,7 +11267,7 @@ function pythonOrmCrossFileEndpoints(files, serviceDir) {
10718
11267
  edgeType: "CALLS",
10719
11268
  confidenceKind: "verified-call-site",
10720
11269
  evidence: {
10721
- file: path45.relative(serviceDir, file.path),
11270
+ file: path46.relative(serviceDir, file.path),
10722
11271
  line,
10723
11272
  snippet: snippet(file.content, line)
10724
11273
  }
@@ -10729,7 +11278,7 @@ function pythonOrmCrossFileEndpoints(files, serviceDir) {
10729
11278
  }
10730
11279
 
10731
11280
  // src/extract/calls/django-orm.ts
10732
- import path46 from "path";
11281
+ import path47 from "path";
10733
11282
  import Parser10 from "tree-sitter";
10734
11283
  import Python6 from "tree-sitter-python";
10735
11284
  import { infraId as infraId10 } from "@neat.is/types";
@@ -10799,7 +11348,7 @@ function djangoOrmEndpointsFromFile(file, serviceDir) {
10799
11348
  const tree = parseSource7(makePyParser4(), file.content);
10800
11349
  const out = [];
10801
11350
  const seen = /* @__PURE__ */ new Set();
10802
- const defaultAppLabel = path46.basename(path46.dirname(file.path));
11351
+ const defaultAppLabel = path47.basename(path47.dirname(file.path));
10803
11352
  walk4(tree.rootNode, (node) => {
10804
11353
  if (node.type !== "class_definition") return;
10805
11354
  if (!extendsDjangoModel(node)) return;
@@ -10817,14 +11366,14 @@ function djangoOrmEndpointsFromFile(file, serviceDir) {
10817
11366
  kind: "sql-table",
10818
11367
  edgeType: "CALLS",
10819
11368
  confidenceKind: "verified-call-site",
10820
- evidence: { file: path46.relative(serviceDir, file.path), line, snippet: snippet(file.content, line) }
11369
+ evidence: { file: path47.relative(serviceDir, file.path), line, snippet: snippet(file.content, line) }
10821
11370
  });
10822
11371
  });
10823
11372
  return out;
10824
11373
  }
10825
11374
 
10826
11375
  // src/extract/calls/drizzle.ts
10827
- import path47 from "path";
11376
+ import path48 from "path";
10828
11377
  import Parser11 from "tree-sitter";
10829
11378
  import JavaScript7 from "tree-sitter-javascript";
10830
11379
  import { infraId as infraId11 } from "@neat.is/types";
@@ -10902,10 +11451,10 @@ function columnsFromObject(obj) {
10902
11451
  }
10903
11452
  function drizzleEndpointsFromFile(file, serviceDir) {
10904
11453
  if (!DRIZZLE_IMPORT_RE.test(file.content)) return [];
10905
- const tree = parseSource3(parserForExt3(path47.extname(file.path)), file.content);
11454
+ const tree = parseSource3(parserForExt3(path48.extname(file.path)), file.content);
10906
11455
  const out = [];
10907
11456
  const seen = /* @__PURE__ */ new Set();
10908
- const walk9 = (node) => {
11457
+ const walk10 = (node) => {
10909
11458
  if (node.type === "call_expression") {
10910
11459
  const fn = node.childForFieldName("function");
10911
11460
  if (fn?.type === "identifier" && TABLE_BUILDERS.has(fn.text)) {
@@ -10925,7 +11474,7 @@ function drizzleEndpointsFromFile(file, serviceDir) {
10925
11474
  confidenceKind: "structural",
10926
11475
  columns,
10927
11476
  evidence: {
10928
- file: path47.relative(serviceDir, file.path),
11477
+ file: path48.relative(serviceDir, file.path),
10929
11478
  line,
10930
11479
  snippet: snippet(file.content, line)
10931
11480
  }
@@ -10933,9 +11482,9 @@ function drizzleEndpointsFromFile(file, serviceDir) {
10933
11482
  }
10934
11483
  }
10935
11484
  }
10936
- for (const c of namedChildren4(node)) walk9(c);
11485
+ for (const c of namedChildren4(node)) walk10(c);
10937
11486
  };
10938
- walk9(tree.rootNode);
11487
+ walk10(tree.rootNode);
10939
11488
  return out;
10940
11489
  }
10941
11490
  function enclosingVarName(call) {
@@ -10957,7 +11506,7 @@ function enclosingVarName(call) {
10957
11506
  function collectDrizzleTables(root) {
10958
11507
  const tables = [];
10959
11508
  const varToTable = /* @__PURE__ */ new Map();
10960
- const walk9 = (node) => {
11509
+ const walk10 = (node) => {
10961
11510
  if (node.type === "call_expression") {
10962
11511
  const fn = node.childForFieldName("function");
10963
11512
  if (fn?.type === "identifier" && TABLE_BUILDERS.has(fn.text)) {
@@ -10972,9 +11521,9 @@ function collectDrizzleTables(root) {
10972
11521
  }
10973
11522
  }
10974
11523
  }
10975
- for (const c of namedChildren4(node)) walk9(c);
11524
+ for (const c of namedChildren4(node)) walk10(c);
10976
11525
  };
10977
- walk9(root);
11526
+ walk10(root);
10978
11527
  return { tables, varToTable };
10979
11528
  }
10980
11529
  function referencesTargetVar(call) {
@@ -10991,13 +11540,13 @@ function referencesTargetVar(call) {
10991
11540
  }
10992
11541
  function drizzleForeignKeys(file, serviceDir) {
10993
11542
  if (!DRIZZLE_IMPORT_RE.test(file.content)) return [];
10994
- const tree = parseSource3(parserForExt3(path47.extname(file.path)), file.content);
11543
+ const tree = parseSource3(parserForExt3(path48.extname(file.path)), file.content);
10995
11544
  const { tables, varToTable } = collectDrizzleTables(tree.rootNode);
10996
11545
  const out = [];
10997
11546
  const seen = /* @__PURE__ */ new Set();
10998
11547
  for (const table of tables) {
10999
11548
  if (!table.object) continue;
11000
- const walk9 = (node) => {
11549
+ const walk10 = (node) => {
11001
11550
  if (node.type === "call_expression") {
11002
11551
  const targetVar = referencesTargetVar(node);
11003
11552
  const parentTable = targetVar ? varToTable.get(targetVar) : void 0;
@@ -11010,7 +11559,7 @@ function drizzleForeignKeys(file, serviceDir) {
11010
11559
  childTable: table.tableName,
11011
11560
  parentTable,
11012
11561
  evidence: {
11013
- file: path47.relative(serviceDir, file.path),
11562
+ file: path48.relative(serviceDir, file.path),
11014
11563
  line,
11015
11564
  snippet: snippet(file.content, line)
11016
11565
  }
@@ -11018,15 +11567,15 @@ function drizzleForeignKeys(file, serviceDir) {
11018
11567
  }
11019
11568
  }
11020
11569
  }
11021
- for (const c of namedChildren4(node)) walk9(c);
11570
+ for (const c of namedChildren4(node)) walk10(c);
11022
11571
  };
11023
- walk9(table.object);
11572
+ walk10(table.object);
11024
11573
  }
11025
11574
  return out;
11026
11575
  }
11027
11576
 
11028
11577
  // src/extract/calls/prisma.ts
11029
- import path48 from "path";
11578
+ import path49 from "path";
11030
11579
  import { infraId as infraId12 } from "@neat.is/types";
11031
11580
  var SCALAR_TYPES = /* @__PURE__ */ new Set([
11032
11581
  "Int",
@@ -11082,7 +11631,7 @@ function prismaColumnsFromSchema(file, serviceDir) {
11082
11631
  confidenceKind: "structural",
11083
11632
  columns: b.columns,
11084
11633
  evidence: {
11085
- file: path48.relative(serviceDir, file.path),
11634
+ file: path49.relative(serviceDir, file.path),
11086
11635
  line: b.startLine,
11087
11636
  snippet: snippet(content, b.startLine)
11088
11637
  }
@@ -11143,7 +11692,7 @@ function prismaColumnsFromSchema(file, serviceDir) {
11143
11692
  }
11144
11693
  async function prismaColumnEndpoints(serviceDir) {
11145
11694
  const schemaPath = await findFirst(serviceDir, [
11146
- path48.join("prisma", "schema.prisma"),
11695
+ path49.join("prisma", "schema.prisma"),
11147
11696
  "schema.prisma"
11148
11697
  ]);
11149
11698
  if (!schemaPath) return [];
@@ -11218,7 +11767,7 @@ function prismaForeignKeysFromSchema(file, serviceDir) {
11218
11767
  childTable: current.table,
11219
11768
  parentTable,
11220
11769
  evidence: {
11221
- file: path48.relative(serviceDir, file.path),
11770
+ file: path49.relative(serviceDir, file.path),
11222
11771
  line: lineNo,
11223
11772
  snippet: snippet(content, lineNo)
11224
11773
  }
@@ -11233,7 +11782,7 @@ function prismaForeignKeysFromSchema(file, serviceDir) {
11233
11782
  }
11234
11783
  async function prismaForeignKeys(serviceDir) {
11235
11784
  const schemaPath = await findFirst(serviceDir, [
11236
- path48.join("prisma", "schema.prisma"),
11785
+ path49.join("prisma", "schema.prisma"),
11237
11786
  "schema.prisma"
11238
11787
  ]);
11239
11788
  if (!schemaPath) return [];
@@ -11243,7 +11792,7 @@ async function prismaForeignKeys(serviceDir) {
11243
11792
  }
11244
11793
 
11245
11794
  // src/extract/calls/activerecord.ts
11246
- import path49 from "path";
11795
+ import path50 from "path";
11247
11796
  import Parser12 from "tree-sitter";
11248
11797
  import Ruby3 from "tree-sitter-ruby";
11249
11798
  import { infraId as infraId13 } from "@neat.is/types";
@@ -11451,7 +12000,7 @@ function railsSchemaEndpointsFromFile(file, serviceDir) {
11451
12000
  confidenceKind: "structural",
11452
12001
  ...table.columns.length > 0 ? { columns: table.columns } : {},
11453
12002
  evidence: {
11454
- file: path49.relative(serviceDir, file.path),
12003
+ file: path50.relative(serviceDir, file.path),
11455
12004
  line: table.line,
11456
12005
  snippet: snippet(file.content, table.line)
11457
12006
  }
@@ -11473,7 +12022,7 @@ function railsSchemaForeignKeys(file, serviceDir) {
11473
12022
  childTable,
11474
12023
  parentTable,
11475
12024
  evidence: {
11476
- file: path49.relative(serviceDir, file.path),
12025
+ file: path50.relative(serviceDir, file.path),
11477
12026
  line,
11478
12027
  snippet: snippet(file.content, line)
11479
12028
  }
@@ -11556,7 +12105,7 @@ function railsModelEndpointsFromFile(file, serviceDir) {
11556
12105
  edgeType: "CALLS",
11557
12106
  confidenceKind: "verified-call-site",
11558
12107
  evidence: {
11559
- file: path49.relative(serviceDir, file.path),
12108
+ file: path50.relative(serviceDir, file.path),
11560
12109
  line,
11561
12110
  snippet: snippet(file.content, line)
11562
12111
  }
@@ -11593,7 +12142,7 @@ function railsModelForeignKeys(file, serviceDir) {
11593
12142
  childTable,
11594
12143
  parentTable,
11595
12144
  evidence: {
11596
- file: path49.relative(serviceDir, file.path),
12145
+ file: path50.relative(serviceDir, file.path),
11597
12146
  line,
11598
12147
  snippet: snippet(file.content, line)
11599
12148
  }
@@ -11604,7 +12153,7 @@ function railsModelForeignKeys(file, serviceDir) {
11604
12153
  }
11605
12154
 
11606
12155
  // src/extract/calls/eloquent.ts
11607
- import path50 from "path";
12156
+ import path51 from "path";
11608
12157
  import Parser13 from "tree-sitter";
11609
12158
  import Php3 from "tree-sitter-php";
11610
12159
  import { infraId as infraId14 } from "@neat.is/types";
@@ -11861,7 +12410,7 @@ function laravelMigrationEndpointsFromFile(file, serviceDir) {
11861
12410
  confidenceKind: "structural",
11862
12411
  ...columns.length > 0 ? { columns } : {},
11863
12412
  evidence: {
11864
- file: path50.relative(serviceDir, file.path),
12413
+ file: path51.relative(serviceDir, file.path),
11865
12414
  line: bp.line,
11866
12415
  snippet: snippet(file.content, bp.line)
11867
12416
  }
@@ -11883,7 +12432,7 @@ function laravelMigrationForeignKeys(file, serviceDir) {
11883
12432
  childTable,
11884
12433
  parentTable,
11885
12434
  evidence: {
11886
- file: path50.relative(serviceDir, file.path),
12435
+ file: path51.relative(serviceDir, file.path),
11887
12436
  line,
11888
12437
  snippet: snippet(file.content, line)
11889
12438
  }
@@ -12003,7 +12552,7 @@ function laravelModelEndpointsFromFile(file, serviceDir) {
12003
12552
  edgeType: "CALLS",
12004
12553
  confidenceKind: "verified-call-site",
12005
12554
  evidence: {
12006
- file: path50.relative(serviceDir, file.path),
12555
+ file: path51.relative(serviceDir, file.path),
12007
12556
  line,
12008
12557
  snippet: snippet(file.content, line)
12009
12558
  }
@@ -12039,7 +12588,7 @@ function laravelModelForeignKeys(file, serviceDir) {
12039
12588
  childTable,
12040
12589
  parentTable,
12041
12590
  evidence: {
12042
- file: path50.relative(serviceDir, file.path),
12591
+ file: path51.relative(serviceDir, file.path),
12043
12592
  line,
12044
12593
  snippet: snippet(file.content, line)
12045
12594
  }
@@ -12050,7 +12599,7 @@ function laravelModelForeignKeys(file, serviceDir) {
12050
12599
  }
12051
12600
 
12052
12601
  // src/extract/calls/go.ts
12053
- import path51 from "path";
12602
+ import path52 from "path";
12054
12603
  import Parser14 from "tree-sitter";
12055
12604
  import Go4 from "tree-sitter-go";
12056
12605
  import { infraId as infraId15 } from "@neat.is/types";
@@ -12124,7 +12673,7 @@ function firstStringLiteralArg(argsNode) {
12124
12673
  return null;
12125
12674
  }
12126
12675
  function goSqlEndpointsFromFile(file, serviceDir) {
12127
- if (path51.extname(file.path) !== ".go") return [];
12676
+ if (path52.extname(file.path) !== ".go") return [];
12128
12677
  if (!file.content.includes(DATABASE_SQL_IMPORT) && !file.content.includes(SQLX_IMPORT)) return [];
12129
12678
  const tree = parseSource10(makeGoParser3(), file.content);
12130
12679
  const importsDatabaseSql = goImportsAny(tree.rootNode, /* @__PURE__ */ new Set([DATABASE_SQL_IMPORT]));
@@ -12153,7 +12702,7 @@ function goSqlEndpointsFromFile(file, serviceDir) {
12153
12702
  confidenceKind: "verified-call-site",
12154
12703
  ...columns.length > 0 ? { columns } : {},
12155
12704
  evidence: {
12156
- file: toPosix(path51.relative(serviceDir, file.path)),
12705
+ file: toPosix(path52.relative(serviceDir, file.path)),
12157
12706
  line,
12158
12707
  snippet: snippet(file.content, line)
12159
12708
  }
@@ -12163,7 +12712,7 @@ function goSqlEndpointsFromFile(file, serviceDir) {
12163
12712
  }
12164
12713
 
12165
12714
  // src/extract/calls/gorm.ts
12166
- import path52 from "path";
12715
+ import path53 from "path";
12167
12716
  import Parser15 from "tree-sitter";
12168
12717
  import Go5 from "tree-sitter-go";
12169
12718
  import { infraId as infraId16 } from "@neat.is/types";
@@ -12596,7 +13145,7 @@ function collectColumns(struct, structs, seen, prefix, out, emitted) {
12596
13145
  seen.delete(struct.name);
12597
13146
  }
12598
13147
  function gormEndpointsFromFile(file, serviceDir) {
12599
- if (path52.extname(file.path) !== ".go") return [];
13148
+ if (path53.extname(file.path) !== ".go") return [];
12600
13149
  if (!GORM_IMPORT_RE.test(file.content)) return [];
12601
13150
  const tree = parseSource11(makeGoParser4(), file.content);
12602
13151
  const { structs, models, tableFor } = analyze(tree);
@@ -12618,7 +13167,7 @@ function gormEndpointsFromFile(file, serviceDir) {
12618
13167
  confidenceKind: "structural",
12619
13168
  ...columns.length > 0 ? { columns } : {},
12620
13169
  evidence: {
12621
- file: toPosix(path52.relative(serviceDir, file.path)),
13170
+ file: toPosix(path53.relative(serviceDir, file.path)),
12622
13171
  line: struct.line,
12623
13172
  snippet: snippet(file.content, struct.line)
12624
13173
  }
@@ -12627,7 +13176,7 @@ function gormEndpointsFromFile(file, serviceDir) {
12627
13176
  return out;
12628
13177
  }
12629
13178
  function gormForeignKeys(file, serviceDir) {
12630
- if (path52.extname(file.path) !== ".go") return [];
13179
+ if (path53.extname(file.path) !== ".go") return [];
12631
13180
  if (!GORM_IMPORT_RE.test(file.content)) return [];
12632
13181
  const tree = parseSource11(makeGoParser4(), file.content);
12633
13182
  const { structs, models, tableFor } = analyze(tree);
@@ -12642,7 +13191,7 @@ function gormForeignKeys(file, serviceDir) {
12642
13191
  childTable,
12643
13192
  parentTable,
12644
13193
  evidence: {
12645
- file: toPosix(path52.relative(serviceDir, file.path)),
13194
+ file: toPosix(path53.relative(serviceDir, file.path)),
12646
13195
  line,
12647
13196
  snippet: snippet(file.content, line)
12648
13197
  }
@@ -12677,6 +13226,121 @@ function gormForeignKeys(file, serviceDir) {
12677
13226
  return out;
12678
13227
  }
12679
13228
 
13229
+ // src/extract/calls/efcore.ts
13230
+ import path54 from "path";
13231
+ import Parser16 from "tree-sitter";
13232
+ import CSharp2 from "tree-sitter-c-sharp";
13233
+ import { infraId as infraId17 } from "@neat.is/types";
13234
+ var EFCORE_GATE = /Microsoft\.EntityFrameworkCore|DataAnnotations\.Schema|\bDbContext\b|\bDbSet\s*</;
13235
+ var PARSE_CHUNK12 = 16384;
13236
+ function makeCsParser() {
13237
+ const p = new Parser16();
13238
+ p.setLanguage(CSharp2);
13239
+ return p;
13240
+ }
13241
+ function parseSource12(parser, source) {
13242
+ return parser.parse(
13243
+ (index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK12)
13244
+ );
13245
+ }
13246
+ function walk9(node, visit) {
13247
+ visit(node);
13248
+ for (let i = 0; i < node.namedChildCount; i++) {
13249
+ const c = node.namedChild(i);
13250
+ if (c) walk9(c, visit);
13251
+ }
13252
+ }
13253
+ function firstChildOfType(node, type) {
13254
+ for (let i = 0; i < node.namedChildCount; i++) {
13255
+ const c = node.namedChild(i);
13256
+ if (c?.type === type) return c;
13257
+ }
13258
+ return null;
13259
+ }
13260
+ function csStringLiteral(node) {
13261
+ if (node.type === "string_literal") {
13262
+ let out = "";
13263
+ for (let i = 0; i < node.namedChildCount; i++) {
13264
+ const c = node.namedChild(i);
13265
+ if (c?.type === "string_literal_content") out += c.text;
13266
+ }
13267
+ return out;
13268
+ }
13269
+ if (node.type === "verbatim_string_literal") {
13270
+ const t = node.text;
13271
+ return t.length >= 3 ? t.slice(2, -1).replace(/""/g, '"') : "";
13272
+ }
13273
+ return null;
13274
+ }
13275
+ function attributeName(attr) {
13276
+ const nameNode = attr.childForFieldName("name") ?? attr.namedChild(0);
13277
+ if (!nameNode) return null;
13278
+ const text = nameNode.text;
13279
+ const base = text.includes(".") ? text.slice(text.lastIndexOf(".") + 1) : text;
13280
+ return base.endsWith("Attribute") ? base.slice(0, -"Attribute".length) : base;
13281
+ }
13282
+ function tableFromAttribute(attr) {
13283
+ if (attributeName(attr) !== "Table") return null;
13284
+ const args = attr.childForFieldName("arguments") ?? firstChildOfType(attr, "attribute_argument_list");
13285
+ if (!args) return null;
13286
+ for (let i = 0; i < args.namedChildCount; i++) {
13287
+ const arg = args.namedChild(i);
13288
+ if (arg?.type !== "attribute_argument") continue;
13289
+ const first = arg.namedChild(0);
13290
+ if (!first) continue;
13291
+ const value = csStringLiteral(first);
13292
+ if (value !== null) return value;
13293
+ return null;
13294
+ }
13295
+ return null;
13296
+ }
13297
+ function tableFromToTable(call) {
13298
+ const fn = call.childForFieldName("function");
13299
+ if (fn?.type !== "member_access_expression") return null;
13300
+ const method = fn.childForFieldName("name") ?? fn.namedChild(fn.namedChildCount - 1);
13301
+ if (method?.text !== "ToTable") return null;
13302
+ const args = call.childForFieldName("arguments");
13303
+ const firstArg2 = args?.namedChild(0);
13304
+ if (firstArg2?.type !== "argument") return null;
13305
+ const value = firstArg2.namedChild(0);
13306
+ return value ? csStringLiteral(value) : null;
13307
+ }
13308
+ function efcoreEndpointsFromFile(file, serviceDir) {
13309
+ if (path54.extname(file.path) !== ".cs") return [];
13310
+ if (!EFCORE_GATE.test(file.content)) return [];
13311
+ const tree = parseSource12(makeCsParser(), file.content);
13312
+ const out = [];
13313
+ const seen = /* @__PURE__ */ new Set();
13314
+ const push = (name, line) => {
13315
+ if (!name || seen.has(name)) return;
13316
+ seen.add(name);
13317
+ out.push({
13318
+ infraId: infraId17("sql-table", name),
13319
+ name,
13320
+ kind: "sql-table",
13321
+ edgeType: "CALLS",
13322
+ confidenceKind: "structural",
13323
+ evidence: {
13324
+ file: toPosix(path54.relative(serviceDir, file.path)),
13325
+ line,
13326
+ snippet: snippet(file.content, line)
13327
+ }
13328
+ });
13329
+ };
13330
+ walk9(tree.rootNode, (node) => {
13331
+ if (node.type === "attribute") {
13332
+ const table = tableFromAttribute(node);
13333
+ if (table) push(table, node.startPosition.row + 1);
13334
+ return;
13335
+ }
13336
+ if (node.type === "invocation_expression") {
13337
+ const table = tableFromToTable(node);
13338
+ if (table) push(table, node.startPosition.row + 1);
13339
+ }
13340
+ });
13341
+ return out;
13342
+ }
13343
+
12680
13344
  // src/extract/calls/index.ts
12681
13345
  function edgeTypeFromEndpoint(ep) {
12682
13346
  switch (ep.edgeType) {
@@ -12735,6 +13399,11 @@ async function addExternalEndpointEdges(graph, services) {
12735
13399
  } catch (err) {
12736
13400
  recordExtractionError("laravel eloquent extraction", file.path, err);
12737
13401
  }
13402
+ try {
13403
+ endpoints.push(...efcoreEndpointsFromFile(file, service.dir));
13404
+ } catch (err) {
13405
+ recordExtractionError("efcore data-axis extraction", file.path, err);
13406
+ }
12738
13407
  }
12739
13408
  endpoints.push(...await mongooseCrossFileEndpoints(maskedFiles, service.dir));
12740
13409
  endpoints.push(...pythonOrmCrossFileEndpoints(maskedFiles, service.dir));
@@ -12838,7 +13507,7 @@ import {
12838
13507
  Provenance as Provenance16,
12839
13508
  confidenceForExtracted as confidenceForExtracted13,
12840
13509
  extractedEdgeId as extractedEdgeId10,
12841
- infraId as infraId17
13510
+ infraId as infraId18
12842
13511
  } from "@neat.is/types";
12843
13512
  async function addTableEdges(graph, services) {
12844
13513
  let nodesAdded = 0;
@@ -12867,8 +13536,8 @@ async function addTableEdges(graph, services) {
12867
13536
  }
12868
13537
  refs.push(...modelRefs);
12869
13538
  for (const ref of refs) {
12870
- const childId = infraId17("sql-table", ref.childTable);
12871
- const parentId = infraId17("sql-table", ref.parentTable);
13539
+ const childId = infraId18("sql-table", ref.childTable);
13540
+ const parentId = infraId18("sql-table", ref.parentTable);
12872
13541
  if (childId === parentId) continue;
12873
13542
  nodesAdded += ensureTableNode(graph, childId, ref.childTable);
12874
13543
  nodesAdded += ensureTableNode(graph, parentId, ref.parentTable);
@@ -12903,14 +13572,14 @@ function ensureTableNode(graph, id, name) {
12903
13572
  }
12904
13573
 
12905
13574
  // src/extract/infra/docker-compose.ts
12906
- import path53 from "path";
13575
+ import path55 from "path";
12907
13576
  import { EdgeType as EdgeType17, Provenance as Provenance18, confidenceForExtracted as confidenceForExtracted15 } from "@neat.is/types";
12908
13577
 
12909
13578
  // src/extract/infra/shared.ts
12910
- import { NodeType as NodeType26, Provenance as Provenance17, confidenceForExtracted as confidenceForExtracted14, infraId as infraId18 } from "@neat.is/types";
13579
+ import { NodeType as NodeType26, Provenance as Provenance17, confidenceForExtracted as confidenceForExtracted14, infraId as infraId19 } from "@neat.is/types";
12911
13580
  function makeInfraNode(kind, name, provider = "self", extras) {
12912
13581
  return {
12913
- id: infraId18(kind, name),
13582
+ id: infraId19(kind, name),
12914
13583
  type: NodeType26.InfraNode,
12915
13584
  name,
12916
13585
  provider,
@@ -12973,7 +13642,7 @@ function dependsOnList(value) {
12973
13642
  }
12974
13643
  function serviceNameToServiceNode(name, services) {
12975
13644
  for (const s of services) {
12976
- if (s.node.name === name || path53.basename(s.dir) === name) return s.node.id;
13645
+ if (s.node.name === name || path55.basename(s.dir) === name) return s.node.id;
12977
13646
  }
12978
13647
  return null;
12979
13648
  }
@@ -12982,7 +13651,7 @@ async function addComposeInfra(graph, scanPath, services) {
12982
13651
  let edgesAdded = 0;
12983
13652
  let composePath = null;
12984
13653
  for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
12985
- const abs = path53.join(scanPath, name);
13654
+ const abs = path55.join(scanPath, name);
12986
13655
  if (await exists(abs)) {
12987
13656
  composePath = abs;
12988
13657
  break;
@@ -12995,13 +13664,13 @@ async function addComposeInfra(graph, scanPath, services) {
12995
13664
  } catch (err) {
12996
13665
  recordExtractionError(
12997
13666
  "infra docker-compose",
12998
- path53.relative(scanPath, composePath),
13667
+ path55.relative(scanPath, composePath),
12999
13668
  err
13000
13669
  );
13001
13670
  return { nodesAdded, edgesAdded };
13002
13671
  }
13003
13672
  if (!compose?.services) return { nodesAdded, edgesAdded };
13004
- const evidenceFile = path53.relative(scanPath, composePath).split(path53.sep).join("/");
13673
+ const evidenceFile = path55.relative(scanPath, composePath).split(path55.sep).join("/");
13005
13674
  const composeNameToNodeId = /* @__PURE__ */ new Map();
13006
13675
  for (const [composeName, svc] of Object.entries(compose.services)) {
13007
13676
  const matchedServiceId = serviceNameToServiceNode(composeName, services);
@@ -13042,8 +13711,8 @@ async function addComposeInfra(graph, scanPath, services) {
13042
13711
  }
13043
13712
 
13044
13713
  // src/extract/infra/dockerfile.ts
13045
- import path54 from "path";
13046
- import { promises as fs24 } from "fs";
13714
+ import path56 from "path";
13715
+ import { promises as fs25 } from "fs";
13047
13716
  import { EdgeType as EdgeType18, Provenance as Provenance19, confidenceForExtracted as confidenceForExtracted16 } from "@neat.is/types";
13048
13717
  function readDockerfile(content) {
13049
13718
  let image = null;
@@ -13073,15 +13742,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
13073
13742
  let nodesAdded = 0;
13074
13743
  let edgesAdded = 0;
13075
13744
  for (const service of services) {
13076
- const dockerfilePath = path54.join(service.dir, "Dockerfile");
13745
+ const dockerfilePath = path56.join(service.dir, "Dockerfile");
13077
13746
  if (!await exists(dockerfilePath)) continue;
13078
13747
  let content;
13079
13748
  try {
13080
- content = await fs24.readFile(dockerfilePath, "utf8");
13749
+ content = await fs25.readFile(dockerfilePath, "utf8");
13081
13750
  } catch (err) {
13082
13751
  recordExtractionError(
13083
13752
  "infra dockerfile",
13084
- path54.relative(scanPath, dockerfilePath),
13753
+ path56.relative(scanPath, dockerfilePath),
13085
13754
  err
13086
13755
  );
13087
13756
  continue;
@@ -13093,8 +13762,8 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
13093
13762
  graph.addNode(node.id, node);
13094
13763
  nodesAdded++;
13095
13764
  }
13096
- const relDockerfile = toPosix(path54.relative(service.dir, dockerfilePath));
13097
- const evidenceFile = toPosix(path54.relative(scanPath, dockerfilePath));
13765
+ const relDockerfile = toPosix(path56.relative(service.dir, dockerfilePath));
13766
+ const evidenceFile = toPosix(path56.relative(scanPath, dockerfilePath));
13098
13767
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
13099
13768
  graph,
13100
13769
  service.pkg.name,
@@ -13145,23 +13814,23 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
13145
13814
  }
13146
13815
 
13147
13816
  // src/extract/infra/terraform.ts
13148
- import { promises as fs25 } from "fs";
13149
- import path55 from "path";
13817
+ import { promises as fs26 } from "fs";
13818
+ import path57 from "path";
13150
13819
  import { EdgeType as EdgeType19, Provenance as Provenance20, confidenceForExtracted as confidenceForExtracted17 } from "@neat.is/types";
13151
13820
  var RESOURCE_RE = /resource\s+"(aws_[A-Za-z0-9_]+)"\s+"([A-Za-z0-9_-]+)"/g;
13152
13821
  var REFERENCE_RE = /(?<![\w.])(aws_[A-Za-z0-9_]+)\.([A-Za-z0-9_-]+)/g;
13153
13822
  async function walkTfFiles(start, depth = 0, max = 5) {
13154
13823
  if (depth > max) return [];
13155
13824
  const out = [];
13156
- const entries = await fs25.readdir(start, { withFileTypes: true }).catch(() => []);
13825
+ const entries = await fs26.readdir(start, { withFileTypes: true }).catch(() => []);
13157
13826
  for (const entry of entries) {
13158
13827
  if (entry.isDirectory()) {
13159
13828
  if (IGNORED_DIRS.has(entry.name) || entry.name === ".terraform") continue;
13160
- const child = path55.join(start, entry.name);
13829
+ const child = path57.join(start, entry.name);
13161
13830
  if (await isPythonVenvDir(child)) continue;
13162
13831
  out.push(...await walkTfFiles(child, depth + 1, max));
13163
13832
  } else if (entry.isFile() && entry.name.endsWith(".tf")) {
13164
- out.push(path55.join(start, entry.name));
13833
+ out.push(path57.join(start, entry.name));
13165
13834
  }
13166
13835
  }
13167
13836
  return out;
@@ -13192,8 +13861,8 @@ async function addTerraformResources(graph, scanPath) {
13192
13861
  let edgesAdded = 0;
13193
13862
  const files = await walkTfFiles(scanPath);
13194
13863
  for (const file of files) {
13195
- const content = await fs25.readFile(file, "utf8");
13196
- const evidenceFile = toPosix(path55.relative(scanPath, file));
13864
+ const content = await fs26.readFile(file, "utf8");
13865
+ const evidenceFile = toPosix(path57.relative(scanPath, file));
13197
13866
  const resources = [];
13198
13867
  const byKey = /* @__PURE__ */ new Map();
13199
13868
  RESOURCE_RE.lastIndex = 0;
@@ -13249,8 +13918,8 @@ async function addTerraformResources(graph, scanPath) {
13249
13918
  }
13250
13919
 
13251
13920
  // src/extract/infra/k8s.ts
13252
- import { promises as fs26 } from "fs";
13253
- import path56 from "path";
13921
+ import { promises as fs27 } from "fs";
13922
+ import path58 from "path";
13254
13923
  import { parseAllDocuments as parseAllDocuments2 } from "yaml";
13255
13924
  var K8S_KIND_TO_INFRA_KIND = {
13256
13925
  Service: "k8s-service",
@@ -13264,15 +13933,15 @@ var K8S_KIND_TO_INFRA_KIND = {
13264
13933
  async function walkYamlFiles2(start, depth = 0, max = 5) {
13265
13934
  if (depth > max) return [];
13266
13935
  const out = [];
13267
- const entries = await fs26.readdir(start, { withFileTypes: true }).catch(() => []);
13936
+ const entries = await fs27.readdir(start, { withFileTypes: true }).catch(() => []);
13268
13937
  for (const entry of entries) {
13269
13938
  if (entry.isDirectory()) {
13270
13939
  if (IGNORED_DIRS.has(entry.name)) continue;
13271
- const child = path56.join(start, entry.name);
13940
+ const child = path58.join(start, entry.name);
13272
13941
  if (await isPythonVenvDir(child)) continue;
13273
13942
  out.push(...await walkYamlFiles2(child, depth + 1, max));
13274
- } else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(path56.extname(entry.name))) {
13275
- out.push(path56.join(start, entry.name));
13943
+ } else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(path58.extname(entry.name))) {
13944
+ out.push(path58.join(start, entry.name));
13276
13945
  }
13277
13946
  }
13278
13947
  return out;
@@ -13281,7 +13950,7 @@ async function addK8sResources(graph, scanPath) {
13281
13950
  let nodesAdded = 0;
13282
13951
  const files = await walkYamlFiles2(scanPath);
13283
13952
  for (const file of files) {
13284
- const content = await fs26.readFile(file, "utf8");
13953
+ const content = await fs27.readFile(file, "utf8");
13285
13954
  let docs;
13286
13955
  try {
13287
13956
  docs = parseAllDocuments2(content).map((d) => d.toJSON());
@@ -13304,16 +13973,16 @@ async function addK8sResources(graph, scanPath) {
13304
13973
  }
13305
13974
 
13306
13975
  // src/extract/infra/cloudflare.ts
13307
- import { promises as fs27 } from "fs";
13308
- import path57 from "path";
13976
+ import { promises as fs28 } from "fs";
13977
+ import path59 from "path";
13309
13978
  import { parse as parseToml3 } from "smol-toml";
13310
13979
  import { EdgeType as EdgeType20, Provenance as Provenance21, confidenceForExtracted as confidenceForExtracted18 } from "@neat.is/types";
13311
13980
  var WRANGLER_FILENAMES = ["wrangler.toml", "wrangler.jsonc", "wrangler.json"];
13312
13981
  async function readWranglerConfig(dir) {
13313
13982
  for (const filename of WRANGLER_FILENAMES) {
13314
- const abs = path57.join(dir, filename);
13983
+ const abs = path59.join(dir, filename);
13315
13984
  if (!await exists(abs)) continue;
13316
- const raw = await fs27.readFile(abs, "utf8");
13985
+ const raw = await fs28.readFile(abs, "utf8");
13317
13986
  const config = filename === "wrangler.toml" ? parseToml3(raw) : JSON.parse(maskCommentsInSource(raw));
13318
13987
  return { config, relFile: filename, raw };
13319
13988
  }
@@ -13374,11 +14043,11 @@ async function addCloudflareWorkers(graph, services, scanPath) {
13374
14043
  try {
13375
14044
  read = await readWranglerConfig(service.dir);
13376
14045
  } catch (err) {
13377
- recordExtractionError("infra cloudflare", path57.relative(scanPath, service.dir), err);
14046
+ recordExtractionError("infra cloudflare", path59.relative(scanPath, service.dir), err);
13378
14047
  continue;
13379
14048
  }
13380
14049
  if (!read || !read.config.name) continue;
13381
- const evidenceFile = toPosix(path57.relative(scanPath, path57.join(service.dir, read.relFile)));
14050
+ const evidenceFile = toPosix(path59.relative(scanPath, path59.join(service.dir, read.relFile)));
13382
14051
  discovered.push({ service, config: read.config, relFile: read.relFile, raw: read.raw, evidenceFile });
13383
14052
  }
13384
14053
  for (const worker of discovered) {
@@ -13390,7 +14059,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
13390
14059
  }
13391
14060
  let anchorId = service.node.id;
13392
14061
  if (config.main) {
13393
- const entryRelPath = toPosix(path57.normalize(config.main));
14062
+ const entryRelPath = toPosix(path59.normalize(config.main));
13394
14063
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
13395
14064
  graph,
13396
14065
  service.pkg.name,
@@ -13536,24 +14205,24 @@ async function addCloudflareWorkers(graph, services, scanPath) {
13536
14205
  }
13537
14206
 
13538
14207
  // src/extract/infra/vercel.ts
13539
- import { promises as fs28 } from "fs";
13540
- import path58 from "path";
14208
+ import { promises as fs29 } from "fs";
14209
+ import path60 from "path";
13541
14210
  import { EdgeType as EdgeType21 } from "@neat.is/types";
13542
14211
  var VERCEL_CONFIG_FILENAMES = ["vercel.json", "vercel.jsonc"];
13543
14212
  async function readVercelConfig(dir) {
13544
14213
  for (const filename of VERCEL_CONFIG_FILENAMES) {
13545
- const abs = path58.join(dir, filename);
14214
+ const abs = path60.join(dir, filename);
13546
14215
  if (!await exists(abs)) continue;
13547
- const raw = await fs28.readFile(abs, "utf8");
14216
+ const raw = await fs29.readFile(abs, "utf8");
13548
14217
  const config = JSON.parse(maskCommentsInSource(raw));
13549
14218
  return { config, relFile: filename, raw };
13550
14219
  }
13551
14220
  return null;
13552
14221
  }
13553
14222
  async function readLinkedProjectName(dir) {
13554
- const abs = path58.join(dir, ".vercel", "project.json");
14223
+ const abs = path60.join(dir, ".vercel", "project.json");
13555
14224
  if (!await exists(abs)) return void 0;
13556
- const parsed = JSON.parse(await fs28.readFile(abs, "utf8"));
14225
+ const parsed = JSON.parse(await fs29.readFile(abs, "utf8"));
13557
14226
  return typeof parsed.projectName === "string" ? parsed.projectName : void 0;
13558
14227
  }
13559
14228
  function routeSource(route) {
@@ -13569,7 +14238,7 @@ async function addVercelServices(graph, services, scanPath) {
13569
14238
  read = await readVercelConfig(service.dir);
13570
14239
  projectName = await readLinkedProjectName(service.dir);
13571
14240
  } catch (err) {
13572
- recordExtractionError("infra vercel", path58.relative(scanPath, service.dir), err);
14241
+ recordExtractionError("infra vercel", path60.relative(scanPath, service.dir), err);
13573
14242
  continue;
13574
14243
  }
13575
14244
  if (!read && !projectName) continue;
@@ -13585,7 +14254,7 @@ async function addVercelServices(graph, services, scanPath) {
13585
14254
  const anchorId = service.node.id;
13586
14255
  if (!read) continue;
13587
14256
  const { config, relFile, raw } = read;
13588
- const evidenceFile = toPosix(path58.relative(scanPath, path58.join(service.dir, relFile)));
14257
+ const evidenceFile = toPosix(path60.relative(scanPath, path60.join(service.dir, relFile)));
13589
14258
  const add = (edgeType, kind, name) => {
13590
14259
  if (!name) return;
13591
14260
  const result = emitPlatformResourceEdge(
@@ -13613,16 +14282,16 @@ async function addVercelServices(graph, services, scanPath) {
13613
14282
  }
13614
14283
 
13615
14284
  // src/extract/infra/railway.ts
13616
- import { promises as fs29 } from "fs";
13617
- import path59 from "path";
14285
+ import { promises as fs30 } from "fs";
14286
+ import path61 from "path";
13618
14287
  import { parse as parseToml4 } from "smol-toml";
13619
14288
  import { EdgeType as EdgeType22 } from "@neat.is/types";
13620
14289
  var RAILWAY_FILENAMES = ["railway.toml", "railway.json", "railway.jsonc"];
13621
14290
  async function readRailwayConfig(dir) {
13622
14291
  for (const filename of RAILWAY_FILENAMES) {
13623
- const abs = path59.join(dir, filename);
14292
+ const abs = path61.join(dir, filename);
13624
14293
  if (!await exists(abs)) continue;
13625
- const raw = await fs29.readFile(abs, "utf8");
14294
+ const raw = await fs30.readFile(abs, "utf8");
13626
14295
  const config = filename === "railway.toml" ? parseToml4(raw) : JSON.parse(maskCommentsInSource(raw));
13627
14296
  return { config, relFile: filename, raw };
13628
14297
  }
@@ -13636,7 +14305,7 @@ async function addRailwayServices(graph, services, scanPath) {
13636
14305
  try {
13637
14306
  read = await readRailwayConfig(service.dir);
13638
14307
  } catch (err) {
13639
- recordExtractionError("infra railway", path59.relative(scanPath, service.dir), err);
14308
+ recordExtractionError("infra railway", path61.relative(scanPath, service.dir), err);
13640
14309
  continue;
13641
14310
  }
13642
14311
  if (!read) continue;
@@ -13646,7 +14315,7 @@ async function addRailwayServices(graph, services, scanPath) {
13646
14315
  }
13647
14316
  const anchorId = service.node.id;
13648
14317
  const { config, relFile, raw } = read;
13649
- const evidenceFile = toPosix(path59.relative(scanPath, path59.join(service.dir, relFile)));
14318
+ const evidenceFile = toPosix(path61.relative(scanPath, path61.join(service.dir, relFile)));
13650
14319
  const add = (edgeType, kind, name) => {
13651
14320
  if (!name) return;
13652
14321
  const result = emitPlatformResourceEdge(
@@ -13670,15 +14339,15 @@ async function addRailwayServices(graph, services, scanPath) {
13670
14339
  }
13671
14340
 
13672
14341
  // src/extract/infra/supabase.ts
13673
- import { promises as fs30 } from "fs";
13674
- import path60 from "path";
14342
+ import { promises as fs31 } from "fs";
14343
+ import path62 from "path";
13675
14344
  import { parse as parseToml5 } from "smol-toml";
13676
14345
  import { EdgeType as EdgeType23 } from "@neat.is/types";
13677
14346
  async function readSupabaseConfig(dir) {
13678
- const relFile = path60.join("supabase", "config.toml");
13679
- const abs = path60.join(dir, relFile);
14347
+ const relFile = path62.join("supabase", "config.toml");
14348
+ const abs = path62.join(dir, relFile);
13680
14349
  if (!await exists(abs)) return null;
13681
- const raw = await fs30.readFile(abs, "utf8");
14350
+ const raw = await fs31.readFile(abs, "utf8");
13682
14351
  const config = parseToml5(raw);
13683
14352
  return { config, relFile, raw };
13684
14353
  }
@@ -13690,7 +14359,7 @@ async function addSupabaseProjects(graph, services, scanPath) {
13690
14359
  try {
13691
14360
  read = await readSupabaseConfig(service.dir);
13692
14361
  } catch (err) {
13693
- recordExtractionError("infra supabase", path60.relative(scanPath, service.dir), err);
14362
+ recordExtractionError("infra supabase", path62.relative(scanPath, service.dir), err);
13694
14363
  continue;
13695
14364
  }
13696
14365
  if (!read) continue;
@@ -13705,7 +14374,7 @@ async function addSupabaseProjects(graph, services, scanPath) {
13705
14374
  });
13706
14375
  }
13707
14376
  const anchorId = service.node.id;
13708
- const evidenceFile = toPosix(path60.relative(scanPath, path60.join(service.dir, relFile)));
14377
+ const evidenceFile = toPosix(path62.relative(scanPath, path62.join(service.dir, relFile)));
13709
14378
  const add = (edgeType, kind, name) => {
13710
14379
  if (!name) return;
13711
14380
  const result = emitPlatformResourceEdge(
@@ -13746,8 +14415,8 @@ async function addInfra(graph, scanPath, services) {
13746
14415
  }
13747
14416
 
13748
14417
  // src/extract/zod-shapes.ts
13749
- import path61 from "path";
13750
- import Parser16 from "tree-sitter";
14418
+ import path63 from "path";
14419
+ import Parser17 from "tree-sitter";
13751
14420
  import JavaScript8 from "tree-sitter-javascript";
13752
14421
  import {
13753
14422
  EdgeType as EdgeType24,
@@ -13755,12 +14424,12 @@ import {
13755
14424
  Provenance as Provenance22,
13756
14425
  confidenceForExtracted as confidenceForExtracted19,
13757
14426
  extractedEdgeId as extractedEdgeId11,
13758
- infraId as infraId19
14427
+ infraId as infraId20
13759
14428
  } from "@neat.is/types";
13760
14429
  var ZOD_IMPORT_RE = /\bzod\b/;
13761
14430
  var ZOD_OBJECTS = /* @__PURE__ */ new Set(["z", "zod"]);
13762
14431
  function parserForExt4(ext) {
13763
- const p = new Parser16();
14432
+ const p = new Parser17();
13764
14433
  p.setLanguage(GRAMMAR_BY_EXT[ext] ?? JavaScript8);
13765
14434
  return p;
13766
14435
  }
@@ -13848,7 +14517,7 @@ function topLevelSchemas(root) {
13848
14517
  }
13849
14518
  function zodShapesFromFile(file, serviceDir) {
13850
14519
  if (!ZOD_IMPORT_RE.test(file.content)) return [];
13851
- const tree = parseSource3(parserForExt4(path61.extname(file.path)), file.content);
14520
+ const tree = parseSource3(parserForExt4(path63.extname(file.path)), file.content);
13852
14521
  const out = [];
13853
14522
  const seen = /* @__PURE__ */ new Set();
13854
14523
  for (const { name, call } of topLevelSchemas(tree.rootNode)) {
@@ -13862,11 +14531,11 @@ function zodShapesFromFile(file, serviceDir) {
13862
14531
  seen.add(name);
13863
14532
  const line = call.startPosition.row + 1;
13864
14533
  out.push({
13865
- infraId: infraId19("zod-schema", name),
14534
+ infraId: infraId20("zod-schema", name),
13866
14535
  name,
13867
14536
  fields,
13868
14537
  evidence: {
13869
- file: path61.relative(serviceDir, file.path),
14538
+ file: path63.relative(serviceDir, file.path),
13870
14539
  line,
13871
14540
  snippet: snippet(file.content, line)
13872
14541
  }
@@ -14102,11 +14771,11 @@ async function addFirestoreRules(graph, services) {
14102
14771
  }
14103
14772
 
14104
14773
  // src/extract/index.ts
14105
- import path63 from "path";
14774
+ import path65 from "path";
14106
14775
 
14107
14776
  // src/extract/retire.ts
14108
14777
  import { existsSync as existsSync2 } from "fs";
14109
- import path62 from "path";
14778
+ import path64 from "path";
14110
14779
  import { NodeType as NodeType29, Provenance as Provenance23 } from "@neat.is/types";
14111
14780
  function dropOrphanedFileNodes(graph) {
14112
14781
  const orphans = [];
@@ -14140,11 +14809,11 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
14140
14809
  if (edge.provenance !== Provenance23.EXTRACTED) return;
14141
14810
  const evidenceFile = edge.evidence?.file;
14142
14811
  if (!evidenceFile) return;
14143
- if (path62.isAbsolute(evidenceFile)) {
14812
+ if (path64.isAbsolute(evidenceFile)) {
14144
14813
  if (!existsSync2(evidenceFile)) toDrop.push(id);
14145
14814
  return;
14146
14815
  }
14147
- const found = bases.some((base) => existsSync2(path62.join(base, evidenceFile)));
14816
+ const found = bases.some((base) => existsSync2(path64.join(base, evidenceFile)));
14148
14817
  if (!found) toDrop.push(id);
14149
14818
  });
14150
14819
  for (const id of toDrop) graph.dropEdge(id);
@@ -14201,7 +14870,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
14201
14870
  }
14202
14871
  const droppedEntries = drainDroppedExtracted();
14203
14872
  if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
14204
- const rejectedPath = path63.join(path63.dirname(opts.errorsPath), "rejected.ndjson");
14873
+ const rejectedPath = path65.join(path65.dirname(opts.errorsPath), "rejected.ndjson");
14205
14874
  try {
14206
14875
  await writeRejectedExtracted(droppedEntries, rejectedPath);
14207
14876
  } catch (err) {
@@ -14575,8 +15244,8 @@ function computeDivergences(graph, opts = {}) {
14575
15244
  }
14576
15245
 
14577
15246
  // src/persist.ts
14578
- import { promises as fs31 } from "fs";
14579
- import path64 from "path";
15247
+ import { promises as fs32 } from "fs";
15248
+ import path66 from "path";
14580
15249
  import { NodeType as NodeType31, Provenance as Provenance25, observedEdgeId as observedEdgeId2 } from "@neat.is/types";
14581
15250
  var SCHEMA_VERSION = 7;
14582
15251
  function migrateV1ToV2(payload) {
@@ -14631,7 +15300,7 @@ function migrateV2ToV3(payload) {
14631
15300
  return { ...payload, schemaVersion: 3 };
14632
15301
  }
14633
15302
  async function ensureDir(filePath) {
14634
- await fs31.mkdir(path64.dirname(filePath), { recursive: true });
15303
+ await fs32.mkdir(path66.dirname(filePath), { recursive: true });
14635
15304
  }
14636
15305
  async function saveGraphToDisk(graph, outPath) {
14637
15306
  await ensureDir(outPath);
@@ -14641,13 +15310,13 @@ async function saveGraphToDisk(graph, outPath) {
14641
15310
  graph: graph.export()
14642
15311
  };
14643
15312
  const tmp = `${outPath}.tmp`;
14644
- await fs31.writeFile(tmp, JSON.stringify(payload), "utf8");
14645
- await fs31.rename(tmp, outPath);
15313
+ await fs32.writeFile(tmp, JSON.stringify(payload), "utf8");
15314
+ await fs32.rename(tmp, outPath);
14646
15315
  }
14647
15316
  async function loadGraphFromDisk(graph, outPath) {
14648
15317
  let raw;
14649
15318
  try {
14650
- raw = await fs31.readFile(outPath, "utf8");
15319
+ raw = await fs32.readFile(outPath, "utf8");
14651
15320
  } catch (err) {
14652
15321
  if (err.code === "ENOENT") return;
14653
15322
  throw err;
@@ -14720,7 +15389,7 @@ function startPersistLoop(graph, outPath, opts = {}) {
14720
15389
  }
14721
15390
 
14722
15391
  // src/diff.ts
14723
- import { promises as fs32 } from "fs";
15392
+ import { promises as fs33 } from "fs";
14724
15393
  async function loadSnapshotForDiff(target) {
14725
15394
  if (/^https?:\/\//i.test(target)) {
14726
15395
  const res = await fetch(target);
@@ -14729,7 +15398,7 @@ async function loadSnapshotForDiff(target) {
14729
15398
  }
14730
15399
  return await res.json();
14731
15400
  }
14732
- const raw = await fs32.readFile(target, "utf8");
15401
+ const raw = await fs33.readFile(target, "utf8");
14733
15402
  return JSON.parse(raw);
14734
15403
  }
14735
15404
  function indexEntries(entries) {
@@ -14796,23 +15465,23 @@ function canonicalJson(value) {
14796
15465
  }
14797
15466
 
14798
15467
  // src/projects.ts
14799
- import path65 from "path";
15468
+ import path67 from "path";
14800
15469
  function pathsForProject(project, baseDir) {
14801
15470
  if (project === DEFAULT_PROJECT) {
14802
15471
  return {
14803
- snapshotPath: path65.join(baseDir, "graph.json"),
14804
- errorsPath: path65.join(baseDir, "errors.ndjson"),
14805
- staleEventsPath: path65.join(baseDir, "stale-events.ndjson"),
14806
- embeddingsCachePath: path65.join(baseDir, "embeddings.json"),
14807
- policyViolationsPath: path65.join(baseDir, "policy-violations.ndjson")
15472
+ snapshotPath: path67.join(baseDir, "graph.json"),
15473
+ errorsPath: path67.join(baseDir, "errors.ndjson"),
15474
+ staleEventsPath: path67.join(baseDir, "stale-events.ndjson"),
15475
+ embeddingsCachePath: path67.join(baseDir, "embeddings.json"),
15476
+ policyViolationsPath: path67.join(baseDir, "policy-violations.ndjson")
14808
15477
  };
14809
15478
  }
14810
15479
  return {
14811
- snapshotPath: path65.join(baseDir, `${project}.json`),
14812
- errorsPath: path65.join(baseDir, `errors.${project}.ndjson`),
14813
- staleEventsPath: path65.join(baseDir, `stale-events.${project}.ndjson`),
14814
- embeddingsCachePath: path65.join(baseDir, `embeddings.${project}.json`),
14815
- policyViolationsPath: path65.join(baseDir, `policy-violations.${project}.ndjson`)
15480
+ snapshotPath: path67.join(baseDir, `${project}.json`),
15481
+ errorsPath: path67.join(baseDir, `errors.${project}.ndjson`),
15482
+ staleEventsPath: path67.join(baseDir, `stale-events.${project}.ndjson`),
15483
+ embeddingsCachePath: path67.join(baseDir, `embeddings.${project}.json`),
15484
+ policyViolationsPath: path67.join(baseDir, `policy-violations.${project}.ndjson`)
14816
15485
  };
14817
15486
  }
14818
15487
  var Projects = class {
@@ -14851,9 +15520,9 @@ function parseExtraProjects(raw) {
14851
15520
  }
14852
15521
 
14853
15522
  // src/registry.ts
14854
- import { promises as fs33 } from "fs";
15523
+ import { promises as fs34 } from "fs";
14855
15524
  import os2 from "os";
14856
- import path66 from "path";
15525
+ import path68 from "path";
14857
15526
  import {
14858
15527
  RegistryFileSchema
14859
15528
  } from "@neat.is/types";
@@ -14861,20 +15530,20 @@ var LOCK_TIMEOUT_MS = 5e3;
14861
15530
  var LOCK_RETRY_MS = 50;
14862
15531
  function neatHome() {
14863
15532
  const override = process.env.NEAT_HOME;
14864
- if (override && override.length > 0) return path66.resolve(override);
14865
- return path66.join(os2.homedir(), ".neat");
15533
+ if (override && override.length > 0) return path68.resolve(override);
15534
+ return path68.join(os2.homedir(), ".neat");
14866
15535
  }
14867
15536
  function registryPath() {
14868
- return path66.join(neatHome(), "projects.json");
15537
+ return path68.join(neatHome(), "projects.json");
14869
15538
  }
14870
15539
  function registryLockPath() {
14871
- return path66.join(neatHome(), "projects.json.lock");
15540
+ return path68.join(neatHome(), "projects.json.lock");
14872
15541
  }
14873
15542
  function daemonPidPath() {
14874
- return path66.join(neatHome(), "neatd.pid");
15543
+ return path68.join(neatHome(), "neatd.pid");
14875
15544
  }
14876
15545
  function daemonsDir() {
14877
- return path66.join(neatHome(), "daemons");
15546
+ return path68.join(neatHome(), "daemons");
14878
15547
  }
14879
15548
  function isFiniteInt(v) {
14880
15549
  return typeof v === "number" && Number.isFinite(v);
@@ -14907,7 +15576,7 @@ async function discoverDaemons(probe = defaultDiscoveryProbe) {
14907
15576
  const dir = daemonsDir();
14908
15577
  let names;
14909
15578
  try {
14910
- names = await fs33.readdir(dir);
15579
+ names = await fs34.readdir(dir);
14911
15580
  } catch (err) {
14912
15581
  if (err.code === "ENOENT") return [];
14913
15582
  throw err;
@@ -14915,10 +15584,10 @@ async function discoverDaemons(probe = defaultDiscoveryProbe) {
14915
15584
  const out = [];
14916
15585
  for (const name of names) {
14917
15586
  if (!name.endsWith(".json")) continue;
14918
- const file = path66.join(dir, name);
15587
+ const file = path68.join(dir, name);
14919
15588
  let raw;
14920
15589
  try {
14921
- raw = await fs33.readFile(file, "utf8");
15590
+ raw = await fs34.readFile(file, "utf8");
14922
15591
  } catch {
14923
15592
  continue;
14924
15593
  }
@@ -14931,7 +15600,7 @@ async function discoverDaemons(probe = defaultDiscoveryProbe) {
14931
15600
  return out;
14932
15601
  }
14933
15602
  async function removeDaemonRecord(source) {
14934
- await fs33.unlink(source).catch(() => {
15603
+ await fs34.unlink(source).catch(() => {
14935
15604
  });
14936
15605
  }
14937
15606
  async function listMachineProjects(probe = defaultDiscoveryProbe) {
@@ -14988,7 +15657,7 @@ function isPidAliveDefault(pid) {
14988
15657
  }
14989
15658
  async function readPidFile(file) {
14990
15659
  try {
14991
- const raw = await fs33.readFile(file, "utf8");
15660
+ const raw = await fs34.readFile(file, "utf8");
14992
15661
  const pid = Number.parseInt(raw.trim(), 10);
14993
15662
  return Number.isInteger(pid) && pid > 0 ? pid : void 0;
14994
15663
  } catch {
@@ -15036,32 +15705,32 @@ function lockHolderMessage(holder, lockPath, timeoutMs) {
15036
15705
  }
15037
15706
  }
15038
15707
  async function normalizeProjectPath(input) {
15039
- const resolved = path66.resolve(input);
15708
+ const resolved = path68.resolve(input);
15040
15709
  try {
15041
- return await fs33.realpath(resolved);
15710
+ return await fs34.realpath(resolved);
15042
15711
  } catch {
15043
15712
  return resolved;
15044
15713
  }
15045
15714
  }
15046
15715
  async function writeAtomically(target, contents) {
15047
- await fs33.mkdir(path66.dirname(target), { recursive: true });
15716
+ await fs34.mkdir(path68.dirname(target), { recursive: true });
15048
15717
  const tmp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
15049
- const fd = await fs33.open(tmp, "w");
15718
+ const fd = await fs34.open(tmp, "w");
15050
15719
  try {
15051
15720
  await fd.writeFile(contents, "utf8");
15052
15721
  await fd.sync();
15053
15722
  } finally {
15054
15723
  await fd.close();
15055
15724
  }
15056
- await fs33.rename(tmp, target);
15725
+ await fs34.rename(tmp, target);
15057
15726
  }
15058
15727
  async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaultLockHolderProbe) {
15059
15728
  const deadline = Date.now() + timeoutMs;
15060
- await fs33.mkdir(path66.dirname(lockPath), { recursive: true });
15729
+ await fs34.mkdir(path68.dirname(lockPath), { recursive: true });
15061
15730
  let probedHolder = false;
15062
15731
  while (true) {
15063
15732
  try {
15064
- const fd = await fs33.open(lockPath, "wx");
15733
+ const fd = await fs34.open(lockPath, "wx");
15065
15734
  try {
15066
15735
  await fd.writeFile(`${process.pid}
15067
15736
  `, "utf8");
@@ -15086,7 +15755,7 @@ async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaul
15086
15755
  }
15087
15756
  }
15088
15757
  async function releaseLock(lockPath) {
15089
- await fs33.unlink(lockPath).catch(() => {
15758
+ await fs34.unlink(lockPath).catch(() => {
15090
15759
  });
15091
15760
  }
15092
15761
  async function withLock(fn) {
@@ -15102,7 +15771,7 @@ async function readRegistry() {
15102
15771
  const file = registryPath();
15103
15772
  let raw;
15104
15773
  try {
15105
- raw = await fs33.readFile(file, "utf8");
15774
+ raw = await fs34.readFile(file, "utf8");
15106
15775
  } catch (err) {
15107
15776
  if (err.code === "ENOENT") {
15108
15777
  return { version: 1, projects: [] };
@@ -15207,7 +15876,7 @@ function pruneTtlMs() {
15207
15876
  }
15208
15877
  async function statPathStatus(p) {
15209
15878
  try {
15210
- const stat = await fs33.stat(p);
15879
+ const stat = await fs34.stat(p);
15211
15880
  return stat.isDirectory() ? "present" : "unknown";
15212
15881
  } catch (err) {
15213
15882
  return err.code === "ENOENT" ? "gone" : "unknown";
@@ -15252,14 +15921,14 @@ import cors from "@fastify/cors";
15252
15921
  import { DivergenceTypeSchema, PoliciesCheckBodySchema, PolicySeveritySchema } from "@neat.is/types";
15253
15922
 
15254
15923
  // src/extend/index.ts
15255
- import { promises as fs35 } from "fs";
15256
- import path68 from "path";
15924
+ import { promises as fs36 } from "fs";
15925
+ import path70 from "path";
15257
15926
  import os3 from "os";
15258
15927
  import { resolve as registryResolve, list as registryList } from "@neat.is/instrumentation-registry";
15259
15928
 
15260
15929
  // src/installers/package-manager.ts
15261
- import { promises as fs34 } from "fs";
15262
- import path67 from "path";
15930
+ import { promises as fs35 } from "fs";
15931
+ import path69 from "path";
15263
15932
  import { spawn } from "child_process";
15264
15933
  var LOCKFILE_PRIORITY = [
15265
15934
  { lockfile: "bun.lockb", pm: "bun", args: ["install", "--no-summary"] },
@@ -15274,29 +15943,29 @@ var LOCKFILE_PRIORITY = [
15274
15943
  var NPM_FALLBACK_ARGS = ["install", "--no-audit", "--no-fund", "--prefer-offline"];
15275
15944
  async function exists2(p) {
15276
15945
  try {
15277
- await fs34.access(p);
15946
+ await fs35.access(p);
15278
15947
  return true;
15279
15948
  } catch {
15280
15949
  return false;
15281
15950
  }
15282
15951
  }
15283
15952
  async function detectPackageManager(serviceDir) {
15284
- let dir = path67.resolve(serviceDir);
15953
+ let dir = path69.resolve(serviceDir);
15285
15954
  const stops = /* @__PURE__ */ new Set();
15286
15955
  for (let i = 0; i < 64; i++) {
15287
15956
  if (stops.has(dir)) break;
15288
15957
  stops.add(dir);
15289
15958
  for (const candidate of LOCKFILE_PRIORITY) {
15290
- const lockPath = path67.join(dir, candidate.lockfile);
15959
+ const lockPath = path69.join(dir, candidate.lockfile);
15291
15960
  if (await exists2(lockPath)) {
15292
15961
  return { pm: candidate.pm, cwd: dir, args: [...candidate.args] };
15293
15962
  }
15294
15963
  }
15295
- const parent = path67.dirname(dir);
15964
+ const parent = path69.dirname(dir);
15296
15965
  if (parent === dir) break;
15297
15966
  dir = parent;
15298
15967
  }
15299
- return { pm: "npm", cwd: path67.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
15968
+ return { pm: "npm", cwd: path69.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
15300
15969
  }
15301
15970
  async function runPackageManagerInstall(cmd) {
15302
15971
  return new Promise((resolve) => {
@@ -15338,15 +16007,15 @@ ${err.message}`
15338
16007
  // src/extend/index.ts
15339
16008
  async function fileExists2(p) {
15340
16009
  try {
15341
- await fs35.access(p);
16010
+ await fs36.access(p);
15342
16011
  return true;
15343
16012
  } catch {
15344
16013
  return false;
15345
16014
  }
15346
16015
  }
15347
16016
  async function readPackageJson(scanPath) {
15348
- const pkgPath = path68.join(scanPath, "package.json");
15349
- const raw = await fs35.readFile(pkgPath, "utf8");
16017
+ const pkgPath = path70.join(scanPath, "package.json");
16018
+ const raw = await fs36.readFile(pkgPath, "utf8");
15350
16019
  return JSON.parse(raw);
15351
16020
  }
15352
16021
  var HOOK_WALK_SKIP_DIRS = /* @__PURE__ */ new Set([
@@ -15359,27 +16028,27 @@ var HOOK_WALK_SKIP_DIRS = /* @__PURE__ */ new Set([
15359
16028
  ]);
15360
16029
  async function findHookFiles(scanPath) {
15361
16030
  const found = [];
15362
- const walk9 = async (dir) => {
15363
- const entries = await fs35.readdir(dir, { withFileTypes: true }).catch(() => []);
16031
+ const walk10 = async (dir) => {
16032
+ const entries = await fs36.readdir(dir, { withFileTypes: true }).catch(() => []);
15364
16033
  for (const entry of entries) {
15365
16034
  if (entry.isDirectory()) {
15366
16035
  if (entry.name.startsWith(".") || HOOK_WALK_SKIP_DIRS.has(entry.name)) continue;
15367
- await walk9(path68.join(dir, entry.name));
16036
+ await walk10(path70.join(dir, entry.name));
15368
16037
  } else if (entry.isFile()) {
15369
16038
  if ((entry.name.startsWith("instrumentation") || entry.name.startsWith("otel-init")) && /\.(ts|js|cjs|mjs)$/.test(entry.name)) {
15370
- const rel = path68.relative(scanPath, path68.join(dir, entry.name));
15371
- found.push(rel.split(path68.sep).join("/"));
16039
+ const rel = path70.relative(scanPath, path70.join(dir, entry.name));
16040
+ found.push(rel.split(path70.sep).join("/"));
15372
16041
  }
15373
16042
  }
15374
16043
  }
15375
16044
  };
15376
- await walk9(scanPath);
16045
+ await walk10(scanPath);
15377
16046
  return found.sort();
15378
16047
  }
15379
16048
  async function pickPrimaryHookFile(scanPath, hookFiles, snippet2) {
15380
16049
  let fallback = null;
15381
16050
  for (const file of hookFiles) {
15382
- const content = await fs35.readFile(path68.join(scanPath, file), "utf8");
16051
+ const content = await fs36.readFile(path70.join(scanPath, file), "utf8");
15383
16052
  const patched = splicedContent(content, snippet2);
15384
16053
  if (patched !== null) return { file, content, patched };
15385
16054
  if (fallback === null) fallback = { file, content };
@@ -15387,12 +16056,12 @@ async function pickPrimaryHookFile(scanPath, hookFiles, snippet2) {
15387
16056
  return { file: fallback.file, content: fallback.content, patched: null };
15388
16057
  }
15389
16058
  function extendLogPath() {
15390
- return process.env.NEAT_EXTEND_LOG ?? path68.join(os3.homedir(), ".neat", "extend-log.ndjson");
16059
+ return process.env.NEAT_EXTEND_LOG ?? path70.join(os3.homedir(), ".neat", "extend-log.ndjson");
15391
16060
  }
15392
16061
  async function appendExtendLog(entry) {
15393
16062
  const logPath = extendLogPath();
15394
- await fs35.mkdir(path68.dirname(logPath), { recursive: true });
15395
- await fs35.appendFile(logPath, JSON.stringify(entry) + "\n", "utf8");
16063
+ await fs36.mkdir(path70.dirname(logPath), { recursive: true });
16064
+ await fs36.appendFile(logPath, JSON.stringify(entry) + "\n", "utf8");
15396
16065
  }
15397
16066
  function splicedContent(fileContent, snippet2) {
15398
16067
  if (fileContent.includes("__INSTRUMENTATION_BLOCK__")) {
@@ -15450,7 +16119,7 @@ function lookupInstrumentation(library, installedVersion) {
15450
16119
  }
15451
16120
  async function describeProjectInstrumentation(ctx) {
15452
16121
  const hookFiles = await findHookFiles(ctx.scanPath);
15453
- const envNeat = await fileExists2(path68.join(ctx.scanPath, ".env.neat"));
16122
+ const envNeat = await fileExists2(path70.join(ctx.scanPath, ".env.neat"));
15454
16123
  const registryInstrPackages = new Set(
15455
16124
  registryList().map((e) => e.instrumentation_package).filter((p) => !!p)
15456
16125
  );
@@ -15472,7 +16141,7 @@ async function applyExtension(ctx, args, options) {
15472
16141
  );
15473
16142
  }
15474
16143
  for (const file of hookFiles) {
15475
- const content = await fs35.readFile(path68.join(ctx.scanPath, file), "utf8");
16144
+ const content = await fs36.readFile(path70.join(ctx.scanPath, file), "utf8");
15476
16145
  if (content.includes(args.registration_snippet)) {
15477
16146
  return { library: args.library, filesTouched: [], depsAdded: [], installOutput: "", alreadyApplied: true };
15478
16147
  }
@@ -15484,18 +16153,18 @@ async function applyExtension(ctx, args, options) {
15484
16153
  );
15485
16154
  }
15486
16155
  const primaryFile = primary.file;
15487
- const primaryPath = path68.join(ctx.scanPath, primaryFile);
16156
+ const primaryPath = path70.join(ctx.scanPath, primaryFile);
15488
16157
  const filesTouched = [];
15489
16158
  const depsAdded = [];
15490
- const pkgPath = path68.join(ctx.scanPath, "package.json");
16159
+ const pkgPath = path70.join(ctx.scanPath, "package.json");
15491
16160
  const pkg = await readPackageJson(ctx.scanPath);
15492
16161
  if (!(pkg.dependencies ?? {})[args.instrumentation_package]) {
15493
16162
  pkg.dependencies = { ...pkg.dependencies ?? {}, [args.instrumentation_package]: args.version };
15494
- await fs35.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
16163
+ await fs36.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
15495
16164
  filesTouched.push("package.json");
15496
16165
  depsAdded.push(`${args.instrumentation_package}@${args.version}`);
15497
16166
  }
15498
- await fs35.writeFile(primaryPath, primary.patched, "utf8");
16167
+ await fs36.writeFile(primaryPath, primary.patched, "utf8");
15499
16168
  filesTouched.push(primaryFile);
15500
16169
  const cmd = await detectPackageManager(ctx.scanPath);
15501
16170
  const installer = options?.runInstall ?? runPackageManagerInstall;
@@ -15526,7 +16195,7 @@ async function dryRunExtension(ctx, args) {
15526
16195
  };
15527
16196
  }
15528
16197
  for (const file of hookFiles) {
15529
- const content = await fs35.readFile(path68.join(ctx.scanPath, file), "utf8");
16198
+ const content = await fs36.readFile(path70.join(ctx.scanPath, file), "utf8");
15530
16199
  if (content.includes(args.registration_snippet)) {
15531
16200
  return {
15532
16201
  library: args.library,
@@ -15561,28 +16230,28 @@ async function rollbackExtension(ctx, args) {
15561
16230
  if (!await fileExists2(logPath)) {
15562
16231
  return { undone: false, message: "no apply found for library" };
15563
16232
  }
15564
- const raw = await fs35.readFile(logPath, "utf8");
16233
+ const raw = await fs36.readFile(logPath, "utf8");
15565
16234
  const entries = raw.trim().split("\n").filter(Boolean).map((line) => JSON.parse(line));
15566
16235
  const match = [...entries].reverse().find((e) => e.project === ctx.project && e.library === args.library);
15567
16236
  if (!match) {
15568
16237
  return { undone: false, message: "no apply found for library" };
15569
16238
  }
15570
- const pkgPath = path68.join(ctx.scanPath, "package.json");
16239
+ const pkgPath = path70.join(ctx.scanPath, "package.json");
15571
16240
  if (await fileExists2(pkgPath)) {
15572
16241
  const pkg = await readPackageJson(ctx.scanPath);
15573
16242
  if (pkg.dependencies?.[match.instrumentation_package]) {
15574
16243
  const { [match.instrumentation_package]: _removed, ...rest } = pkg.dependencies;
15575
16244
  pkg.dependencies = rest;
15576
- await fs35.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
16245
+ await fs36.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
15577
16246
  }
15578
16247
  }
15579
16248
  const hookFiles = await findHookFiles(ctx.scanPath);
15580
16249
  for (const file of hookFiles) {
15581
- const filePath = path68.join(ctx.scanPath, file);
15582
- const content = await fs35.readFile(filePath, "utf8");
16250
+ const filePath = path70.join(ctx.scanPath, file);
16251
+ const content = await fs36.readFile(filePath, "utf8");
15583
16252
  if (content.includes(match.registration_snippet)) {
15584
16253
  const filtered = content.split("\n").filter((line) => !line.includes(match.registration_snippet)).join("\n");
15585
- await fs35.writeFile(filePath, filtered, "utf8");
16254
+ await fs36.writeFile(filePath, filtered, "utf8");
15586
16255
  break;
15587
16256
  }
15588
16257
  }
@@ -15693,8 +16362,8 @@ data: ${JSON.stringify(envelope.payload)}
15693
16362
 
15694
16363
  // src/connectors-config.ts
15695
16364
  import os4 from "os";
15696
- import path69 from "path";
15697
- import { promises as fs36 } from "fs";
16365
+ import path71 from "path";
16366
+ import { promises as fs37 } from "fs";
15698
16367
  var CONNECTORS_CONFIG_VERSION = 1;
15699
16368
  var EnvRefUnsetError = class extends Error {
15700
16369
  ref;
@@ -15708,17 +16377,17 @@ var EnvRefUnsetError = class extends Error {
15708
16377
  };
15709
16378
  function neatHome2() {
15710
16379
  const override = process.env.NEAT_HOME;
15711
- if (override && override.length > 0) return path69.resolve(override);
15712
- return path69.join(os4.homedir(), ".neat");
16380
+ if (override && override.length > 0) return path71.resolve(override);
16381
+ return path71.join(os4.homedir(), ".neat");
15713
16382
  }
15714
16383
  function connectorsConfigPath(home = neatHome2()) {
15715
- return path69.join(home, "connectors.json");
16384
+ return path71.join(home, "connectors.json");
15716
16385
  }
15717
16386
  var MODE_MASK_LOOSER_THAN_0600 = 63;
15718
16387
  async function warnIfModeLooserThan0600(file) {
15719
16388
  if (process.platform === "win32") return;
15720
16389
  try {
15721
- const stat = await fs36.stat(file);
16390
+ const stat = await fs37.stat(file);
15722
16391
  if ((stat.mode & MODE_MASK_LOOSER_THAN_0600) !== 0) {
15723
16392
  const mode = (stat.mode & 511).toString(8).padStart(3, "0");
15724
16393
  console.warn(
@@ -15732,7 +16401,7 @@ async function readConnectorsConfig(home = neatHome2()) {
15732
16401
  const file = connectorsConfigPath(home);
15733
16402
  let raw;
15734
16403
  try {
15735
- raw = await fs36.readFile(file, "utf8");
16404
+ raw = await fs37.readFile(file, "utf8");
15736
16405
  } catch (err) {
15737
16406
  if (err.code === "ENOENT") {
15738
16407
  return { version: CONNECTORS_CONFIG_VERSION, connectors: [] };
@@ -15843,7 +16512,7 @@ function connectorMatchesProject(entry, project) {
15843
16512
  var CONNECTORS_LOCK_TIMEOUT_MS = 5e3;
15844
16513
  var CONNECTORS_LOCK_RETRY_MS = 50;
15845
16514
  function connectorsConfigLockPath(home = neatHome2()) {
15846
- return path69.join(home, "connectors.json.lock");
16515
+ return path71.join(home, "connectors.json.lock");
15847
16516
  }
15848
16517
  function isEnvRef(value) {
15849
16518
  return value.length > 1 && value.startsWith("$");
@@ -15856,9 +16525,9 @@ function redactCredentialRef(ref) {
15856
16525
  return out;
15857
16526
  }
15858
16527
  async function writeConfigAtomically0600(file, contents) {
15859
- await fs36.mkdir(path69.dirname(file), { recursive: true });
16528
+ await fs37.mkdir(path71.dirname(file), { recursive: true });
15860
16529
  const tmp = `${file}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
15861
- const fd = await fs36.open(tmp, "w", 384);
16530
+ const fd = await fs37.open(tmp, "w", 384);
15862
16531
  try {
15863
16532
  await fd.writeFile(contents, "utf8");
15864
16533
  await fd.chmod(384);
@@ -15866,14 +16535,14 @@ async function writeConfigAtomically0600(file, contents) {
15866
16535
  } finally {
15867
16536
  await fd.close();
15868
16537
  }
15869
- await fs36.rename(tmp, file);
16538
+ await fs37.rename(tmp, file);
15870
16539
  }
15871
16540
  async function acquireConnectorsLock(lockPath, timeoutMs = CONNECTORS_LOCK_TIMEOUT_MS) {
15872
16541
  const deadline = Date.now() + timeoutMs;
15873
- await fs36.mkdir(path69.dirname(lockPath), { recursive: true });
16542
+ await fs37.mkdir(path71.dirname(lockPath), { recursive: true });
15874
16543
  for (; ; ) {
15875
16544
  try {
15876
- const fd = await fs36.open(lockPath, "wx");
16545
+ const fd = await fs37.open(lockPath, "wx");
15877
16546
  try {
15878
16547
  await fd.writeFile(`${process.pid}
15879
16548
  `, "utf8");
@@ -15893,7 +16562,7 @@ async function acquireConnectorsLock(lockPath, timeoutMs = CONNECTORS_LOCK_TIMEO
15893
16562
  }
15894
16563
  }
15895
16564
  async function releaseConnectorsLock(lockPath) {
15896
- await fs36.unlink(lockPath).catch(() => {
16565
+ await fs37.unlink(lockPath).catch(() => {
15897
16566
  });
15898
16567
  }
15899
16568
  async function withConnectorsLock(home, fn) {
@@ -16522,10 +17191,10 @@ var SUPABASE_RPC_TARGET_KIND = "supabase-rpc";
16522
17191
  // src/connectors/supabase/map.ts
16523
17192
  var REST_RPC_PATH_RE = /^\/rest\/v1\/rpc\/([^/?]+)/;
16524
17193
  var REST_TABLE_PATH_RE = /^\/rest\/v1\/([^/?]+)/;
16525
- function targetFromRestPath(path70) {
16526
- const rpcMatch = REST_RPC_PATH_RE.exec(path70);
17194
+ function targetFromRestPath(path72) {
17195
+ const rpcMatch = REST_RPC_PATH_RE.exec(path72);
16527
17196
  if (rpcMatch) return { targetKind: SUPABASE_RPC_TARGET_KIND, name: rpcMatch[1] };
16528
- const tableMatch = REST_TABLE_PATH_RE.exec(path70);
17197
+ const tableMatch = REST_TABLE_PATH_RE.exec(path72);
16529
17198
  if (tableMatch) return { targetKind: SUPABASE_TABLE_TARGET_KIND, name: tableMatch[1] };
16530
17199
  return null;
16531
17200
  }
@@ -16634,21 +17303,21 @@ async function fetchPgStatStatements(connectionString, limit = DEFAULT_STATEMENT
16634
17303
  }
16635
17304
 
16636
17305
  // src/connectors/supabase/resolve.ts
16637
- import { EdgeType as EdgeType26, infraId as infraId20 } from "@neat.is/types";
17306
+ import { EdgeType as EdgeType26, infraId as infraId21 } from "@neat.is/types";
16638
17307
  function createSupabaseResolveTarget(graph, config) {
16639
17308
  return (signal, _ctx) => {
16640
17309
  if (signal.targetKind !== SUPABASE_TABLE_TARGET_KIND && signal.targetKind !== SUPABASE_RPC_TARGET_KIND) {
16641
17310
  return null;
16642
17311
  }
16643
- const subResourceId = infraId20(signal.targetKind, `${config.nodeRef}/${signal.targetName}`);
17312
+ const subResourceId = infraId21(signal.targetKind, `${config.nodeRef}/${signal.targetName}`);
16644
17313
  if (graph.hasNode(subResourceId)) {
16645
17314
  return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType: EdgeType26.CALLS };
16646
17315
  }
16647
- const bareResourceId = infraId20(signal.targetKind, signal.targetName);
17316
+ const bareResourceId = infraId21(signal.targetKind, signal.targetName);
16648
17317
  if (graph.hasNode(bareResourceId)) {
16649
17318
  return { targetNodeId: bareResourceId, serviceName: config.serviceName, edgeType: EdgeType26.CALLS };
16650
17319
  }
16651
- const projectLevelId = infraId20("supabase", config.nodeRef);
17320
+ const projectLevelId = infraId21("supabase", config.nodeRef);
16652
17321
  if (graph.hasNode(projectLevelId)) {
16653
17322
  return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType: EdgeType26.CALLS };
16654
17323
  }
@@ -17120,9 +17789,9 @@ function parseFirebaseTargetName(targetName) {
17120
17789
  const secondSep = rest.indexOf(FIELD_SEP);
17121
17790
  if (secondSep === -1) return null;
17122
17791
  const method = rest.slice(0, secondSep);
17123
- const path70 = rest.slice(secondSep + 1);
17124
- if (!resourceName || !method || !path70) return null;
17125
- return { resourceName, method, path: path70 };
17792
+ const path72 = rest.slice(secondSep + 1);
17793
+ if (!resourceName || !method || !path72) return null;
17794
+ return { resourceName, method, path: path72 };
17126
17795
  }
17127
17796
  function resourceNameFor(type, labels) {
17128
17797
  if (!labels) return null;
@@ -17160,14 +17829,14 @@ function mapLogEntryToSignal(entry) {
17160
17829
  if (!req) return null;
17161
17830
  if (typeof req.requestMethod !== "string" || req.requestMethod.length === 0) return null;
17162
17831
  const method = req.requestMethod.toUpperCase();
17163
- const path70 = pathFromRequestUrl(req.requestUrl);
17164
- if (path70 === null) return null;
17832
+ const path72 = pathFromRequestUrl(req.requestUrl);
17833
+ if (path72 === null) return null;
17165
17834
  const timestamp = entry.timestamp;
17166
17835
  if (typeof timestamp !== "string" || timestamp.length === 0) return null;
17167
17836
  const isError = typeof req.status === "number" && req.status >= ERROR_STATUS_THRESHOLD2;
17168
17837
  return {
17169
17838
  targetKind: resourceType,
17170
- targetName: packFirebaseTargetName({ resourceName, method, path: path70 }),
17839
+ targetName: packFirebaseTargetName({ resourceName, method, path: path72 }),
17171
17840
  callCount: 1,
17172
17841
  errorCount: isError ? 1 : 0,
17173
17842
  lastObservedIso: timestamp
@@ -17253,7 +17922,7 @@ function createFirebaseConnector(graph, serviceMap) {
17253
17922
  }
17254
17923
 
17255
17924
  // src/connectors/cloudflare/connector.ts
17256
- import { EdgeType as EdgeType29, NodeType as NodeType35, fileId as fileId5, infraId as infraId21 } from "@neat.is/types";
17925
+ import { EdgeType as EdgeType29, NodeType as NodeType35, fileId as fileId5, infraId as infraId22 } from "@neat.is/types";
17257
17926
 
17258
17927
  // src/connectors/cloudflare/client.ts
17259
17928
  import { randomUUID } from "crypto";
@@ -17364,7 +18033,7 @@ function mapEventToSignal(event) {
17364
18033
  if (Number.isNaN(observedAt.getTime())) return null;
17365
18034
  const statusCode = metadata?.statusCode;
17366
18035
  const isError = typeof statusCode === "number" && statusCode >= ERROR_STATUS_THRESHOLD3;
17367
- const path70 = metadata?.trigger ? parsePathFromTrigger(metadata.trigger) : void 0;
18036
+ const path72 = metadata?.trigger ? parsePathFromTrigger(metadata.trigger) : void 0;
17368
18037
  return {
17369
18038
  targetKind: CLOUDFLARE_TARGET_KIND,
17370
18039
  targetName: scriptName,
@@ -17372,7 +18041,7 @@ function mapEventToSignal(event) {
17372
18041
  errorCount: isError ? 1 : 0,
17373
18042
  lastObservedIso: observedAt.toISOString(),
17374
18043
  method,
17375
- ...path70 ? { path: path70 } : {},
18044
+ ...path72 ? { path: path72 } : {},
17376
18045
  ...typeof statusCode === "number" ? { statusCode } : {},
17377
18046
  ...typeof metadata?.duration === "number" ? { duration: metadata.duration } : {}
17378
18047
  };
@@ -17418,8 +18087,8 @@ function findTaggedWorkerFileNode(graph, workerName) {
17418
18087
  });
17419
18088
  return found;
17420
18089
  }
17421
- function findMatchingRouteNode(graph, serviceName, method, path70) {
17422
- const normalizedPath = normalizePathTemplate(path70);
18090
+ function findMatchingRouteNode(graph, serviceName, method, path72) {
18091
+ const normalizedPath = normalizePathTemplate(path72);
17423
18092
  let found = null;
17424
18093
  graph.forEachNode((id, attrs) => {
17425
18094
  if (found) return;
@@ -17436,10 +18105,10 @@ function createCloudflareResolveTarget(config, graph) {
17436
18105
  return (signal) => {
17437
18106
  if (signal.targetKind !== CLOUDFLARE_TARGET_KIND) return null;
17438
18107
  const scriptName = signal.targetName;
17439
- const { method, path: path70 } = signal;
18108
+ const { method, path: path72 } = signal;
17440
18109
  const resolveRouteGrain = (serviceName, wholeFileId) => {
17441
- if (!method || !path70) return wholeFileId;
17442
- return findMatchingRouteNode(graph, serviceName, method, path70) ?? wholeFileId;
18110
+ if (!method || !path72) return wholeFileId;
18111
+ return findMatchingRouteNode(graph, serviceName, method, path72) ?? wholeFileId;
17443
18112
  };
17444
18113
  const mapping = config.workers?.[scriptName];
17445
18114
  if (mapping) {
@@ -17460,7 +18129,7 @@ function createCloudflareResolveTarget(config, graph) {
17460
18129
  };
17461
18130
  }
17462
18131
  return {
17463
- targetNodeId: infraId21("cloudflare-worker", scriptName),
18132
+ targetNodeId: infraId22("cloudflare-worker", scriptName),
17464
18133
  serviceName: scriptName,
17465
18134
  edgeType: EdgeType29.CALLS,
17466
18135
  ensureInfraNode: { kind: "cloudflare-worker", name: scriptName, provider: "cloudflare" }
@@ -17644,12 +18313,12 @@ function diffNeonStatementsToSignals(rows, previous, observedAtIso) {
17644
18313
  }
17645
18314
 
17646
18315
  // src/connectors/neon/resolve.ts
17647
- import { EdgeType as EdgeType30, infraId as infraId22 } from "@neat.is/types";
18316
+ import { EdgeType as EdgeType30, infraId as infraId23 } from "@neat.is/types";
17648
18317
  function createNeonResolveTarget(config) {
17649
18318
  return (signal) => {
17650
18319
  if (signal.targetKind !== NEON_SQL_TABLE_TARGET_KIND || !signal.targetName) return null;
17651
18320
  return {
17652
- targetNodeId: infraId22("sql-table", signal.targetName),
18321
+ targetNodeId: infraId23("sql-table", signal.targetName),
17653
18322
  serviceName: config.serviceName,
17654
18323
  edgeType: EdgeType30.CALLS,
17655
18324
  ensureInfraNode: { kind: "sql-table", name: signal.targetName, provider: "neon" }
@@ -17769,9 +18438,9 @@ function parseCloudRunTargetName(targetName) {
17769
18438
  const secondSep = rest.indexOf(FIELD_SEP2);
17770
18439
  if (secondSep === -1) return null;
17771
18440
  const method = rest.slice(0, secondSep);
17772
- const path70 = rest.slice(secondSep + 1);
17773
- if (!serviceName || !method || !path70) return null;
17774
- return { serviceName, method, path: path70 };
18441
+ const path72 = rest.slice(secondSep + 1);
18442
+ if (!serviceName || !method || !path72) return null;
18443
+ return { serviceName, method, path: path72 };
17775
18444
  }
17776
18445
 
17777
18446
  // src/connectors/cloud-run/map.ts
@@ -17800,14 +18469,14 @@ function mapLogEntryToSignal2(entry) {
17800
18469
  if (!req) return null;
17801
18470
  if (typeof req.requestMethod !== "string" || req.requestMethod.length === 0) return null;
17802
18471
  const method = req.requestMethod.toUpperCase();
17803
- const path70 = pathFromRequestUrl2(req.requestUrl);
17804
- if (path70 === null) return null;
18472
+ const path72 = pathFromRequestUrl2(req.requestUrl);
18473
+ if (path72 === null) return null;
17805
18474
  const timestamp = entry.timestamp;
17806
18475
  if (typeof timestamp !== "string" || timestamp.length === 0) return null;
17807
18476
  const isError = typeof req.status === "number" && req.status >= ERROR_STATUS_THRESHOLD4;
17808
18477
  return {
17809
18478
  targetKind: CLOUD_RUN_TARGET_KIND,
17810
- targetName: packCloudRunTargetName({ serviceName, method, path: path70 }),
18479
+ targetName: packCloudRunTargetName({ serviceName, method, path: path72 }),
17811
18480
  callCount: 1,
17812
18481
  errorCount: isError ? 1 : 0,
17813
18482
  lastObservedIso: timestamp
@@ -17823,7 +18492,7 @@ function mapLogEntriesToSignals2(entries) {
17823
18492
  }
17824
18493
 
17825
18494
  // src/connectors/cloud-run/resolve.ts
17826
- import { EdgeType as EdgeType31, NodeType as NodeType36, infraId as infraId23 } from "@neat.is/types";
18495
+ import { EdgeType as EdgeType31, NodeType as NodeType36, infraId as infraId24 } from "@neat.is/types";
17827
18496
  var CLOUD_RUN_SERVICE_INFRA_KIND = "cloud-run-service";
17828
18497
  function findMatchingRouteNode2(graph, serviceName, method, normalizedPath) {
17829
18498
  let found = null;
@@ -17845,21 +18514,21 @@ function createCloudRunResolveTarget(graph, config) {
17845
18514
  if (signal.targetKind !== CLOUD_RUN_TARGET_KIND) return null;
17846
18515
  const identity = parseCloudRunTargetName(signal.targetName);
17847
18516
  if (!identity) return null;
17848
- const { serviceName: gcpServiceName, method, path: path70 } = identity;
18517
+ const { serviceName: gcpServiceName, method, path: path72 } = identity;
17849
18518
  const mappedService = config.serviceMap?.[gcpServiceName];
17850
18519
  if (mappedService) {
17851
18520
  const routeNodeId = findMatchingRouteNode2(
17852
18521
  graph,
17853
18522
  mappedService,
17854
18523
  method,
17855
- normalizePathTemplate(path70)
18524
+ normalizePathTemplate(path72)
17856
18525
  );
17857
18526
  if (routeNodeId) {
17858
18527
  return { targetNodeId: routeNodeId, serviceName: mappedService, edgeType: EdgeType31.CALLS };
17859
18528
  }
17860
18529
  }
17861
18530
  return {
17862
- targetNodeId: infraId23(CLOUD_RUN_SERVICE_INFRA_KIND, gcpServiceName),
18531
+ targetNodeId: infraId24(CLOUD_RUN_SERVICE_INFRA_KIND, gcpServiceName),
17863
18532
  serviceName: mappedService ?? gcpServiceName,
17864
18533
  edgeType: EdgeType31.CALLS,
17865
18534
  ensureInfraNode: {
@@ -18191,17 +18860,17 @@ function mapInsightsToSignals(rows, observedAtIso) {
18191
18860
  }
18192
18861
 
18193
18862
  // src/connectors/planetscale/resolve.ts
18194
- import { EdgeType as EdgeType33, infraId as infraId24 } from "@neat.is/types";
18863
+ import { EdgeType as EdgeType33, infraId as infraId25 } from "@neat.is/types";
18195
18864
  var PLANETSCALE_DATABASE_KIND = "planetscale-database";
18196
18865
  function createPlanetscaleResolveTarget(graph, config) {
18197
18866
  const databaseName = `${config.organization}/${config.database}`;
18198
18867
  return (signal, _ctx) => {
18199
18868
  if (signal.targetKind !== PLANETSCALE_SQL_TABLE_TARGET_KIND || !signal.targetName) return null;
18200
- const tableId = infraId24("sql-table", signal.targetName);
18869
+ const tableId = infraId25("sql-table", signal.targetName);
18201
18870
  if (graph.hasNode(tableId)) {
18202
18871
  return { targetNodeId: tableId, serviceName: config.serviceName, edgeType: EdgeType33.CALLS };
18203
18872
  }
18204
- const providerId = infraId24(PLANETSCALE_DATABASE_KIND, databaseName);
18873
+ const providerId = infraId25(PLANETSCALE_DATABASE_KIND, databaseName);
18205
18874
  return {
18206
18875
  targetNodeId: providerId,
18207
18876
  serviceName: config.serviceName,
@@ -20101,4 +20770,4 @@ export {
20101
20770
  deprovisionConnector,
20102
20771
  buildApi
20103
20772
  };
20104
- //# sourceMappingURL=chunk-JUVJBH23.js.map
20773
+ //# sourceMappingURL=chunk-UN7VFA4H.js.map