@neat.is/core 0.9.0 → 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);
5558
+ edges.push(hop.edge);
5559
+ visited.add(hop.nextService);
5560
+ current = hop.nextService;
5561
+ }
5562
+ if (edges.length === 0) return null;
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);
5242
5607
  edges.push(hop.edge);
5243
5608
  visited.add(hop.nextService);
5244
5609
  current = hop.nextService;
5245
5610
  }
5246
5611
  if (edges.length === 0) return null;
5247
- return { path: path70, edges, culprit: current };
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,10 +10055,15 @@ 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;
10062
+ var SARAMA_IMPORT_RE = /"[^"]*\/sarama"/;
10063
+ var GO_PRODUCER_RE = /ProducerMessage\s*\{[\s\S]{0,300}?\bTopic\s*:\s*(?:"([^"]+)"|`([^`]+)`|([A-Za-z_]\w*))/g;
10064
+ var GO_CONSUMER_RE = /\.Consume\s*\([\s\S]{0,120}?\[\]string\s*\{([^}]*)\}/g;
10065
+ var GO_STRING_LITERAL_RE = /"([^"]+)"|`([^`]+)`/g;
10066
+ var GO_IDENT_RE = /[A-Za-z_]\w*/g;
9513
10067
  function findAll(re, text) {
9514
10068
  re.lastIndex = 0;
9515
10069
  const out = [];
@@ -9519,37 +10073,93 @@ function findAll(re, text) {
9519
10073
  }
9520
10074
  return out;
9521
10075
  }
10076
+ function resolveGoLiteral(ident, text) {
10077
+ const esc = ident.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
10078
+ const re = new RegExp(
10079
+ `\\b${esc}(?:\\s+\\w[\\w.\\[\\]*]*)?\\s*(?::=|=)\\s*(?:"([^"]+)"|\`([^\`]+)\`)`
10080
+ );
10081
+ const m = re.exec(text);
10082
+ if (!m) return void 0;
10083
+ return m[1] ?? m[2];
10084
+ }
10085
+ function producerTopicAnchor(match) {
10086
+ const [full] = match;
10087
+ const tokenLen = match[1] ? match[1].length + 2 : match[2] ? match[2].length + 2 : match[3]?.length ?? 0;
10088
+ return match.index + full.length - tokenLen;
10089
+ }
10090
+ function goSaramaEndpoints(text, emit) {
10091
+ if (!SARAMA_IMPORT_RE.test(text)) return;
10092
+ GO_PRODUCER_RE.lastIndex = 0;
10093
+ let m;
10094
+ while ((m = GO_PRODUCER_RE.exec(text)) !== null) {
10095
+ const literal = m[1] ?? m[2];
10096
+ const anchor = producerTopicAnchor(m);
10097
+ if (literal) {
10098
+ emit(literal, "PUBLISHES_TO", anchor);
10099
+ } else if (m[3]) {
10100
+ const resolved = resolveGoLiteral(m[3], text);
10101
+ if (resolved) emit(resolved, "PUBLISHES_TO", anchor);
10102
+ }
10103
+ }
10104
+ GO_CONSUMER_RE.lastIndex = 0;
10105
+ while ((m = GO_CONSUMER_RE.exec(text)) !== null) {
10106
+ const inside = m[1] ?? "";
10107
+ const anchor = m.index + Math.max(0, m[0].indexOf("[]string"));
10108
+ let sawLiteral = false;
10109
+ GO_STRING_LITERAL_RE.lastIndex = 0;
10110
+ let s;
10111
+ while ((s = GO_STRING_LITERAL_RE.exec(inside)) !== null) {
10112
+ const topic = s[1] ?? s[2];
10113
+ if (topic) {
10114
+ emit(topic, "CONSUMES_FROM", anchor);
10115
+ sawLiteral = true;
10116
+ }
10117
+ }
10118
+ if (sawLiteral) continue;
10119
+ GO_IDENT_RE.lastIndex = 0;
10120
+ let id;
10121
+ while ((id = GO_IDENT_RE.exec(inside)) !== null) {
10122
+ const resolved = resolveGoLiteral(id[0], text);
10123
+ if (resolved) emit(resolved, "CONSUMES_FROM", anchor);
10124
+ }
10125
+ }
10126
+ }
9522
10127
  function kafkaEndpointsFromFile(file, serviceDir) {
9523
10128
  const out = [];
9524
10129
  const seen = /* @__PURE__ */ new Set();
9525
- const make = (topic, edgeType) => {
10130
+ const make = (topic, edgeType, anchor) => {
9526
10131
  const key = `${edgeType}|${topic}`;
9527
10132
  if (seen.has(key)) return;
9528
10133
  seen.add(key);
9529
- const line = lineOf(file.content, topic);
10134
+ const line = anchor !== void 0 ? file.content.slice(0, anchor).split("\n").length : lineOf(file.content, topic);
9530
10135
  out.push({
9531
10136
  infraId: infraId2("kafka-topic", topic),
9532
10137
  name: topic,
9533
10138
  kind: "kafka-topic",
9534
10139
  edgeType,
9535
- // `producer.send({topic: 'x'})` / `consumer.subscribe({topic: 'x'})`
9536
- // framework-aware (kafkajs / node-rdkafka shape). Verified-call-site
9537
- // tier (ADR-066).
10140
+ // JS: `producer.send({topic: 'x'})` / `consumer.subscribe({topic: 'x'})`
10141
+ // (kafkajs / node-rdkafka). Go: `sarama.ProducerMessage{Topic: 'x'}` /
10142
+ // `consumerGroup.Consume(ctx, []string{'x'}, …)`. Both are framework-aware
10143
+ // call sites — verified-call-site tier (ADR-066).
9538
10144
  confidenceKind: "verified-call-site",
9539
10145
  evidence: {
9540
- file: path38.relative(serviceDir, file.path),
10146
+ file: path39.relative(serviceDir, file.path),
9541
10147
  line,
9542
10148
  snippet: snippet(file.content, line)
9543
10149
  }
9544
10150
  });
9545
10151
  };
9546
- for (const { topic } of findAll(PRODUCER_TOPIC_RE, file.content)) make(topic, "PUBLISHES_TO");
9547
- for (const { topic } of findAll(CONSUMER_TOPIC_RE, file.content)) make(topic, "CONSUMES_FROM");
10152
+ if (path39.extname(file.path) === ".go") {
10153
+ goSaramaEndpoints(file.content, make);
10154
+ } else {
10155
+ for (const { topic } of findAll(PRODUCER_TOPIC_RE, file.content)) make(topic, "PUBLISHES_TO");
10156
+ for (const { topic } of findAll(CONSUMER_TOPIC_RE, file.content)) make(topic, "CONSUMES_FROM");
10157
+ }
9548
10158
  return out;
9549
10159
  }
9550
10160
 
9551
10161
  // src/extract/calls/redis.ts
9552
- import path39 from "path";
10162
+ import path40 from "path";
9553
10163
  import { infraId as infraId3 } from "@neat.is/types";
9554
10164
  var REDIS_URL_RE = /redis(?:s)?:\/\/(?:[^@'"`\s]+@)?([^:/'"`\s]+)(?::(\d+))?/g;
9555
10165
  function redisEndpointsFromFile(file, serviceDir) {
@@ -9572,7 +10182,7 @@ function redisEndpointsFromFile(file, serviceDir) {
9572
10182
  // support tier (ADR-066).
9573
10183
  confidenceKind: "url-with-structural-support",
9574
10184
  evidence: {
9575
- file: path39.relative(serviceDir, file.path),
10185
+ file: path40.relative(serviceDir, file.path),
9576
10186
  line,
9577
10187
  snippet: snippet(file.content, line)
9578
10188
  }
@@ -9582,7 +10192,7 @@ function redisEndpointsFromFile(file, serviceDir) {
9582
10192
  }
9583
10193
 
9584
10194
  // src/extract/calls/aws.ts
9585
- import path40 from "path";
10195
+ import path41 from "path";
9586
10196
  import { infraId as infraId4 } from "@neat.is/types";
9587
10197
  var S3_BUCKET_RE = /Bucket\s*:\s*['"`]([^'"`]+)['"`]/g;
9588
10198
  var DYNAMO_TABLE_RE = /TableName\s*:\s*['"`]([^'"`]+)['"`]/g;
@@ -9616,7 +10226,7 @@ function awsEndpointsFromFile(file, serviceDir) {
9616
10226
  // (ADR-066).
9617
10227
  confidenceKind: "verified-call-site",
9618
10228
  evidence: {
9619
- file: path40.relative(serviceDir, file.path),
10229
+ file: path41.relative(serviceDir, file.path),
9620
10230
  line,
9621
10231
  snippet: snippet(file.content, line)
9622
10232
  }
@@ -9640,7 +10250,7 @@ function awsEndpointsFromFile(file, serviceDir) {
9640
10250
  }
9641
10251
 
9642
10252
  // src/extract/calls/grpc.ts
9643
- import path41 from "path";
10253
+ import path42 from "path";
9644
10254
  import { infraId as infraId5 } from "@neat.is/types";
9645
10255
  var GRPC_CLIENT_RE = /new\s+([A-Z][A-Za-z0-9_]*)Client\s*\(\s*['"`]?([^,'"`)]+)?/g;
9646
10256
  var AWS_SDK_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@aws-sdk\/client-([a-z0-9-]+)['"`]/g;
@@ -9699,7 +10309,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
9699
10309
  // tier (ADR-066).
9700
10310
  confidenceKind: "verified-call-site",
9701
10311
  evidence: {
9702
- file: path41.relative(serviceDir, file.path),
10312
+ file: path42.relative(serviceDir, file.path),
9703
10313
  line,
9704
10314
  snippet: snippet(file.content, line)
9705
10315
  }
@@ -9709,7 +10319,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
9709
10319
  }
9710
10320
 
9711
10321
  // src/extract/calls/supabase.ts
9712
- import path42 from "path";
10322
+ import path43 from "path";
9713
10323
  import { infraId as infraId6 } from "@neat.is/types";
9714
10324
  var SUPABASE_JS_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@supabase\/supabase-js['"`]/;
9715
10325
  var SUPABASE_SSR_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@supabase\/ssr['"`]/;
@@ -9768,7 +10378,7 @@ function supabaseEndpointsFromFile(file, serviceDir) {
9768
10378
  // tier (ADR-066), the same grade aws.ts / grpc.ts emit at.
9769
10379
  confidenceKind: "verified-call-site",
9770
10380
  evidence: {
9771
- file: path42.relative(serviceDir, file.path),
10381
+ file: path43.relative(serviceDir, file.path),
9772
10382
  line,
9773
10383
  snippet: snippet(file.content, line)
9774
10384
  }
@@ -9795,7 +10405,7 @@ function supabaseEndpointsFromFile(file, serviceDir) {
9795
10405
  edgeType: "CALLS",
9796
10406
  confidenceKind: "verified-call-site",
9797
10407
  evidence: {
9798
- file: path42.relative(serviceDir, file.path),
10408
+ file: path43.relative(serviceDir, file.path),
9799
10409
  line,
9800
10410
  snippet: snippet(file.content, line)
9801
10411
  }
@@ -9806,7 +10416,7 @@ function supabaseEndpointsFromFile(file, serviceDir) {
9806
10416
  }
9807
10417
 
9808
10418
  // src/extract/calls/firestore.ts
9809
- import path43 from "path";
10419
+ import path44 from "path";
9810
10420
  import Parser8 from "tree-sitter";
9811
10421
  import JavaScript6 from "tree-sitter-javascript";
9812
10422
  import { infraId as infraId7 } from "@neat.is/types";
@@ -9845,7 +10455,7 @@ function isFirestoreClientFactory(node) {
9845
10455
  }
9846
10456
  function firestoreClientVars(root) {
9847
10457
  const vars = /* @__PURE__ */ new Set();
9848
- const walk9 = (node) => {
10458
+ const walk10 = (node) => {
9849
10459
  if (node.type === "variable_declarator") {
9850
10460
  const name = node.childForFieldName("name");
9851
10461
  let value = node.childForFieldName("value");
@@ -9854,9 +10464,9 @@ function firestoreClientVars(root) {
9854
10464
  vars.add(name.text);
9855
10465
  }
9856
10466
  }
9857
- for (const c of namedChildren(node)) walk9(c);
10467
+ for (const c of namedChildren(node)) walk10(c);
9858
10468
  };
9859
- walk9(root);
10469
+ walk10(root);
9860
10470
  return vars;
9861
10471
  }
9862
10472
  function isClientExpr(node, clientVars) {
@@ -9977,7 +10587,7 @@ function firestoreEndpointsFromFile(file, serviceDir) {
9977
10587
  const hasAdmin = FIRESTORE_ADMIN_IMPORT_RE.test(file.content);
9978
10588
  if (!hasClient && !hasAdmin) return [];
9979
10589
  const fileSdk = hasClient && !hasAdmin ? "client" : hasAdmin && !hasClient ? "admin" : null;
9980
- const tree = parseSource3(parserForExt2(path43.extname(file.path)), file.content);
10590
+ const tree = parseSource3(parserForExt2(path44.extname(file.path)), file.content);
9981
10591
  const clientVars = firestoreClientVars(tree.rootNode);
9982
10592
  const collLine = /* @__PURE__ */ new Map();
9983
10593
  const writes = /* @__PURE__ */ new Map();
@@ -10011,7 +10621,7 @@ function firestoreEndpointsFromFile(file, serviceDir) {
10011
10621
  }
10012
10622
  s.add(field);
10013
10623
  };
10014
- const walk9 = (node) => {
10624
+ const walk10 = (node) => {
10015
10625
  if (node.type === "call_expression") {
10016
10626
  const fn = node.childForFieldName("function");
10017
10627
  const line = node.startPosition.row + 1;
@@ -10051,9 +10661,9 @@ function firestoreEndpointsFromFile(file, serviceDir) {
10051
10661
  }
10052
10662
  }
10053
10663
  }
10054
- for (const c of namedChildren(node)) walk9(c);
10664
+ for (const c of namedChildren(node)) walk10(c);
10055
10665
  };
10056
- walk9(tree.rootNode);
10666
+ walk10(tree.rootNode);
10057
10667
  const out = [];
10058
10668
  for (const [collPath, line] of collLine) {
10059
10669
  const byField = writes.get(collPath);
@@ -10081,7 +10691,7 @@ function firestoreEndpointsFromFile(file, serviceDir) {
10081
10691
  ...columnSet.size > 0 ? { columns: [...columnSet] } : {},
10082
10692
  ...sdkWrites ? { sdkWrites } : {},
10083
10693
  evidence: {
10084
- file: path43.relative(serviceDir, file.path),
10694
+ file: path44.relative(serviceDir, file.path),
10085
10695
  line,
10086
10696
  snippet: snippet(file.content, line)
10087
10697
  }
@@ -10091,7 +10701,7 @@ function firestoreEndpointsFromFile(file, serviceDir) {
10091
10701
  }
10092
10702
 
10093
10703
  // src/extract/calls/mongoose.ts
10094
- import path44 from "path";
10704
+ import path45 from "path";
10095
10705
  import { infraId as infraId8 } from "@neat.is/types";
10096
10706
  var MONGOOSE_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])mongoose['"`]/;
10097
10707
  var MONGODB_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])mongodb['"`]/;
@@ -10242,7 +10852,7 @@ function endpoint(r, file, serviceDir, matchText) {
10242
10852
  kind: r.kind,
10243
10853
  edgeType: "CALLS",
10244
10854
  confidenceKind: "verified-call-site",
10245
- 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) }
10246
10856
  };
10247
10857
  }
10248
10858
  function mongooseEndpointsFromFile(file, serviceDir) {
@@ -10332,7 +10942,7 @@ async function mongooseCrossFileEndpoints(files, serviceDir) {
10332
10942
  const registry = /* @__PURE__ */ new Map();
10333
10943
  for (const f of mongooseFiles) {
10334
10944
  const fx = fileExportsOf(f.content, pluralizeOn);
10335
- if (fx) registry.set(toPosix(path44.relative(serviceDir, f.path)), fx);
10945
+ if (fx) registry.set(toPosix(path45.relative(serviceDir, f.path)), fx);
10336
10946
  }
10337
10947
  if (registry.size === 0) return [];
10338
10948
  const out = [];
@@ -10343,7 +10953,7 @@ async function mongooseCrossFileEndpoints(files, serviceDir) {
10343
10953
  const directColl = /* @__PURE__ */ new Map();
10344
10954
  const nsExports = /* @__PURE__ */ new Map();
10345
10955
  for (const b of bindings) {
10346
- 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);
10347
10957
  if (!resolvedRel) continue;
10348
10958
  const fx = registry.get(resolvedRel);
10349
10959
  if (!fx) continue;
@@ -10386,7 +10996,7 @@ async function mongooseCrossFileEndpoints(files, serviceDir) {
10386
10996
  }
10387
10997
 
10388
10998
  // src/extract/calls/sqlalchemy.ts
10389
- import path45 from "path";
10999
+ import path46 from "path";
10390
11000
  import Parser9 from "tree-sitter";
10391
11001
  import Python5 from "tree-sitter-python";
10392
11002
  import { infraId as infraId9 } from "@neat.is/types";
@@ -10525,7 +11135,7 @@ function sqlalchemyForeignKeys(file, serviceDir) {
10525
11135
  childTable,
10526
11136
  parentTable,
10527
11137
  evidence: {
10528
- file: path45.relative(serviceDir, file.path),
11138
+ file: path46.relative(serviceDir, file.path),
10529
11139
  line,
10530
11140
  snippet: snippet(file.content, line)
10531
11141
  }
@@ -10550,7 +11160,7 @@ function sqlalchemyEndpointsFromFile(file, serviceDir) {
10550
11160
  confidenceKind: "verified-call-site",
10551
11161
  ...columns && columns.length > 0 ? { columns } : {},
10552
11162
  evidence: {
10553
- file: path45.relative(serviceDir, file.path),
11163
+ file: path46.relative(serviceDir, file.path),
10554
11164
  line,
10555
11165
  snippet: snippet(file.content, line)
10556
11166
  }
@@ -10657,7 +11267,7 @@ function pythonOrmCrossFileEndpoints(files, serviceDir) {
10657
11267
  edgeType: "CALLS",
10658
11268
  confidenceKind: "verified-call-site",
10659
11269
  evidence: {
10660
- file: path45.relative(serviceDir, file.path),
11270
+ file: path46.relative(serviceDir, file.path),
10661
11271
  line,
10662
11272
  snippet: snippet(file.content, line)
10663
11273
  }
@@ -10668,7 +11278,7 @@ function pythonOrmCrossFileEndpoints(files, serviceDir) {
10668
11278
  }
10669
11279
 
10670
11280
  // src/extract/calls/django-orm.ts
10671
- import path46 from "path";
11281
+ import path47 from "path";
10672
11282
  import Parser10 from "tree-sitter";
10673
11283
  import Python6 from "tree-sitter-python";
10674
11284
  import { infraId as infraId10 } from "@neat.is/types";
@@ -10738,7 +11348,7 @@ function djangoOrmEndpointsFromFile(file, serviceDir) {
10738
11348
  const tree = parseSource7(makePyParser4(), file.content);
10739
11349
  const out = [];
10740
11350
  const seen = /* @__PURE__ */ new Set();
10741
- const defaultAppLabel = path46.basename(path46.dirname(file.path));
11351
+ const defaultAppLabel = path47.basename(path47.dirname(file.path));
10742
11352
  walk4(tree.rootNode, (node) => {
10743
11353
  if (node.type !== "class_definition") return;
10744
11354
  if (!extendsDjangoModel(node)) return;
@@ -10756,14 +11366,14 @@ function djangoOrmEndpointsFromFile(file, serviceDir) {
10756
11366
  kind: "sql-table",
10757
11367
  edgeType: "CALLS",
10758
11368
  confidenceKind: "verified-call-site",
10759
- 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) }
10760
11370
  });
10761
11371
  });
10762
11372
  return out;
10763
11373
  }
10764
11374
 
10765
11375
  // src/extract/calls/drizzle.ts
10766
- import path47 from "path";
11376
+ import path48 from "path";
10767
11377
  import Parser11 from "tree-sitter";
10768
11378
  import JavaScript7 from "tree-sitter-javascript";
10769
11379
  import { infraId as infraId11 } from "@neat.is/types";
@@ -10841,10 +11451,10 @@ function columnsFromObject(obj) {
10841
11451
  }
10842
11452
  function drizzleEndpointsFromFile(file, serviceDir) {
10843
11453
  if (!DRIZZLE_IMPORT_RE.test(file.content)) return [];
10844
- const tree = parseSource3(parserForExt3(path47.extname(file.path)), file.content);
11454
+ const tree = parseSource3(parserForExt3(path48.extname(file.path)), file.content);
10845
11455
  const out = [];
10846
11456
  const seen = /* @__PURE__ */ new Set();
10847
- const walk9 = (node) => {
11457
+ const walk10 = (node) => {
10848
11458
  if (node.type === "call_expression") {
10849
11459
  const fn = node.childForFieldName("function");
10850
11460
  if (fn?.type === "identifier" && TABLE_BUILDERS.has(fn.text)) {
@@ -10864,7 +11474,7 @@ function drizzleEndpointsFromFile(file, serviceDir) {
10864
11474
  confidenceKind: "structural",
10865
11475
  columns,
10866
11476
  evidence: {
10867
- file: path47.relative(serviceDir, file.path),
11477
+ file: path48.relative(serviceDir, file.path),
10868
11478
  line,
10869
11479
  snippet: snippet(file.content, line)
10870
11480
  }
@@ -10872,9 +11482,9 @@ function drizzleEndpointsFromFile(file, serviceDir) {
10872
11482
  }
10873
11483
  }
10874
11484
  }
10875
- for (const c of namedChildren4(node)) walk9(c);
11485
+ for (const c of namedChildren4(node)) walk10(c);
10876
11486
  };
10877
- walk9(tree.rootNode);
11487
+ walk10(tree.rootNode);
10878
11488
  return out;
10879
11489
  }
10880
11490
  function enclosingVarName(call) {
@@ -10896,7 +11506,7 @@ function enclosingVarName(call) {
10896
11506
  function collectDrizzleTables(root) {
10897
11507
  const tables = [];
10898
11508
  const varToTable = /* @__PURE__ */ new Map();
10899
- const walk9 = (node) => {
11509
+ const walk10 = (node) => {
10900
11510
  if (node.type === "call_expression") {
10901
11511
  const fn = node.childForFieldName("function");
10902
11512
  if (fn?.type === "identifier" && TABLE_BUILDERS.has(fn.text)) {
@@ -10911,9 +11521,9 @@ function collectDrizzleTables(root) {
10911
11521
  }
10912
11522
  }
10913
11523
  }
10914
- for (const c of namedChildren4(node)) walk9(c);
11524
+ for (const c of namedChildren4(node)) walk10(c);
10915
11525
  };
10916
- walk9(root);
11526
+ walk10(root);
10917
11527
  return { tables, varToTable };
10918
11528
  }
10919
11529
  function referencesTargetVar(call) {
@@ -10930,13 +11540,13 @@ function referencesTargetVar(call) {
10930
11540
  }
10931
11541
  function drizzleForeignKeys(file, serviceDir) {
10932
11542
  if (!DRIZZLE_IMPORT_RE.test(file.content)) return [];
10933
- const tree = parseSource3(parserForExt3(path47.extname(file.path)), file.content);
11543
+ const tree = parseSource3(parserForExt3(path48.extname(file.path)), file.content);
10934
11544
  const { tables, varToTable } = collectDrizzleTables(tree.rootNode);
10935
11545
  const out = [];
10936
11546
  const seen = /* @__PURE__ */ new Set();
10937
11547
  for (const table of tables) {
10938
11548
  if (!table.object) continue;
10939
- const walk9 = (node) => {
11549
+ const walk10 = (node) => {
10940
11550
  if (node.type === "call_expression") {
10941
11551
  const targetVar = referencesTargetVar(node);
10942
11552
  const parentTable = targetVar ? varToTable.get(targetVar) : void 0;
@@ -10949,7 +11559,7 @@ function drizzleForeignKeys(file, serviceDir) {
10949
11559
  childTable: table.tableName,
10950
11560
  parentTable,
10951
11561
  evidence: {
10952
- file: path47.relative(serviceDir, file.path),
11562
+ file: path48.relative(serviceDir, file.path),
10953
11563
  line,
10954
11564
  snippet: snippet(file.content, line)
10955
11565
  }
@@ -10957,15 +11567,15 @@ function drizzleForeignKeys(file, serviceDir) {
10957
11567
  }
10958
11568
  }
10959
11569
  }
10960
- for (const c of namedChildren4(node)) walk9(c);
11570
+ for (const c of namedChildren4(node)) walk10(c);
10961
11571
  };
10962
- walk9(table.object);
11572
+ walk10(table.object);
10963
11573
  }
10964
11574
  return out;
10965
11575
  }
10966
11576
 
10967
11577
  // src/extract/calls/prisma.ts
10968
- import path48 from "path";
11578
+ import path49 from "path";
10969
11579
  import { infraId as infraId12 } from "@neat.is/types";
10970
11580
  var SCALAR_TYPES = /* @__PURE__ */ new Set([
10971
11581
  "Int",
@@ -11021,7 +11631,7 @@ function prismaColumnsFromSchema(file, serviceDir) {
11021
11631
  confidenceKind: "structural",
11022
11632
  columns: b.columns,
11023
11633
  evidence: {
11024
- file: path48.relative(serviceDir, file.path),
11634
+ file: path49.relative(serviceDir, file.path),
11025
11635
  line: b.startLine,
11026
11636
  snippet: snippet(content, b.startLine)
11027
11637
  }
@@ -11082,7 +11692,7 @@ function prismaColumnsFromSchema(file, serviceDir) {
11082
11692
  }
11083
11693
  async function prismaColumnEndpoints(serviceDir) {
11084
11694
  const schemaPath = await findFirst(serviceDir, [
11085
- path48.join("prisma", "schema.prisma"),
11695
+ path49.join("prisma", "schema.prisma"),
11086
11696
  "schema.prisma"
11087
11697
  ]);
11088
11698
  if (!schemaPath) return [];
@@ -11157,7 +11767,7 @@ function prismaForeignKeysFromSchema(file, serviceDir) {
11157
11767
  childTable: current.table,
11158
11768
  parentTable,
11159
11769
  evidence: {
11160
- file: path48.relative(serviceDir, file.path),
11770
+ file: path49.relative(serviceDir, file.path),
11161
11771
  line: lineNo,
11162
11772
  snippet: snippet(content, lineNo)
11163
11773
  }
@@ -11172,7 +11782,7 @@ function prismaForeignKeysFromSchema(file, serviceDir) {
11172
11782
  }
11173
11783
  async function prismaForeignKeys(serviceDir) {
11174
11784
  const schemaPath = await findFirst(serviceDir, [
11175
- path48.join("prisma", "schema.prisma"),
11785
+ path49.join("prisma", "schema.prisma"),
11176
11786
  "schema.prisma"
11177
11787
  ]);
11178
11788
  if (!schemaPath) return [];
@@ -11182,7 +11792,7 @@ async function prismaForeignKeys(serviceDir) {
11182
11792
  }
11183
11793
 
11184
11794
  // src/extract/calls/activerecord.ts
11185
- import path49 from "path";
11795
+ import path50 from "path";
11186
11796
  import Parser12 from "tree-sitter";
11187
11797
  import Ruby3 from "tree-sitter-ruby";
11188
11798
  import { infraId as infraId13 } from "@neat.is/types";
@@ -11390,7 +12000,7 @@ function railsSchemaEndpointsFromFile(file, serviceDir) {
11390
12000
  confidenceKind: "structural",
11391
12001
  ...table.columns.length > 0 ? { columns: table.columns } : {},
11392
12002
  evidence: {
11393
- file: path49.relative(serviceDir, file.path),
12003
+ file: path50.relative(serviceDir, file.path),
11394
12004
  line: table.line,
11395
12005
  snippet: snippet(file.content, table.line)
11396
12006
  }
@@ -11412,7 +12022,7 @@ function railsSchemaForeignKeys(file, serviceDir) {
11412
12022
  childTable,
11413
12023
  parentTable,
11414
12024
  evidence: {
11415
- file: path49.relative(serviceDir, file.path),
12025
+ file: path50.relative(serviceDir, file.path),
11416
12026
  line,
11417
12027
  snippet: snippet(file.content, line)
11418
12028
  }
@@ -11495,7 +12105,7 @@ function railsModelEndpointsFromFile(file, serviceDir) {
11495
12105
  edgeType: "CALLS",
11496
12106
  confidenceKind: "verified-call-site",
11497
12107
  evidence: {
11498
- file: path49.relative(serviceDir, file.path),
12108
+ file: path50.relative(serviceDir, file.path),
11499
12109
  line,
11500
12110
  snippet: snippet(file.content, line)
11501
12111
  }
@@ -11532,7 +12142,7 @@ function railsModelForeignKeys(file, serviceDir) {
11532
12142
  childTable,
11533
12143
  parentTable,
11534
12144
  evidence: {
11535
- file: path49.relative(serviceDir, file.path),
12145
+ file: path50.relative(serviceDir, file.path),
11536
12146
  line,
11537
12147
  snippet: snippet(file.content, line)
11538
12148
  }
@@ -11543,7 +12153,7 @@ function railsModelForeignKeys(file, serviceDir) {
11543
12153
  }
11544
12154
 
11545
12155
  // src/extract/calls/eloquent.ts
11546
- import path50 from "path";
12156
+ import path51 from "path";
11547
12157
  import Parser13 from "tree-sitter";
11548
12158
  import Php3 from "tree-sitter-php";
11549
12159
  import { infraId as infraId14 } from "@neat.is/types";
@@ -11800,7 +12410,7 @@ function laravelMigrationEndpointsFromFile(file, serviceDir) {
11800
12410
  confidenceKind: "structural",
11801
12411
  ...columns.length > 0 ? { columns } : {},
11802
12412
  evidence: {
11803
- file: path50.relative(serviceDir, file.path),
12413
+ file: path51.relative(serviceDir, file.path),
11804
12414
  line: bp.line,
11805
12415
  snippet: snippet(file.content, bp.line)
11806
12416
  }
@@ -11822,7 +12432,7 @@ function laravelMigrationForeignKeys(file, serviceDir) {
11822
12432
  childTable,
11823
12433
  parentTable,
11824
12434
  evidence: {
11825
- file: path50.relative(serviceDir, file.path),
12435
+ file: path51.relative(serviceDir, file.path),
11826
12436
  line,
11827
12437
  snippet: snippet(file.content, line)
11828
12438
  }
@@ -11942,7 +12552,7 @@ function laravelModelEndpointsFromFile(file, serviceDir) {
11942
12552
  edgeType: "CALLS",
11943
12553
  confidenceKind: "verified-call-site",
11944
12554
  evidence: {
11945
- file: path50.relative(serviceDir, file.path),
12555
+ file: path51.relative(serviceDir, file.path),
11946
12556
  line,
11947
12557
  snippet: snippet(file.content, line)
11948
12558
  }
@@ -11978,7 +12588,7 @@ function laravelModelForeignKeys(file, serviceDir) {
11978
12588
  childTable,
11979
12589
  parentTable,
11980
12590
  evidence: {
11981
- file: path50.relative(serviceDir, file.path),
12591
+ file: path51.relative(serviceDir, file.path),
11982
12592
  line,
11983
12593
  snippet: snippet(file.content, line)
11984
12594
  }
@@ -11989,7 +12599,7 @@ function laravelModelForeignKeys(file, serviceDir) {
11989
12599
  }
11990
12600
 
11991
12601
  // src/extract/calls/go.ts
11992
- import path51 from "path";
12602
+ import path52 from "path";
11993
12603
  import Parser14 from "tree-sitter";
11994
12604
  import Go4 from "tree-sitter-go";
11995
12605
  import { infraId as infraId15 } from "@neat.is/types";
@@ -12063,7 +12673,7 @@ function firstStringLiteralArg(argsNode) {
12063
12673
  return null;
12064
12674
  }
12065
12675
  function goSqlEndpointsFromFile(file, serviceDir) {
12066
- if (path51.extname(file.path) !== ".go") return [];
12676
+ if (path52.extname(file.path) !== ".go") return [];
12067
12677
  if (!file.content.includes(DATABASE_SQL_IMPORT) && !file.content.includes(SQLX_IMPORT)) return [];
12068
12678
  const tree = parseSource10(makeGoParser3(), file.content);
12069
12679
  const importsDatabaseSql = goImportsAny(tree.rootNode, /* @__PURE__ */ new Set([DATABASE_SQL_IMPORT]));
@@ -12092,7 +12702,7 @@ function goSqlEndpointsFromFile(file, serviceDir) {
12092
12702
  confidenceKind: "verified-call-site",
12093
12703
  ...columns.length > 0 ? { columns } : {},
12094
12704
  evidence: {
12095
- file: toPosix(path51.relative(serviceDir, file.path)),
12705
+ file: toPosix(path52.relative(serviceDir, file.path)),
12096
12706
  line,
12097
12707
  snippet: snippet(file.content, line)
12098
12708
  }
@@ -12102,7 +12712,7 @@ function goSqlEndpointsFromFile(file, serviceDir) {
12102
12712
  }
12103
12713
 
12104
12714
  // src/extract/calls/gorm.ts
12105
- import path52 from "path";
12715
+ import path53 from "path";
12106
12716
  import Parser15 from "tree-sitter";
12107
12717
  import Go5 from "tree-sitter-go";
12108
12718
  import { infraId as infraId16 } from "@neat.is/types";
@@ -12535,7 +13145,7 @@ function collectColumns(struct, structs, seen, prefix, out, emitted) {
12535
13145
  seen.delete(struct.name);
12536
13146
  }
12537
13147
  function gormEndpointsFromFile(file, serviceDir) {
12538
- if (path52.extname(file.path) !== ".go") return [];
13148
+ if (path53.extname(file.path) !== ".go") return [];
12539
13149
  if (!GORM_IMPORT_RE.test(file.content)) return [];
12540
13150
  const tree = parseSource11(makeGoParser4(), file.content);
12541
13151
  const { structs, models, tableFor } = analyze(tree);
@@ -12557,7 +13167,7 @@ function gormEndpointsFromFile(file, serviceDir) {
12557
13167
  confidenceKind: "structural",
12558
13168
  ...columns.length > 0 ? { columns } : {},
12559
13169
  evidence: {
12560
- file: toPosix(path52.relative(serviceDir, file.path)),
13170
+ file: toPosix(path53.relative(serviceDir, file.path)),
12561
13171
  line: struct.line,
12562
13172
  snippet: snippet(file.content, struct.line)
12563
13173
  }
@@ -12566,7 +13176,7 @@ function gormEndpointsFromFile(file, serviceDir) {
12566
13176
  return out;
12567
13177
  }
12568
13178
  function gormForeignKeys(file, serviceDir) {
12569
- if (path52.extname(file.path) !== ".go") return [];
13179
+ if (path53.extname(file.path) !== ".go") return [];
12570
13180
  if (!GORM_IMPORT_RE.test(file.content)) return [];
12571
13181
  const tree = parseSource11(makeGoParser4(), file.content);
12572
13182
  const { structs, models, tableFor } = analyze(tree);
@@ -12581,7 +13191,7 @@ function gormForeignKeys(file, serviceDir) {
12581
13191
  childTable,
12582
13192
  parentTable,
12583
13193
  evidence: {
12584
- file: toPosix(path52.relative(serviceDir, file.path)),
13194
+ file: toPosix(path53.relative(serviceDir, file.path)),
12585
13195
  line,
12586
13196
  snippet: snippet(file.content, line)
12587
13197
  }
@@ -12616,6 +13226,121 @@ function gormForeignKeys(file, serviceDir) {
12616
13226
  return out;
12617
13227
  }
12618
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
+
12619
13344
  // src/extract/calls/index.ts
12620
13345
  function edgeTypeFromEndpoint(ep) {
12621
13346
  switch (ep.edgeType) {
@@ -12674,6 +13399,11 @@ async function addExternalEndpointEdges(graph, services) {
12674
13399
  } catch (err) {
12675
13400
  recordExtractionError("laravel eloquent extraction", file.path, err);
12676
13401
  }
13402
+ try {
13403
+ endpoints.push(...efcoreEndpointsFromFile(file, service.dir));
13404
+ } catch (err) {
13405
+ recordExtractionError("efcore data-axis extraction", file.path, err);
13406
+ }
12677
13407
  }
12678
13408
  endpoints.push(...await mongooseCrossFileEndpoints(maskedFiles, service.dir));
12679
13409
  endpoints.push(...pythonOrmCrossFileEndpoints(maskedFiles, service.dir));
@@ -12777,7 +13507,7 @@ import {
12777
13507
  Provenance as Provenance16,
12778
13508
  confidenceForExtracted as confidenceForExtracted13,
12779
13509
  extractedEdgeId as extractedEdgeId10,
12780
- infraId as infraId17
13510
+ infraId as infraId18
12781
13511
  } from "@neat.is/types";
12782
13512
  async function addTableEdges(graph, services) {
12783
13513
  let nodesAdded = 0;
@@ -12806,8 +13536,8 @@ async function addTableEdges(graph, services) {
12806
13536
  }
12807
13537
  refs.push(...modelRefs);
12808
13538
  for (const ref of refs) {
12809
- const childId = infraId17("sql-table", ref.childTable);
12810
- const parentId = infraId17("sql-table", ref.parentTable);
13539
+ const childId = infraId18("sql-table", ref.childTable);
13540
+ const parentId = infraId18("sql-table", ref.parentTable);
12811
13541
  if (childId === parentId) continue;
12812
13542
  nodesAdded += ensureTableNode(graph, childId, ref.childTable);
12813
13543
  nodesAdded += ensureTableNode(graph, parentId, ref.parentTable);
@@ -12842,14 +13572,14 @@ function ensureTableNode(graph, id, name) {
12842
13572
  }
12843
13573
 
12844
13574
  // src/extract/infra/docker-compose.ts
12845
- import path53 from "path";
13575
+ import path55 from "path";
12846
13576
  import { EdgeType as EdgeType17, Provenance as Provenance18, confidenceForExtracted as confidenceForExtracted15 } from "@neat.is/types";
12847
13577
 
12848
13578
  // src/extract/infra/shared.ts
12849
- 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";
12850
13580
  function makeInfraNode(kind, name, provider = "self", extras) {
12851
13581
  return {
12852
- id: infraId18(kind, name),
13582
+ id: infraId19(kind, name),
12853
13583
  type: NodeType26.InfraNode,
12854
13584
  name,
12855
13585
  provider,
@@ -12912,7 +13642,7 @@ function dependsOnList(value) {
12912
13642
  }
12913
13643
  function serviceNameToServiceNode(name, services) {
12914
13644
  for (const s of services) {
12915
- 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;
12916
13646
  }
12917
13647
  return null;
12918
13648
  }
@@ -12921,7 +13651,7 @@ async function addComposeInfra(graph, scanPath, services) {
12921
13651
  let edgesAdded = 0;
12922
13652
  let composePath = null;
12923
13653
  for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
12924
- const abs = path53.join(scanPath, name);
13654
+ const abs = path55.join(scanPath, name);
12925
13655
  if (await exists(abs)) {
12926
13656
  composePath = abs;
12927
13657
  break;
@@ -12934,13 +13664,13 @@ async function addComposeInfra(graph, scanPath, services) {
12934
13664
  } catch (err) {
12935
13665
  recordExtractionError(
12936
13666
  "infra docker-compose",
12937
- path53.relative(scanPath, composePath),
13667
+ path55.relative(scanPath, composePath),
12938
13668
  err
12939
13669
  );
12940
13670
  return { nodesAdded, edgesAdded };
12941
13671
  }
12942
13672
  if (!compose?.services) return { nodesAdded, edgesAdded };
12943
- const evidenceFile = path53.relative(scanPath, composePath).split(path53.sep).join("/");
13673
+ const evidenceFile = path55.relative(scanPath, composePath).split(path55.sep).join("/");
12944
13674
  const composeNameToNodeId = /* @__PURE__ */ new Map();
12945
13675
  for (const [composeName, svc] of Object.entries(compose.services)) {
12946
13676
  const matchedServiceId = serviceNameToServiceNode(composeName, services);
@@ -12981,8 +13711,8 @@ async function addComposeInfra(graph, scanPath, services) {
12981
13711
  }
12982
13712
 
12983
13713
  // src/extract/infra/dockerfile.ts
12984
- import path54 from "path";
12985
- import { promises as fs24 } from "fs";
13714
+ import path56 from "path";
13715
+ import { promises as fs25 } from "fs";
12986
13716
  import { EdgeType as EdgeType18, Provenance as Provenance19, confidenceForExtracted as confidenceForExtracted16 } from "@neat.is/types";
12987
13717
  function readDockerfile(content) {
12988
13718
  let image = null;
@@ -13012,15 +13742,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
13012
13742
  let nodesAdded = 0;
13013
13743
  let edgesAdded = 0;
13014
13744
  for (const service of services) {
13015
- const dockerfilePath = path54.join(service.dir, "Dockerfile");
13745
+ const dockerfilePath = path56.join(service.dir, "Dockerfile");
13016
13746
  if (!await exists(dockerfilePath)) continue;
13017
13747
  let content;
13018
13748
  try {
13019
- content = await fs24.readFile(dockerfilePath, "utf8");
13749
+ content = await fs25.readFile(dockerfilePath, "utf8");
13020
13750
  } catch (err) {
13021
13751
  recordExtractionError(
13022
13752
  "infra dockerfile",
13023
- path54.relative(scanPath, dockerfilePath),
13753
+ path56.relative(scanPath, dockerfilePath),
13024
13754
  err
13025
13755
  );
13026
13756
  continue;
@@ -13032,8 +13762,8 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
13032
13762
  graph.addNode(node.id, node);
13033
13763
  nodesAdded++;
13034
13764
  }
13035
- const relDockerfile = toPosix(path54.relative(service.dir, dockerfilePath));
13036
- const evidenceFile = toPosix(path54.relative(scanPath, dockerfilePath));
13765
+ const relDockerfile = toPosix(path56.relative(service.dir, dockerfilePath));
13766
+ const evidenceFile = toPosix(path56.relative(scanPath, dockerfilePath));
13037
13767
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
13038
13768
  graph,
13039
13769
  service.pkg.name,
@@ -13084,23 +13814,23 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
13084
13814
  }
13085
13815
 
13086
13816
  // src/extract/infra/terraform.ts
13087
- import { promises as fs25 } from "fs";
13088
- import path55 from "path";
13817
+ import { promises as fs26 } from "fs";
13818
+ import path57 from "path";
13089
13819
  import { EdgeType as EdgeType19, Provenance as Provenance20, confidenceForExtracted as confidenceForExtracted17 } from "@neat.is/types";
13090
13820
  var RESOURCE_RE = /resource\s+"(aws_[A-Za-z0-9_]+)"\s+"([A-Za-z0-9_-]+)"/g;
13091
13821
  var REFERENCE_RE = /(?<![\w.])(aws_[A-Za-z0-9_]+)\.([A-Za-z0-9_-]+)/g;
13092
13822
  async function walkTfFiles(start, depth = 0, max = 5) {
13093
13823
  if (depth > max) return [];
13094
13824
  const out = [];
13095
- const entries = await fs25.readdir(start, { withFileTypes: true }).catch(() => []);
13825
+ const entries = await fs26.readdir(start, { withFileTypes: true }).catch(() => []);
13096
13826
  for (const entry of entries) {
13097
13827
  if (entry.isDirectory()) {
13098
13828
  if (IGNORED_DIRS.has(entry.name) || entry.name === ".terraform") continue;
13099
- const child = path55.join(start, entry.name);
13829
+ const child = path57.join(start, entry.name);
13100
13830
  if (await isPythonVenvDir(child)) continue;
13101
13831
  out.push(...await walkTfFiles(child, depth + 1, max));
13102
13832
  } else if (entry.isFile() && entry.name.endsWith(".tf")) {
13103
- out.push(path55.join(start, entry.name));
13833
+ out.push(path57.join(start, entry.name));
13104
13834
  }
13105
13835
  }
13106
13836
  return out;
@@ -13131,8 +13861,8 @@ async function addTerraformResources(graph, scanPath) {
13131
13861
  let edgesAdded = 0;
13132
13862
  const files = await walkTfFiles(scanPath);
13133
13863
  for (const file of files) {
13134
- const content = await fs25.readFile(file, "utf8");
13135
- const evidenceFile = toPosix(path55.relative(scanPath, file));
13864
+ const content = await fs26.readFile(file, "utf8");
13865
+ const evidenceFile = toPosix(path57.relative(scanPath, file));
13136
13866
  const resources = [];
13137
13867
  const byKey = /* @__PURE__ */ new Map();
13138
13868
  RESOURCE_RE.lastIndex = 0;
@@ -13188,8 +13918,8 @@ async function addTerraformResources(graph, scanPath) {
13188
13918
  }
13189
13919
 
13190
13920
  // src/extract/infra/k8s.ts
13191
- import { promises as fs26 } from "fs";
13192
- import path56 from "path";
13921
+ import { promises as fs27 } from "fs";
13922
+ import path58 from "path";
13193
13923
  import { parseAllDocuments as parseAllDocuments2 } from "yaml";
13194
13924
  var K8S_KIND_TO_INFRA_KIND = {
13195
13925
  Service: "k8s-service",
@@ -13203,15 +13933,15 @@ var K8S_KIND_TO_INFRA_KIND = {
13203
13933
  async function walkYamlFiles2(start, depth = 0, max = 5) {
13204
13934
  if (depth > max) return [];
13205
13935
  const out = [];
13206
- const entries = await fs26.readdir(start, { withFileTypes: true }).catch(() => []);
13936
+ const entries = await fs27.readdir(start, { withFileTypes: true }).catch(() => []);
13207
13937
  for (const entry of entries) {
13208
13938
  if (entry.isDirectory()) {
13209
13939
  if (IGNORED_DIRS.has(entry.name)) continue;
13210
- const child = path56.join(start, entry.name);
13940
+ const child = path58.join(start, entry.name);
13211
13941
  if (await isPythonVenvDir(child)) continue;
13212
13942
  out.push(...await walkYamlFiles2(child, depth + 1, max));
13213
- } else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(path56.extname(entry.name))) {
13214
- 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));
13215
13945
  }
13216
13946
  }
13217
13947
  return out;
@@ -13220,7 +13950,7 @@ async function addK8sResources(graph, scanPath) {
13220
13950
  let nodesAdded = 0;
13221
13951
  const files = await walkYamlFiles2(scanPath);
13222
13952
  for (const file of files) {
13223
- const content = await fs26.readFile(file, "utf8");
13953
+ const content = await fs27.readFile(file, "utf8");
13224
13954
  let docs;
13225
13955
  try {
13226
13956
  docs = parseAllDocuments2(content).map((d) => d.toJSON());
@@ -13243,16 +13973,16 @@ async function addK8sResources(graph, scanPath) {
13243
13973
  }
13244
13974
 
13245
13975
  // src/extract/infra/cloudflare.ts
13246
- import { promises as fs27 } from "fs";
13247
- import path57 from "path";
13976
+ import { promises as fs28 } from "fs";
13977
+ import path59 from "path";
13248
13978
  import { parse as parseToml3 } from "smol-toml";
13249
13979
  import { EdgeType as EdgeType20, Provenance as Provenance21, confidenceForExtracted as confidenceForExtracted18 } from "@neat.is/types";
13250
13980
  var WRANGLER_FILENAMES = ["wrangler.toml", "wrangler.jsonc", "wrangler.json"];
13251
13981
  async function readWranglerConfig(dir) {
13252
13982
  for (const filename of WRANGLER_FILENAMES) {
13253
- const abs = path57.join(dir, filename);
13983
+ const abs = path59.join(dir, filename);
13254
13984
  if (!await exists(abs)) continue;
13255
- const raw = await fs27.readFile(abs, "utf8");
13985
+ const raw = await fs28.readFile(abs, "utf8");
13256
13986
  const config = filename === "wrangler.toml" ? parseToml3(raw) : JSON.parse(maskCommentsInSource(raw));
13257
13987
  return { config, relFile: filename, raw };
13258
13988
  }
@@ -13313,11 +14043,11 @@ async function addCloudflareWorkers(graph, services, scanPath) {
13313
14043
  try {
13314
14044
  read = await readWranglerConfig(service.dir);
13315
14045
  } catch (err) {
13316
- recordExtractionError("infra cloudflare", path57.relative(scanPath, service.dir), err);
14046
+ recordExtractionError("infra cloudflare", path59.relative(scanPath, service.dir), err);
13317
14047
  continue;
13318
14048
  }
13319
14049
  if (!read || !read.config.name) continue;
13320
- 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)));
13321
14051
  discovered.push({ service, config: read.config, relFile: read.relFile, raw: read.raw, evidenceFile });
13322
14052
  }
13323
14053
  for (const worker of discovered) {
@@ -13329,7 +14059,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
13329
14059
  }
13330
14060
  let anchorId = service.node.id;
13331
14061
  if (config.main) {
13332
- const entryRelPath = toPosix(path57.normalize(config.main));
14062
+ const entryRelPath = toPosix(path59.normalize(config.main));
13333
14063
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
13334
14064
  graph,
13335
14065
  service.pkg.name,
@@ -13475,24 +14205,24 @@ async function addCloudflareWorkers(graph, services, scanPath) {
13475
14205
  }
13476
14206
 
13477
14207
  // src/extract/infra/vercel.ts
13478
- import { promises as fs28 } from "fs";
13479
- import path58 from "path";
14208
+ import { promises as fs29 } from "fs";
14209
+ import path60 from "path";
13480
14210
  import { EdgeType as EdgeType21 } from "@neat.is/types";
13481
14211
  var VERCEL_CONFIG_FILENAMES = ["vercel.json", "vercel.jsonc"];
13482
14212
  async function readVercelConfig(dir) {
13483
14213
  for (const filename of VERCEL_CONFIG_FILENAMES) {
13484
- const abs = path58.join(dir, filename);
14214
+ const abs = path60.join(dir, filename);
13485
14215
  if (!await exists(abs)) continue;
13486
- const raw = await fs28.readFile(abs, "utf8");
14216
+ const raw = await fs29.readFile(abs, "utf8");
13487
14217
  const config = JSON.parse(maskCommentsInSource(raw));
13488
14218
  return { config, relFile: filename, raw };
13489
14219
  }
13490
14220
  return null;
13491
14221
  }
13492
14222
  async function readLinkedProjectName(dir) {
13493
- const abs = path58.join(dir, ".vercel", "project.json");
14223
+ const abs = path60.join(dir, ".vercel", "project.json");
13494
14224
  if (!await exists(abs)) return void 0;
13495
- const parsed = JSON.parse(await fs28.readFile(abs, "utf8"));
14225
+ const parsed = JSON.parse(await fs29.readFile(abs, "utf8"));
13496
14226
  return typeof parsed.projectName === "string" ? parsed.projectName : void 0;
13497
14227
  }
13498
14228
  function routeSource(route) {
@@ -13508,7 +14238,7 @@ async function addVercelServices(graph, services, scanPath) {
13508
14238
  read = await readVercelConfig(service.dir);
13509
14239
  projectName = await readLinkedProjectName(service.dir);
13510
14240
  } catch (err) {
13511
- recordExtractionError("infra vercel", path58.relative(scanPath, service.dir), err);
14241
+ recordExtractionError("infra vercel", path60.relative(scanPath, service.dir), err);
13512
14242
  continue;
13513
14243
  }
13514
14244
  if (!read && !projectName) continue;
@@ -13524,7 +14254,7 @@ async function addVercelServices(graph, services, scanPath) {
13524
14254
  const anchorId = service.node.id;
13525
14255
  if (!read) continue;
13526
14256
  const { config, relFile, raw } = read;
13527
- const evidenceFile = toPosix(path58.relative(scanPath, path58.join(service.dir, relFile)));
14257
+ const evidenceFile = toPosix(path60.relative(scanPath, path60.join(service.dir, relFile)));
13528
14258
  const add = (edgeType, kind, name) => {
13529
14259
  if (!name) return;
13530
14260
  const result = emitPlatformResourceEdge(
@@ -13552,16 +14282,16 @@ async function addVercelServices(graph, services, scanPath) {
13552
14282
  }
13553
14283
 
13554
14284
  // src/extract/infra/railway.ts
13555
- import { promises as fs29 } from "fs";
13556
- import path59 from "path";
14285
+ import { promises as fs30 } from "fs";
14286
+ import path61 from "path";
13557
14287
  import { parse as parseToml4 } from "smol-toml";
13558
14288
  import { EdgeType as EdgeType22 } from "@neat.is/types";
13559
14289
  var RAILWAY_FILENAMES = ["railway.toml", "railway.json", "railway.jsonc"];
13560
14290
  async function readRailwayConfig(dir) {
13561
14291
  for (const filename of RAILWAY_FILENAMES) {
13562
- const abs = path59.join(dir, filename);
14292
+ const abs = path61.join(dir, filename);
13563
14293
  if (!await exists(abs)) continue;
13564
- const raw = await fs29.readFile(abs, "utf8");
14294
+ const raw = await fs30.readFile(abs, "utf8");
13565
14295
  const config = filename === "railway.toml" ? parseToml4(raw) : JSON.parse(maskCommentsInSource(raw));
13566
14296
  return { config, relFile: filename, raw };
13567
14297
  }
@@ -13575,7 +14305,7 @@ async function addRailwayServices(graph, services, scanPath) {
13575
14305
  try {
13576
14306
  read = await readRailwayConfig(service.dir);
13577
14307
  } catch (err) {
13578
- recordExtractionError("infra railway", path59.relative(scanPath, service.dir), err);
14308
+ recordExtractionError("infra railway", path61.relative(scanPath, service.dir), err);
13579
14309
  continue;
13580
14310
  }
13581
14311
  if (!read) continue;
@@ -13585,7 +14315,7 @@ async function addRailwayServices(graph, services, scanPath) {
13585
14315
  }
13586
14316
  const anchorId = service.node.id;
13587
14317
  const { config, relFile, raw } = read;
13588
- const evidenceFile = toPosix(path59.relative(scanPath, path59.join(service.dir, relFile)));
14318
+ const evidenceFile = toPosix(path61.relative(scanPath, path61.join(service.dir, relFile)));
13589
14319
  const add = (edgeType, kind, name) => {
13590
14320
  if (!name) return;
13591
14321
  const result = emitPlatformResourceEdge(
@@ -13609,15 +14339,15 @@ async function addRailwayServices(graph, services, scanPath) {
13609
14339
  }
13610
14340
 
13611
14341
  // src/extract/infra/supabase.ts
13612
- import { promises as fs30 } from "fs";
13613
- import path60 from "path";
14342
+ import { promises as fs31 } from "fs";
14343
+ import path62 from "path";
13614
14344
  import { parse as parseToml5 } from "smol-toml";
13615
14345
  import { EdgeType as EdgeType23 } from "@neat.is/types";
13616
14346
  async function readSupabaseConfig(dir) {
13617
- const relFile = path60.join("supabase", "config.toml");
13618
- const abs = path60.join(dir, relFile);
14347
+ const relFile = path62.join("supabase", "config.toml");
14348
+ const abs = path62.join(dir, relFile);
13619
14349
  if (!await exists(abs)) return null;
13620
- const raw = await fs30.readFile(abs, "utf8");
14350
+ const raw = await fs31.readFile(abs, "utf8");
13621
14351
  const config = parseToml5(raw);
13622
14352
  return { config, relFile, raw };
13623
14353
  }
@@ -13629,7 +14359,7 @@ async function addSupabaseProjects(graph, services, scanPath) {
13629
14359
  try {
13630
14360
  read = await readSupabaseConfig(service.dir);
13631
14361
  } catch (err) {
13632
- recordExtractionError("infra supabase", path60.relative(scanPath, service.dir), err);
14362
+ recordExtractionError("infra supabase", path62.relative(scanPath, service.dir), err);
13633
14363
  continue;
13634
14364
  }
13635
14365
  if (!read) continue;
@@ -13644,7 +14374,7 @@ async function addSupabaseProjects(graph, services, scanPath) {
13644
14374
  });
13645
14375
  }
13646
14376
  const anchorId = service.node.id;
13647
- const evidenceFile = toPosix(path60.relative(scanPath, path60.join(service.dir, relFile)));
14377
+ const evidenceFile = toPosix(path62.relative(scanPath, path62.join(service.dir, relFile)));
13648
14378
  const add = (edgeType, kind, name) => {
13649
14379
  if (!name) return;
13650
14380
  const result = emitPlatformResourceEdge(
@@ -13685,8 +14415,8 @@ async function addInfra(graph, scanPath, services) {
13685
14415
  }
13686
14416
 
13687
14417
  // src/extract/zod-shapes.ts
13688
- import path61 from "path";
13689
- import Parser16 from "tree-sitter";
14418
+ import path63 from "path";
14419
+ import Parser17 from "tree-sitter";
13690
14420
  import JavaScript8 from "tree-sitter-javascript";
13691
14421
  import {
13692
14422
  EdgeType as EdgeType24,
@@ -13694,12 +14424,12 @@ import {
13694
14424
  Provenance as Provenance22,
13695
14425
  confidenceForExtracted as confidenceForExtracted19,
13696
14426
  extractedEdgeId as extractedEdgeId11,
13697
- infraId as infraId19
14427
+ infraId as infraId20
13698
14428
  } from "@neat.is/types";
13699
14429
  var ZOD_IMPORT_RE = /\bzod\b/;
13700
14430
  var ZOD_OBJECTS = /* @__PURE__ */ new Set(["z", "zod"]);
13701
14431
  function parserForExt4(ext) {
13702
- const p = new Parser16();
14432
+ const p = new Parser17();
13703
14433
  p.setLanguage(GRAMMAR_BY_EXT[ext] ?? JavaScript8);
13704
14434
  return p;
13705
14435
  }
@@ -13787,7 +14517,7 @@ function topLevelSchemas(root) {
13787
14517
  }
13788
14518
  function zodShapesFromFile(file, serviceDir) {
13789
14519
  if (!ZOD_IMPORT_RE.test(file.content)) return [];
13790
- const tree = parseSource3(parserForExt4(path61.extname(file.path)), file.content);
14520
+ const tree = parseSource3(parserForExt4(path63.extname(file.path)), file.content);
13791
14521
  const out = [];
13792
14522
  const seen = /* @__PURE__ */ new Set();
13793
14523
  for (const { name, call } of topLevelSchemas(tree.rootNode)) {
@@ -13801,11 +14531,11 @@ function zodShapesFromFile(file, serviceDir) {
13801
14531
  seen.add(name);
13802
14532
  const line = call.startPosition.row + 1;
13803
14533
  out.push({
13804
- infraId: infraId19("zod-schema", name),
14534
+ infraId: infraId20("zod-schema", name),
13805
14535
  name,
13806
14536
  fields,
13807
14537
  evidence: {
13808
- file: path61.relative(serviceDir, file.path),
14538
+ file: path63.relative(serviceDir, file.path),
13809
14539
  line,
13810
14540
  snippet: snippet(file.content, line)
13811
14541
  }
@@ -14041,11 +14771,11 @@ async function addFirestoreRules(graph, services) {
14041
14771
  }
14042
14772
 
14043
14773
  // src/extract/index.ts
14044
- import path63 from "path";
14774
+ import path65 from "path";
14045
14775
 
14046
14776
  // src/extract/retire.ts
14047
14777
  import { existsSync as existsSync2 } from "fs";
14048
- import path62 from "path";
14778
+ import path64 from "path";
14049
14779
  import { NodeType as NodeType29, Provenance as Provenance23 } from "@neat.is/types";
14050
14780
  function dropOrphanedFileNodes(graph) {
14051
14781
  const orphans = [];
@@ -14079,11 +14809,11 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
14079
14809
  if (edge.provenance !== Provenance23.EXTRACTED) return;
14080
14810
  const evidenceFile = edge.evidence?.file;
14081
14811
  if (!evidenceFile) return;
14082
- if (path62.isAbsolute(evidenceFile)) {
14812
+ if (path64.isAbsolute(evidenceFile)) {
14083
14813
  if (!existsSync2(evidenceFile)) toDrop.push(id);
14084
14814
  return;
14085
14815
  }
14086
- const found = bases.some((base) => existsSync2(path62.join(base, evidenceFile)));
14816
+ const found = bases.some((base) => existsSync2(path64.join(base, evidenceFile)));
14087
14817
  if (!found) toDrop.push(id);
14088
14818
  });
14089
14819
  for (const id of toDrop) graph.dropEdge(id);
@@ -14140,7 +14870,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
14140
14870
  }
14141
14871
  const droppedEntries = drainDroppedExtracted();
14142
14872
  if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
14143
- const rejectedPath = path63.join(path63.dirname(opts.errorsPath), "rejected.ndjson");
14873
+ const rejectedPath = path65.join(path65.dirname(opts.errorsPath), "rejected.ndjson");
14144
14874
  try {
14145
14875
  await writeRejectedExtracted(droppedEntries, rejectedPath);
14146
14876
  } catch (err) {
@@ -14514,8 +15244,8 @@ function computeDivergences(graph, opts = {}) {
14514
15244
  }
14515
15245
 
14516
15246
  // src/persist.ts
14517
- import { promises as fs31 } from "fs";
14518
- import path64 from "path";
15247
+ import { promises as fs32 } from "fs";
15248
+ import path66 from "path";
14519
15249
  import { NodeType as NodeType31, Provenance as Provenance25, observedEdgeId as observedEdgeId2 } from "@neat.is/types";
14520
15250
  var SCHEMA_VERSION = 7;
14521
15251
  function migrateV1ToV2(payload) {
@@ -14570,7 +15300,7 @@ function migrateV2ToV3(payload) {
14570
15300
  return { ...payload, schemaVersion: 3 };
14571
15301
  }
14572
15302
  async function ensureDir(filePath) {
14573
- await fs31.mkdir(path64.dirname(filePath), { recursive: true });
15303
+ await fs32.mkdir(path66.dirname(filePath), { recursive: true });
14574
15304
  }
14575
15305
  async function saveGraphToDisk(graph, outPath) {
14576
15306
  await ensureDir(outPath);
@@ -14580,13 +15310,13 @@ async function saveGraphToDisk(graph, outPath) {
14580
15310
  graph: graph.export()
14581
15311
  };
14582
15312
  const tmp = `${outPath}.tmp`;
14583
- await fs31.writeFile(tmp, JSON.stringify(payload), "utf8");
14584
- await fs31.rename(tmp, outPath);
15313
+ await fs32.writeFile(tmp, JSON.stringify(payload), "utf8");
15314
+ await fs32.rename(tmp, outPath);
14585
15315
  }
14586
15316
  async function loadGraphFromDisk(graph, outPath) {
14587
15317
  let raw;
14588
15318
  try {
14589
- raw = await fs31.readFile(outPath, "utf8");
15319
+ raw = await fs32.readFile(outPath, "utf8");
14590
15320
  } catch (err) {
14591
15321
  if (err.code === "ENOENT") return;
14592
15322
  throw err;
@@ -14659,7 +15389,7 @@ function startPersistLoop(graph, outPath, opts = {}) {
14659
15389
  }
14660
15390
 
14661
15391
  // src/diff.ts
14662
- import { promises as fs32 } from "fs";
15392
+ import { promises as fs33 } from "fs";
14663
15393
  async function loadSnapshotForDiff(target) {
14664
15394
  if (/^https?:\/\//i.test(target)) {
14665
15395
  const res = await fetch(target);
@@ -14668,7 +15398,7 @@ async function loadSnapshotForDiff(target) {
14668
15398
  }
14669
15399
  return await res.json();
14670
15400
  }
14671
- const raw = await fs32.readFile(target, "utf8");
15401
+ const raw = await fs33.readFile(target, "utf8");
14672
15402
  return JSON.parse(raw);
14673
15403
  }
14674
15404
  function indexEntries(entries) {
@@ -14735,23 +15465,23 @@ function canonicalJson(value) {
14735
15465
  }
14736
15466
 
14737
15467
  // src/projects.ts
14738
- import path65 from "path";
15468
+ import path67 from "path";
14739
15469
  function pathsForProject(project, baseDir) {
14740
15470
  if (project === DEFAULT_PROJECT) {
14741
15471
  return {
14742
- snapshotPath: path65.join(baseDir, "graph.json"),
14743
- errorsPath: path65.join(baseDir, "errors.ndjson"),
14744
- staleEventsPath: path65.join(baseDir, "stale-events.ndjson"),
14745
- embeddingsCachePath: path65.join(baseDir, "embeddings.json"),
14746
- 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")
14747
15477
  };
14748
15478
  }
14749
15479
  return {
14750
- snapshotPath: path65.join(baseDir, `${project}.json`),
14751
- errorsPath: path65.join(baseDir, `errors.${project}.ndjson`),
14752
- staleEventsPath: path65.join(baseDir, `stale-events.${project}.ndjson`),
14753
- embeddingsCachePath: path65.join(baseDir, `embeddings.${project}.json`),
14754
- 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`)
14755
15485
  };
14756
15486
  }
14757
15487
  var Projects = class {
@@ -14790,9 +15520,9 @@ function parseExtraProjects(raw) {
14790
15520
  }
14791
15521
 
14792
15522
  // src/registry.ts
14793
- import { promises as fs33 } from "fs";
15523
+ import { promises as fs34 } from "fs";
14794
15524
  import os2 from "os";
14795
- import path66 from "path";
15525
+ import path68 from "path";
14796
15526
  import {
14797
15527
  RegistryFileSchema
14798
15528
  } from "@neat.is/types";
@@ -14800,20 +15530,20 @@ var LOCK_TIMEOUT_MS = 5e3;
14800
15530
  var LOCK_RETRY_MS = 50;
14801
15531
  function neatHome() {
14802
15532
  const override = process.env.NEAT_HOME;
14803
- if (override && override.length > 0) return path66.resolve(override);
14804
- return path66.join(os2.homedir(), ".neat");
15533
+ if (override && override.length > 0) return path68.resolve(override);
15534
+ return path68.join(os2.homedir(), ".neat");
14805
15535
  }
14806
15536
  function registryPath() {
14807
- return path66.join(neatHome(), "projects.json");
15537
+ return path68.join(neatHome(), "projects.json");
14808
15538
  }
14809
15539
  function registryLockPath() {
14810
- return path66.join(neatHome(), "projects.json.lock");
15540
+ return path68.join(neatHome(), "projects.json.lock");
14811
15541
  }
14812
15542
  function daemonPidPath() {
14813
- return path66.join(neatHome(), "neatd.pid");
15543
+ return path68.join(neatHome(), "neatd.pid");
14814
15544
  }
14815
15545
  function daemonsDir() {
14816
- return path66.join(neatHome(), "daemons");
15546
+ return path68.join(neatHome(), "daemons");
14817
15547
  }
14818
15548
  function isFiniteInt(v) {
14819
15549
  return typeof v === "number" && Number.isFinite(v);
@@ -14846,7 +15576,7 @@ async function discoverDaemons(probe = defaultDiscoveryProbe) {
14846
15576
  const dir = daemonsDir();
14847
15577
  let names;
14848
15578
  try {
14849
- names = await fs33.readdir(dir);
15579
+ names = await fs34.readdir(dir);
14850
15580
  } catch (err) {
14851
15581
  if (err.code === "ENOENT") return [];
14852
15582
  throw err;
@@ -14854,10 +15584,10 @@ async function discoverDaemons(probe = defaultDiscoveryProbe) {
14854
15584
  const out = [];
14855
15585
  for (const name of names) {
14856
15586
  if (!name.endsWith(".json")) continue;
14857
- const file = path66.join(dir, name);
15587
+ const file = path68.join(dir, name);
14858
15588
  let raw;
14859
15589
  try {
14860
- raw = await fs33.readFile(file, "utf8");
15590
+ raw = await fs34.readFile(file, "utf8");
14861
15591
  } catch {
14862
15592
  continue;
14863
15593
  }
@@ -14870,7 +15600,7 @@ async function discoverDaemons(probe = defaultDiscoveryProbe) {
14870
15600
  return out;
14871
15601
  }
14872
15602
  async function removeDaemonRecord(source) {
14873
- await fs33.unlink(source).catch(() => {
15603
+ await fs34.unlink(source).catch(() => {
14874
15604
  });
14875
15605
  }
14876
15606
  async function listMachineProjects(probe = defaultDiscoveryProbe) {
@@ -14927,7 +15657,7 @@ function isPidAliveDefault(pid) {
14927
15657
  }
14928
15658
  async function readPidFile(file) {
14929
15659
  try {
14930
- const raw = await fs33.readFile(file, "utf8");
15660
+ const raw = await fs34.readFile(file, "utf8");
14931
15661
  const pid = Number.parseInt(raw.trim(), 10);
14932
15662
  return Number.isInteger(pid) && pid > 0 ? pid : void 0;
14933
15663
  } catch {
@@ -14975,32 +15705,32 @@ function lockHolderMessage(holder, lockPath, timeoutMs) {
14975
15705
  }
14976
15706
  }
14977
15707
  async function normalizeProjectPath(input) {
14978
- const resolved = path66.resolve(input);
15708
+ const resolved = path68.resolve(input);
14979
15709
  try {
14980
- return await fs33.realpath(resolved);
15710
+ return await fs34.realpath(resolved);
14981
15711
  } catch {
14982
15712
  return resolved;
14983
15713
  }
14984
15714
  }
14985
15715
  async function writeAtomically(target, contents) {
14986
- await fs33.mkdir(path66.dirname(target), { recursive: true });
15716
+ await fs34.mkdir(path68.dirname(target), { recursive: true });
14987
15717
  const tmp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
14988
- const fd = await fs33.open(tmp, "w");
15718
+ const fd = await fs34.open(tmp, "w");
14989
15719
  try {
14990
15720
  await fd.writeFile(contents, "utf8");
14991
15721
  await fd.sync();
14992
15722
  } finally {
14993
15723
  await fd.close();
14994
15724
  }
14995
- await fs33.rename(tmp, target);
15725
+ await fs34.rename(tmp, target);
14996
15726
  }
14997
15727
  async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaultLockHolderProbe) {
14998
15728
  const deadline = Date.now() + timeoutMs;
14999
- await fs33.mkdir(path66.dirname(lockPath), { recursive: true });
15729
+ await fs34.mkdir(path68.dirname(lockPath), { recursive: true });
15000
15730
  let probedHolder = false;
15001
15731
  while (true) {
15002
15732
  try {
15003
- const fd = await fs33.open(lockPath, "wx");
15733
+ const fd = await fs34.open(lockPath, "wx");
15004
15734
  try {
15005
15735
  await fd.writeFile(`${process.pid}
15006
15736
  `, "utf8");
@@ -15025,7 +15755,7 @@ async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaul
15025
15755
  }
15026
15756
  }
15027
15757
  async function releaseLock(lockPath) {
15028
- await fs33.unlink(lockPath).catch(() => {
15758
+ await fs34.unlink(lockPath).catch(() => {
15029
15759
  });
15030
15760
  }
15031
15761
  async function withLock(fn) {
@@ -15041,7 +15771,7 @@ async function readRegistry() {
15041
15771
  const file = registryPath();
15042
15772
  let raw;
15043
15773
  try {
15044
- raw = await fs33.readFile(file, "utf8");
15774
+ raw = await fs34.readFile(file, "utf8");
15045
15775
  } catch (err) {
15046
15776
  if (err.code === "ENOENT") {
15047
15777
  return { version: 1, projects: [] };
@@ -15146,7 +15876,7 @@ function pruneTtlMs() {
15146
15876
  }
15147
15877
  async function statPathStatus(p) {
15148
15878
  try {
15149
- const stat = await fs33.stat(p);
15879
+ const stat = await fs34.stat(p);
15150
15880
  return stat.isDirectory() ? "present" : "unknown";
15151
15881
  } catch (err) {
15152
15882
  return err.code === "ENOENT" ? "gone" : "unknown";
@@ -15191,14 +15921,14 @@ import cors from "@fastify/cors";
15191
15921
  import { DivergenceTypeSchema, PoliciesCheckBodySchema, PolicySeveritySchema } from "@neat.is/types";
15192
15922
 
15193
15923
  // src/extend/index.ts
15194
- import { promises as fs35 } from "fs";
15195
- import path68 from "path";
15924
+ import { promises as fs36 } from "fs";
15925
+ import path70 from "path";
15196
15926
  import os3 from "os";
15197
15927
  import { resolve as registryResolve, list as registryList } from "@neat.is/instrumentation-registry";
15198
15928
 
15199
15929
  // src/installers/package-manager.ts
15200
- import { promises as fs34 } from "fs";
15201
- import path67 from "path";
15930
+ import { promises as fs35 } from "fs";
15931
+ import path69 from "path";
15202
15932
  import { spawn } from "child_process";
15203
15933
  var LOCKFILE_PRIORITY = [
15204
15934
  { lockfile: "bun.lockb", pm: "bun", args: ["install", "--no-summary"] },
@@ -15213,29 +15943,29 @@ var LOCKFILE_PRIORITY = [
15213
15943
  var NPM_FALLBACK_ARGS = ["install", "--no-audit", "--no-fund", "--prefer-offline"];
15214
15944
  async function exists2(p) {
15215
15945
  try {
15216
- await fs34.access(p);
15946
+ await fs35.access(p);
15217
15947
  return true;
15218
15948
  } catch {
15219
15949
  return false;
15220
15950
  }
15221
15951
  }
15222
15952
  async function detectPackageManager(serviceDir) {
15223
- let dir = path67.resolve(serviceDir);
15953
+ let dir = path69.resolve(serviceDir);
15224
15954
  const stops = /* @__PURE__ */ new Set();
15225
15955
  for (let i = 0; i < 64; i++) {
15226
15956
  if (stops.has(dir)) break;
15227
15957
  stops.add(dir);
15228
15958
  for (const candidate of LOCKFILE_PRIORITY) {
15229
- const lockPath = path67.join(dir, candidate.lockfile);
15959
+ const lockPath = path69.join(dir, candidate.lockfile);
15230
15960
  if (await exists2(lockPath)) {
15231
15961
  return { pm: candidate.pm, cwd: dir, args: [...candidate.args] };
15232
15962
  }
15233
15963
  }
15234
- const parent = path67.dirname(dir);
15964
+ const parent = path69.dirname(dir);
15235
15965
  if (parent === dir) break;
15236
15966
  dir = parent;
15237
15967
  }
15238
- return { pm: "npm", cwd: path67.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
15968
+ return { pm: "npm", cwd: path69.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
15239
15969
  }
15240
15970
  async function runPackageManagerInstall(cmd) {
15241
15971
  return new Promise((resolve) => {
@@ -15277,15 +16007,15 @@ ${err.message}`
15277
16007
  // src/extend/index.ts
15278
16008
  async function fileExists2(p) {
15279
16009
  try {
15280
- await fs35.access(p);
16010
+ await fs36.access(p);
15281
16011
  return true;
15282
16012
  } catch {
15283
16013
  return false;
15284
16014
  }
15285
16015
  }
15286
16016
  async function readPackageJson(scanPath) {
15287
- const pkgPath = path68.join(scanPath, "package.json");
15288
- const raw = await fs35.readFile(pkgPath, "utf8");
16017
+ const pkgPath = path70.join(scanPath, "package.json");
16018
+ const raw = await fs36.readFile(pkgPath, "utf8");
15289
16019
  return JSON.parse(raw);
15290
16020
  }
15291
16021
  var HOOK_WALK_SKIP_DIRS = /* @__PURE__ */ new Set([
@@ -15298,27 +16028,27 @@ var HOOK_WALK_SKIP_DIRS = /* @__PURE__ */ new Set([
15298
16028
  ]);
15299
16029
  async function findHookFiles(scanPath) {
15300
16030
  const found = [];
15301
- const walk9 = async (dir) => {
15302
- const entries = await fs35.readdir(dir, { withFileTypes: true }).catch(() => []);
16031
+ const walk10 = async (dir) => {
16032
+ const entries = await fs36.readdir(dir, { withFileTypes: true }).catch(() => []);
15303
16033
  for (const entry of entries) {
15304
16034
  if (entry.isDirectory()) {
15305
16035
  if (entry.name.startsWith(".") || HOOK_WALK_SKIP_DIRS.has(entry.name)) continue;
15306
- await walk9(path68.join(dir, entry.name));
16036
+ await walk10(path70.join(dir, entry.name));
15307
16037
  } else if (entry.isFile()) {
15308
16038
  if ((entry.name.startsWith("instrumentation") || entry.name.startsWith("otel-init")) && /\.(ts|js|cjs|mjs)$/.test(entry.name)) {
15309
- const rel = path68.relative(scanPath, path68.join(dir, entry.name));
15310
- 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("/"));
15311
16041
  }
15312
16042
  }
15313
16043
  }
15314
16044
  };
15315
- await walk9(scanPath);
16045
+ await walk10(scanPath);
15316
16046
  return found.sort();
15317
16047
  }
15318
16048
  async function pickPrimaryHookFile(scanPath, hookFiles, snippet2) {
15319
16049
  let fallback = null;
15320
16050
  for (const file of hookFiles) {
15321
- const content = await fs35.readFile(path68.join(scanPath, file), "utf8");
16051
+ const content = await fs36.readFile(path70.join(scanPath, file), "utf8");
15322
16052
  const patched = splicedContent(content, snippet2);
15323
16053
  if (patched !== null) return { file, content, patched };
15324
16054
  if (fallback === null) fallback = { file, content };
@@ -15326,12 +16056,12 @@ async function pickPrimaryHookFile(scanPath, hookFiles, snippet2) {
15326
16056
  return { file: fallback.file, content: fallback.content, patched: null };
15327
16057
  }
15328
16058
  function extendLogPath() {
15329
- 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");
15330
16060
  }
15331
16061
  async function appendExtendLog(entry) {
15332
16062
  const logPath = extendLogPath();
15333
- await fs35.mkdir(path68.dirname(logPath), { recursive: true });
15334
- 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");
15335
16065
  }
15336
16066
  function splicedContent(fileContent, snippet2) {
15337
16067
  if (fileContent.includes("__INSTRUMENTATION_BLOCK__")) {
@@ -15389,7 +16119,7 @@ function lookupInstrumentation(library, installedVersion) {
15389
16119
  }
15390
16120
  async function describeProjectInstrumentation(ctx) {
15391
16121
  const hookFiles = await findHookFiles(ctx.scanPath);
15392
- const envNeat = await fileExists2(path68.join(ctx.scanPath, ".env.neat"));
16122
+ const envNeat = await fileExists2(path70.join(ctx.scanPath, ".env.neat"));
15393
16123
  const registryInstrPackages = new Set(
15394
16124
  registryList().map((e) => e.instrumentation_package).filter((p) => !!p)
15395
16125
  );
@@ -15411,7 +16141,7 @@ async function applyExtension(ctx, args, options) {
15411
16141
  );
15412
16142
  }
15413
16143
  for (const file of hookFiles) {
15414
- const content = await fs35.readFile(path68.join(ctx.scanPath, file), "utf8");
16144
+ const content = await fs36.readFile(path70.join(ctx.scanPath, file), "utf8");
15415
16145
  if (content.includes(args.registration_snippet)) {
15416
16146
  return { library: args.library, filesTouched: [], depsAdded: [], installOutput: "", alreadyApplied: true };
15417
16147
  }
@@ -15423,18 +16153,18 @@ async function applyExtension(ctx, args, options) {
15423
16153
  );
15424
16154
  }
15425
16155
  const primaryFile = primary.file;
15426
- const primaryPath = path68.join(ctx.scanPath, primaryFile);
16156
+ const primaryPath = path70.join(ctx.scanPath, primaryFile);
15427
16157
  const filesTouched = [];
15428
16158
  const depsAdded = [];
15429
- const pkgPath = path68.join(ctx.scanPath, "package.json");
16159
+ const pkgPath = path70.join(ctx.scanPath, "package.json");
15430
16160
  const pkg = await readPackageJson(ctx.scanPath);
15431
16161
  if (!(pkg.dependencies ?? {})[args.instrumentation_package]) {
15432
16162
  pkg.dependencies = { ...pkg.dependencies ?? {}, [args.instrumentation_package]: args.version };
15433
- await fs35.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
16163
+ await fs36.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
15434
16164
  filesTouched.push("package.json");
15435
16165
  depsAdded.push(`${args.instrumentation_package}@${args.version}`);
15436
16166
  }
15437
- await fs35.writeFile(primaryPath, primary.patched, "utf8");
16167
+ await fs36.writeFile(primaryPath, primary.patched, "utf8");
15438
16168
  filesTouched.push(primaryFile);
15439
16169
  const cmd = await detectPackageManager(ctx.scanPath);
15440
16170
  const installer = options?.runInstall ?? runPackageManagerInstall;
@@ -15465,7 +16195,7 @@ async function dryRunExtension(ctx, args) {
15465
16195
  };
15466
16196
  }
15467
16197
  for (const file of hookFiles) {
15468
- const content = await fs35.readFile(path68.join(ctx.scanPath, file), "utf8");
16198
+ const content = await fs36.readFile(path70.join(ctx.scanPath, file), "utf8");
15469
16199
  if (content.includes(args.registration_snippet)) {
15470
16200
  return {
15471
16201
  library: args.library,
@@ -15500,28 +16230,28 @@ async function rollbackExtension(ctx, args) {
15500
16230
  if (!await fileExists2(logPath)) {
15501
16231
  return { undone: false, message: "no apply found for library" };
15502
16232
  }
15503
- const raw = await fs35.readFile(logPath, "utf8");
16233
+ const raw = await fs36.readFile(logPath, "utf8");
15504
16234
  const entries = raw.trim().split("\n").filter(Boolean).map((line) => JSON.parse(line));
15505
16235
  const match = [...entries].reverse().find((e) => e.project === ctx.project && e.library === args.library);
15506
16236
  if (!match) {
15507
16237
  return { undone: false, message: "no apply found for library" };
15508
16238
  }
15509
- const pkgPath = path68.join(ctx.scanPath, "package.json");
16239
+ const pkgPath = path70.join(ctx.scanPath, "package.json");
15510
16240
  if (await fileExists2(pkgPath)) {
15511
16241
  const pkg = await readPackageJson(ctx.scanPath);
15512
16242
  if (pkg.dependencies?.[match.instrumentation_package]) {
15513
16243
  const { [match.instrumentation_package]: _removed, ...rest } = pkg.dependencies;
15514
16244
  pkg.dependencies = rest;
15515
- await fs35.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
16245
+ await fs36.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
15516
16246
  }
15517
16247
  }
15518
16248
  const hookFiles = await findHookFiles(ctx.scanPath);
15519
16249
  for (const file of hookFiles) {
15520
- const filePath = path68.join(ctx.scanPath, file);
15521
- const content = await fs35.readFile(filePath, "utf8");
16250
+ const filePath = path70.join(ctx.scanPath, file);
16251
+ const content = await fs36.readFile(filePath, "utf8");
15522
16252
  if (content.includes(match.registration_snippet)) {
15523
16253
  const filtered = content.split("\n").filter((line) => !line.includes(match.registration_snippet)).join("\n");
15524
- await fs35.writeFile(filePath, filtered, "utf8");
16254
+ await fs36.writeFile(filePath, filtered, "utf8");
15525
16255
  break;
15526
16256
  }
15527
16257
  }
@@ -15632,8 +16362,8 @@ data: ${JSON.stringify(envelope.payload)}
15632
16362
 
15633
16363
  // src/connectors-config.ts
15634
16364
  import os4 from "os";
15635
- import path69 from "path";
15636
- import { promises as fs36 } from "fs";
16365
+ import path71 from "path";
16366
+ import { promises as fs37 } from "fs";
15637
16367
  var CONNECTORS_CONFIG_VERSION = 1;
15638
16368
  var EnvRefUnsetError = class extends Error {
15639
16369
  ref;
@@ -15647,17 +16377,17 @@ var EnvRefUnsetError = class extends Error {
15647
16377
  };
15648
16378
  function neatHome2() {
15649
16379
  const override = process.env.NEAT_HOME;
15650
- if (override && override.length > 0) return path69.resolve(override);
15651
- return path69.join(os4.homedir(), ".neat");
16380
+ if (override && override.length > 0) return path71.resolve(override);
16381
+ return path71.join(os4.homedir(), ".neat");
15652
16382
  }
15653
16383
  function connectorsConfigPath(home = neatHome2()) {
15654
- return path69.join(home, "connectors.json");
16384
+ return path71.join(home, "connectors.json");
15655
16385
  }
15656
16386
  var MODE_MASK_LOOSER_THAN_0600 = 63;
15657
16387
  async function warnIfModeLooserThan0600(file) {
15658
16388
  if (process.platform === "win32") return;
15659
16389
  try {
15660
- const stat = await fs36.stat(file);
16390
+ const stat = await fs37.stat(file);
15661
16391
  if ((stat.mode & MODE_MASK_LOOSER_THAN_0600) !== 0) {
15662
16392
  const mode = (stat.mode & 511).toString(8).padStart(3, "0");
15663
16393
  console.warn(
@@ -15671,7 +16401,7 @@ async function readConnectorsConfig(home = neatHome2()) {
15671
16401
  const file = connectorsConfigPath(home);
15672
16402
  let raw;
15673
16403
  try {
15674
- raw = await fs36.readFile(file, "utf8");
16404
+ raw = await fs37.readFile(file, "utf8");
15675
16405
  } catch (err) {
15676
16406
  if (err.code === "ENOENT") {
15677
16407
  return { version: CONNECTORS_CONFIG_VERSION, connectors: [] };
@@ -15782,7 +16512,7 @@ function connectorMatchesProject(entry, project) {
15782
16512
  var CONNECTORS_LOCK_TIMEOUT_MS = 5e3;
15783
16513
  var CONNECTORS_LOCK_RETRY_MS = 50;
15784
16514
  function connectorsConfigLockPath(home = neatHome2()) {
15785
- return path69.join(home, "connectors.json.lock");
16515
+ return path71.join(home, "connectors.json.lock");
15786
16516
  }
15787
16517
  function isEnvRef(value) {
15788
16518
  return value.length > 1 && value.startsWith("$");
@@ -15795,9 +16525,9 @@ function redactCredentialRef(ref) {
15795
16525
  return out;
15796
16526
  }
15797
16527
  async function writeConfigAtomically0600(file, contents) {
15798
- await fs36.mkdir(path69.dirname(file), { recursive: true });
16528
+ await fs37.mkdir(path71.dirname(file), { recursive: true });
15799
16529
  const tmp = `${file}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
15800
- const fd = await fs36.open(tmp, "w", 384);
16530
+ const fd = await fs37.open(tmp, "w", 384);
15801
16531
  try {
15802
16532
  await fd.writeFile(contents, "utf8");
15803
16533
  await fd.chmod(384);
@@ -15805,14 +16535,14 @@ async function writeConfigAtomically0600(file, contents) {
15805
16535
  } finally {
15806
16536
  await fd.close();
15807
16537
  }
15808
- await fs36.rename(tmp, file);
16538
+ await fs37.rename(tmp, file);
15809
16539
  }
15810
16540
  async function acquireConnectorsLock(lockPath, timeoutMs = CONNECTORS_LOCK_TIMEOUT_MS) {
15811
16541
  const deadline = Date.now() + timeoutMs;
15812
- await fs36.mkdir(path69.dirname(lockPath), { recursive: true });
16542
+ await fs37.mkdir(path71.dirname(lockPath), { recursive: true });
15813
16543
  for (; ; ) {
15814
16544
  try {
15815
- const fd = await fs36.open(lockPath, "wx");
16545
+ const fd = await fs37.open(lockPath, "wx");
15816
16546
  try {
15817
16547
  await fd.writeFile(`${process.pid}
15818
16548
  `, "utf8");
@@ -15832,7 +16562,7 @@ async function acquireConnectorsLock(lockPath, timeoutMs = CONNECTORS_LOCK_TIMEO
15832
16562
  }
15833
16563
  }
15834
16564
  async function releaseConnectorsLock(lockPath) {
15835
- await fs36.unlink(lockPath).catch(() => {
16565
+ await fs37.unlink(lockPath).catch(() => {
15836
16566
  });
15837
16567
  }
15838
16568
  async function withConnectorsLock(home, fn) {
@@ -16461,10 +17191,10 @@ var SUPABASE_RPC_TARGET_KIND = "supabase-rpc";
16461
17191
  // src/connectors/supabase/map.ts
16462
17192
  var REST_RPC_PATH_RE = /^\/rest\/v1\/rpc\/([^/?]+)/;
16463
17193
  var REST_TABLE_PATH_RE = /^\/rest\/v1\/([^/?]+)/;
16464
- function targetFromRestPath(path70) {
16465
- const rpcMatch = REST_RPC_PATH_RE.exec(path70);
17194
+ function targetFromRestPath(path72) {
17195
+ const rpcMatch = REST_RPC_PATH_RE.exec(path72);
16466
17196
  if (rpcMatch) return { targetKind: SUPABASE_RPC_TARGET_KIND, name: rpcMatch[1] };
16467
- const tableMatch = REST_TABLE_PATH_RE.exec(path70);
17197
+ const tableMatch = REST_TABLE_PATH_RE.exec(path72);
16468
17198
  if (tableMatch) return { targetKind: SUPABASE_TABLE_TARGET_KIND, name: tableMatch[1] };
16469
17199
  return null;
16470
17200
  }
@@ -16573,21 +17303,21 @@ async function fetchPgStatStatements(connectionString, limit = DEFAULT_STATEMENT
16573
17303
  }
16574
17304
 
16575
17305
  // src/connectors/supabase/resolve.ts
16576
- import { EdgeType as EdgeType26, infraId as infraId20 } from "@neat.is/types";
17306
+ import { EdgeType as EdgeType26, infraId as infraId21 } from "@neat.is/types";
16577
17307
  function createSupabaseResolveTarget(graph, config) {
16578
17308
  return (signal, _ctx) => {
16579
17309
  if (signal.targetKind !== SUPABASE_TABLE_TARGET_KIND && signal.targetKind !== SUPABASE_RPC_TARGET_KIND) {
16580
17310
  return null;
16581
17311
  }
16582
- const subResourceId = infraId20(signal.targetKind, `${config.nodeRef}/${signal.targetName}`);
17312
+ const subResourceId = infraId21(signal.targetKind, `${config.nodeRef}/${signal.targetName}`);
16583
17313
  if (graph.hasNode(subResourceId)) {
16584
17314
  return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType: EdgeType26.CALLS };
16585
17315
  }
16586
- const bareResourceId = infraId20(signal.targetKind, signal.targetName);
17316
+ const bareResourceId = infraId21(signal.targetKind, signal.targetName);
16587
17317
  if (graph.hasNode(bareResourceId)) {
16588
17318
  return { targetNodeId: bareResourceId, serviceName: config.serviceName, edgeType: EdgeType26.CALLS };
16589
17319
  }
16590
- const projectLevelId = infraId20("supabase", config.nodeRef);
17320
+ const projectLevelId = infraId21("supabase", config.nodeRef);
16591
17321
  if (graph.hasNode(projectLevelId)) {
16592
17322
  return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType: EdgeType26.CALLS };
16593
17323
  }
@@ -17059,9 +17789,9 @@ function parseFirebaseTargetName(targetName) {
17059
17789
  const secondSep = rest.indexOf(FIELD_SEP);
17060
17790
  if (secondSep === -1) return null;
17061
17791
  const method = rest.slice(0, secondSep);
17062
- const path70 = rest.slice(secondSep + 1);
17063
- if (!resourceName || !method || !path70) return null;
17064
- 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 };
17065
17795
  }
17066
17796
  function resourceNameFor(type, labels) {
17067
17797
  if (!labels) return null;
@@ -17099,14 +17829,14 @@ function mapLogEntryToSignal(entry) {
17099
17829
  if (!req) return null;
17100
17830
  if (typeof req.requestMethod !== "string" || req.requestMethod.length === 0) return null;
17101
17831
  const method = req.requestMethod.toUpperCase();
17102
- const path70 = pathFromRequestUrl(req.requestUrl);
17103
- if (path70 === null) return null;
17832
+ const path72 = pathFromRequestUrl(req.requestUrl);
17833
+ if (path72 === null) return null;
17104
17834
  const timestamp = entry.timestamp;
17105
17835
  if (typeof timestamp !== "string" || timestamp.length === 0) return null;
17106
17836
  const isError = typeof req.status === "number" && req.status >= ERROR_STATUS_THRESHOLD2;
17107
17837
  return {
17108
17838
  targetKind: resourceType,
17109
- targetName: packFirebaseTargetName({ resourceName, method, path: path70 }),
17839
+ targetName: packFirebaseTargetName({ resourceName, method, path: path72 }),
17110
17840
  callCount: 1,
17111
17841
  errorCount: isError ? 1 : 0,
17112
17842
  lastObservedIso: timestamp
@@ -17192,7 +17922,7 @@ function createFirebaseConnector(graph, serviceMap) {
17192
17922
  }
17193
17923
 
17194
17924
  // src/connectors/cloudflare/connector.ts
17195
- 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";
17196
17926
 
17197
17927
  // src/connectors/cloudflare/client.ts
17198
17928
  import { randomUUID } from "crypto";
@@ -17303,7 +18033,7 @@ function mapEventToSignal(event) {
17303
18033
  if (Number.isNaN(observedAt.getTime())) return null;
17304
18034
  const statusCode = metadata?.statusCode;
17305
18035
  const isError = typeof statusCode === "number" && statusCode >= ERROR_STATUS_THRESHOLD3;
17306
- const path70 = metadata?.trigger ? parsePathFromTrigger(metadata.trigger) : void 0;
18036
+ const path72 = metadata?.trigger ? parsePathFromTrigger(metadata.trigger) : void 0;
17307
18037
  return {
17308
18038
  targetKind: CLOUDFLARE_TARGET_KIND,
17309
18039
  targetName: scriptName,
@@ -17311,7 +18041,7 @@ function mapEventToSignal(event) {
17311
18041
  errorCount: isError ? 1 : 0,
17312
18042
  lastObservedIso: observedAt.toISOString(),
17313
18043
  method,
17314
- ...path70 ? { path: path70 } : {},
18044
+ ...path72 ? { path: path72 } : {},
17315
18045
  ...typeof statusCode === "number" ? { statusCode } : {},
17316
18046
  ...typeof metadata?.duration === "number" ? { duration: metadata.duration } : {}
17317
18047
  };
@@ -17357,8 +18087,8 @@ function findTaggedWorkerFileNode(graph, workerName) {
17357
18087
  });
17358
18088
  return found;
17359
18089
  }
17360
- function findMatchingRouteNode(graph, serviceName, method, path70) {
17361
- const normalizedPath = normalizePathTemplate(path70);
18090
+ function findMatchingRouteNode(graph, serviceName, method, path72) {
18091
+ const normalizedPath = normalizePathTemplate(path72);
17362
18092
  let found = null;
17363
18093
  graph.forEachNode((id, attrs) => {
17364
18094
  if (found) return;
@@ -17375,10 +18105,10 @@ function createCloudflareResolveTarget(config, graph) {
17375
18105
  return (signal) => {
17376
18106
  if (signal.targetKind !== CLOUDFLARE_TARGET_KIND) return null;
17377
18107
  const scriptName = signal.targetName;
17378
- const { method, path: path70 } = signal;
18108
+ const { method, path: path72 } = signal;
17379
18109
  const resolveRouteGrain = (serviceName, wholeFileId) => {
17380
- if (!method || !path70) return wholeFileId;
17381
- return findMatchingRouteNode(graph, serviceName, method, path70) ?? wholeFileId;
18110
+ if (!method || !path72) return wholeFileId;
18111
+ return findMatchingRouteNode(graph, serviceName, method, path72) ?? wholeFileId;
17382
18112
  };
17383
18113
  const mapping = config.workers?.[scriptName];
17384
18114
  if (mapping) {
@@ -17399,7 +18129,7 @@ function createCloudflareResolveTarget(config, graph) {
17399
18129
  };
17400
18130
  }
17401
18131
  return {
17402
- targetNodeId: infraId21("cloudflare-worker", scriptName),
18132
+ targetNodeId: infraId22("cloudflare-worker", scriptName),
17403
18133
  serviceName: scriptName,
17404
18134
  edgeType: EdgeType29.CALLS,
17405
18135
  ensureInfraNode: { kind: "cloudflare-worker", name: scriptName, provider: "cloudflare" }
@@ -17583,12 +18313,12 @@ function diffNeonStatementsToSignals(rows, previous, observedAtIso) {
17583
18313
  }
17584
18314
 
17585
18315
  // src/connectors/neon/resolve.ts
17586
- import { EdgeType as EdgeType30, infraId as infraId22 } from "@neat.is/types";
18316
+ import { EdgeType as EdgeType30, infraId as infraId23 } from "@neat.is/types";
17587
18317
  function createNeonResolveTarget(config) {
17588
18318
  return (signal) => {
17589
18319
  if (signal.targetKind !== NEON_SQL_TABLE_TARGET_KIND || !signal.targetName) return null;
17590
18320
  return {
17591
- targetNodeId: infraId22("sql-table", signal.targetName),
18321
+ targetNodeId: infraId23("sql-table", signal.targetName),
17592
18322
  serviceName: config.serviceName,
17593
18323
  edgeType: EdgeType30.CALLS,
17594
18324
  ensureInfraNode: { kind: "sql-table", name: signal.targetName, provider: "neon" }
@@ -17708,9 +18438,9 @@ function parseCloudRunTargetName(targetName) {
17708
18438
  const secondSep = rest.indexOf(FIELD_SEP2);
17709
18439
  if (secondSep === -1) return null;
17710
18440
  const method = rest.slice(0, secondSep);
17711
- const path70 = rest.slice(secondSep + 1);
17712
- if (!serviceName || !method || !path70) return null;
17713
- 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 };
17714
18444
  }
17715
18445
 
17716
18446
  // src/connectors/cloud-run/map.ts
@@ -17739,14 +18469,14 @@ function mapLogEntryToSignal2(entry) {
17739
18469
  if (!req) return null;
17740
18470
  if (typeof req.requestMethod !== "string" || req.requestMethod.length === 0) return null;
17741
18471
  const method = req.requestMethod.toUpperCase();
17742
- const path70 = pathFromRequestUrl2(req.requestUrl);
17743
- if (path70 === null) return null;
18472
+ const path72 = pathFromRequestUrl2(req.requestUrl);
18473
+ if (path72 === null) return null;
17744
18474
  const timestamp = entry.timestamp;
17745
18475
  if (typeof timestamp !== "string" || timestamp.length === 0) return null;
17746
18476
  const isError = typeof req.status === "number" && req.status >= ERROR_STATUS_THRESHOLD4;
17747
18477
  return {
17748
18478
  targetKind: CLOUD_RUN_TARGET_KIND,
17749
- targetName: packCloudRunTargetName({ serviceName, method, path: path70 }),
18479
+ targetName: packCloudRunTargetName({ serviceName, method, path: path72 }),
17750
18480
  callCount: 1,
17751
18481
  errorCount: isError ? 1 : 0,
17752
18482
  lastObservedIso: timestamp
@@ -17762,7 +18492,7 @@ function mapLogEntriesToSignals2(entries) {
17762
18492
  }
17763
18493
 
17764
18494
  // src/connectors/cloud-run/resolve.ts
17765
- 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";
17766
18496
  var CLOUD_RUN_SERVICE_INFRA_KIND = "cloud-run-service";
17767
18497
  function findMatchingRouteNode2(graph, serviceName, method, normalizedPath) {
17768
18498
  let found = null;
@@ -17784,21 +18514,21 @@ function createCloudRunResolveTarget(graph, config) {
17784
18514
  if (signal.targetKind !== CLOUD_RUN_TARGET_KIND) return null;
17785
18515
  const identity = parseCloudRunTargetName(signal.targetName);
17786
18516
  if (!identity) return null;
17787
- const { serviceName: gcpServiceName, method, path: path70 } = identity;
18517
+ const { serviceName: gcpServiceName, method, path: path72 } = identity;
17788
18518
  const mappedService = config.serviceMap?.[gcpServiceName];
17789
18519
  if (mappedService) {
17790
18520
  const routeNodeId = findMatchingRouteNode2(
17791
18521
  graph,
17792
18522
  mappedService,
17793
18523
  method,
17794
- normalizePathTemplate(path70)
18524
+ normalizePathTemplate(path72)
17795
18525
  );
17796
18526
  if (routeNodeId) {
17797
18527
  return { targetNodeId: routeNodeId, serviceName: mappedService, edgeType: EdgeType31.CALLS };
17798
18528
  }
17799
18529
  }
17800
18530
  return {
17801
- targetNodeId: infraId23(CLOUD_RUN_SERVICE_INFRA_KIND, gcpServiceName),
18531
+ targetNodeId: infraId24(CLOUD_RUN_SERVICE_INFRA_KIND, gcpServiceName),
17802
18532
  serviceName: mappedService ?? gcpServiceName,
17803
18533
  edgeType: EdgeType31.CALLS,
17804
18534
  ensureInfraNode: {
@@ -18130,17 +18860,17 @@ function mapInsightsToSignals(rows, observedAtIso) {
18130
18860
  }
18131
18861
 
18132
18862
  // src/connectors/planetscale/resolve.ts
18133
- import { EdgeType as EdgeType33, infraId as infraId24 } from "@neat.is/types";
18863
+ import { EdgeType as EdgeType33, infraId as infraId25 } from "@neat.is/types";
18134
18864
  var PLANETSCALE_DATABASE_KIND = "planetscale-database";
18135
18865
  function createPlanetscaleResolveTarget(graph, config) {
18136
18866
  const databaseName = `${config.organization}/${config.database}`;
18137
18867
  return (signal, _ctx) => {
18138
18868
  if (signal.targetKind !== PLANETSCALE_SQL_TABLE_TARGET_KIND || !signal.targetName) return null;
18139
- const tableId = infraId24("sql-table", signal.targetName);
18869
+ const tableId = infraId25("sql-table", signal.targetName);
18140
18870
  if (graph.hasNode(tableId)) {
18141
18871
  return { targetNodeId: tableId, serviceName: config.serviceName, edgeType: EdgeType33.CALLS };
18142
18872
  }
18143
- const providerId = infraId24(PLANETSCALE_DATABASE_KIND, databaseName);
18873
+ const providerId = infraId25(PLANETSCALE_DATABASE_KIND, databaseName);
18144
18874
  return {
18145
18875
  targetNodeId: providerId,
18146
18876
  serviceName: config.serviceName,
@@ -20040,4 +20770,4 @@ export {
20040
20770
  deprovisionConnector,
20041
20771
  buildApi
20042
20772
  };
20043
- //# sourceMappingURL=chunk-RDLDSIEY.js.map
20773
+ //# sourceMappingURL=chunk-UN7VFA4H.js.map