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

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.
@@ -3,7 +3,7 @@ import {
3
3
  mountBearerAuth,
4
4
  readAuthEnv,
5
5
  tableFromSqlStatement
6
- } from "./chunk-UUYCTH2E.js";
6
+ } from "./chunk-GDGUY4T6.js";
7
7
 
8
8
  // src/graph.ts
9
9
  import GraphDefault from "graphology";
@@ -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];
@@ -3526,6 +3831,14 @@ function grpcStatusCodeFromAttrs(attrs) {
3526
3831
  }
3527
3832
  return void 0;
3528
3833
  }
3834
+ function spanRecordsError(span) {
3835
+ if (span.statusCode === 2) return true;
3836
+ const grpc = grpcStatusCodeFromAttrs(span.attributes);
3837
+ if (grpc !== void 0 && grpc !== 0) return true;
3838
+ const httpStatus = httpResponseStatusFromAttrs(span.attributes);
3839
+ if (httpStatus !== void 0 && httpStatus >= 500) return true;
3840
+ return false;
3841
+ }
3529
3842
  function nonHttpFailureMessageFromAttrs(attrs) {
3530
3843
  const grpc = grpcStatusCodeFromAttrs(attrs);
3531
3844
  if (grpc !== void 0 && grpc !== 0) {
@@ -4324,6 +4637,21 @@ async function recordExceptionIncident(ctx, span, ts) {
4324
4637
  };
4325
4638
  await appendErrorEvent(ctx, ev);
4326
4639
  }
4640
+ async function recordGrpcFailureIncident(ctx, span, ts) {
4641
+ const attrs = sanitizeAttributes(span.attributes);
4642
+ const ev = {
4643
+ id: `${span.traceId}:${span.spanId}`,
4644
+ timestamp: ts,
4645
+ service: span.service,
4646
+ traceId: span.traceId,
4647
+ spanId: span.spanId,
4648
+ errorType: "grpc-failure",
4649
+ errorMessage: incidentMessage(span),
4650
+ ...Object.keys(attrs).length > 0 ? { attributes: attrs } : {},
4651
+ affectedNode: incidentAffectedNode(span, ctx.graph, ctx.scanPath)
4652
+ };
4653
+ await appendErrorEvent(ctx, ev);
4654
+ }
4327
4655
  async function advance4xxBurst(ctx, span, affectedNode, ts, nowMs, status) {
4328
4656
  const { threshold, windowMs } = loadIncidentThresholdsFromEnv();
4329
4657
  if (!ctx.burstState) ctx.burstState = /* @__PURE__ */ new Map();
@@ -4367,6 +4695,12 @@ async function advance4xxBurst(ctx, span, affectedNode, ts, nowMs, status) {
4367
4695
  );
4368
4696
  ctx.burstState.delete(key);
4369
4697
  }
4698
+ var NEXT_API_ROUTE_SPAN_NAME = /^executing api route \((?:pages|app)\) (\/\S*)$/;
4699
+ function nextApiRouteTemplate(span) {
4700
+ const raw = pickAttr(span, "next.span_name") ?? span.name;
4701
+ const match = raw ? NEXT_API_ROUTE_SPAN_NAME.exec(raw) : null;
4702
+ return match ? match[1] : void 0;
4703
+ }
4370
4704
  function findRouteNodeByHttpRoute(graph, serviceName, method, httpRoute) {
4371
4705
  const target = normalizePathTemplate(httpRoute);
4372
4706
  const m = method?.toUpperCase();
@@ -4388,8 +4722,8 @@ async function handleSpan(ctx, span) {
4388
4722
  warnUnidentifiedSpan(ctx.project ?? DEFAULT_PROJECT);
4389
4723
  }
4390
4724
  const sourceId = ensureServiceNode(ctx.graph, span.service, env);
4391
- const isError = span.statusCode === 2;
4392
- const durationMs = span.durationNanos > 0n ? Number(span.durationNanos) / 1e6 : void 0;
4725
+ const isError = spanRecordsError(span);
4726
+ const durationMs = span.durationNanos > 0n && !spanIsStreaming(span) ? Number(span.durationNanos) / 1e6 : void 0;
4393
4727
  const sourceServiceNode = ctx.graph.getNodeAttributes(sourceId);
4394
4728
  const callSite = callSiteFromSpan(span, sourceServiceNode, ctx.scanPath);
4395
4729
  cacheSpanService(span, nowMs, callSite);
@@ -4586,12 +4920,13 @@ async function handleSpan(ctx, span) {
4586
4920
  }
4587
4921
  }
4588
4922
  }
4589
- if (span.httpRoute && (span.kind === 2 || span.kind === 1 || span.kind === 0 || span.kind === void 0)) {
4923
+ const fusionRoute = nextApiRouteTemplate(span) ?? span.httpRoute;
4924
+ if (fusionRoute && (span.kind === 2 || span.kind === 1 || span.kind === 0 || span.kind === void 0)) {
4590
4925
  const routeNodeId = findRouteNodeByHttpRoute(
4591
4926
  ctx.graph,
4592
4927
  span.service,
4593
4928
  span.httpMethod,
4594
- span.httpRoute
4929
+ fusionRoute
4595
4930
  );
4596
4931
  if (routeNodeId) {
4597
4932
  const routeSvc = ctx.graph.getNodeAttributes(routeNodeId).service;
@@ -4623,10 +4958,13 @@ async function handleSpan(ctx, span) {
4623
4958
  }
4624
4959
  if (span.statusCode !== 2) {
4625
4960
  const status = httpResponseStatus(span);
4961
+ const grpcStatus = grpcStatusCodeFromAttrs(span.attributes);
4626
4962
  if (span.exception) {
4627
4963
  await recordExceptionIncident(ctx, span, ts);
4628
4964
  } else if (status !== void 0 && status >= 500) {
4629
4965
  await recordFailingResponseIncident(ctx, span, sourceId, ts, status, 1);
4966
+ } else if (grpcStatus !== void 0 && grpcStatus !== 0) {
4967
+ await recordGrpcFailureIncident(ctx, span, ts);
4630
4968
  } else if (status !== void 0 && status >= 400 && spanMintsObservedEdge(span.kind)) {
4631
4969
  await advance4xxBurst(ctx, span, sourceId, ts, nowMs, status);
4632
4970
  }
@@ -5008,19 +5346,19 @@ function confidenceFromMix(edges, now = Date.now()) {
5008
5346
  function longestIncomingWalk(graph, start, maxDepth) {
5009
5347
  let best = { path: [start], edges: [] };
5010
5348
  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] };
5349
+ function step(node, path72, edges) {
5350
+ if (path72.length > best.path.length) {
5351
+ best = { path: [...path72], edges: [...edges] };
5014
5352
  }
5015
- if (path70.length - 1 >= maxDepth) return;
5353
+ if (path72.length - 1 >= maxDepth) return;
5016
5354
  const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
5017
5355
  for (const [srcId, edge] of incoming) {
5018
5356
  if (visited.has(srcId)) continue;
5019
5357
  visited.add(srcId);
5020
- path70.push(srcId);
5358
+ path72.push(srcId);
5021
5359
  edges.push(edge);
5022
- step(srcId, path70, edges);
5023
- path70.pop();
5360
+ step(srcId, path72, edges);
5361
+ path72.pop();
5024
5362
  edges.pop();
5025
5363
  visited.delete(srcId);
5026
5364
  }
@@ -5028,11 +5366,11 @@ function longestIncomingWalk(graph, start, maxDepth) {
5028
5366
  step(start, [start], []);
5029
5367
  return best;
5030
5368
  }
5031
- function databaseRootCauseShape(graph, origin, walk9) {
5369
+ function databaseRootCauseShape(graph, origin, walk10) {
5032
5370
  const targetDb = origin;
5033
5371
  const candidatePairs = compatPairs().filter((p) => p.engine === targetDb.engine);
5034
5372
  if (candidatePairs.length === 0) return null;
5035
- for (const id of walk9.path) {
5373
+ for (const id of walk10.path) {
5036
5374
  const owner = resolveOwningService(graph, id);
5037
5375
  if (!owner) continue;
5038
5376
  const { id: serviceId15, svc } = owner;
@@ -5059,8 +5397,8 @@ function databaseRootCauseShape(graph, origin, walk9) {
5059
5397
  }
5060
5398
  return null;
5061
5399
  }
5062
- function serviceRootCauseShape(graph, _origin, walk9) {
5063
- for (const id of walk9.path) {
5400
+ function serviceRootCauseShape(graph, _origin, walk10) {
5401
+ for (const id of walk10.path) {
5064
5402
  const owner = resolveOwningService(graph, id);
5065
5403
  if (!owner) continue;
5066
5404
  const { id: serviceId15, svc } = owner;
@@ -5096,15 +5434,15 @@ function serviceRootCauseShape(graph, _origin, walk9) {
5096
5434
  }
5097
5435
  return null;
5098
5436
  }
5099
- function fileRootCauseShape(graph, origin, walk9) {
5437
+ function fileRootCauseShape(graph, origin, walk10) {
5100
5438
  const owner = resolveOwningService(graph, origin.id);
5101
5439
  if (!owner) return null;
5102
- return serviceRootCauseShape(graph, owner.svc, walk9);
5440
+ return serviceRootCauseShape(graph, owner.svc, walk10);
5103
5441
  }
5104
- function symbolRootCauseShape(graph, origin, walk9) {
5442
+ function symbolRootCauseShape(graph, origin, walk10) {
5105
5443
  const owner = resolveOwningService(graph, origin.id);
5106
5444
  if (!owner) return null;
5107
- return serviceRootCauseShape(graph, owner.svc, walk9);
5445
+ return serviceRootCauseShape(graph, owner.svc, walk10);
5108
5446
  }
5109
5447
  var rootCauseShapes = {
5110
5448
  [NodeType5.DatabaseNode]: databaseRootCauseShape,
@@ -5117,25 +5455,29 @@ function legacyRootCause(graph, errorNodeId, errorEvent, incidents) {
5117
5455
  const origin = graph.getNodeAttributes(errorNodeId);
5118
5456
  const shape = rootCauseShapes[origin.type];
5119
5457
  if (shape) {
5120
- const walk9 = longestIncomingWalk(graph, errorNodeId, ROOT_CAUSE_MAX_DEPTH);
5121
- const match = shape(graph, origin, walk9);
5458
+ const walk10 = longestIncomingWalk(graph, errorNodeId, ROOT_CAUSE_MAX_DEPTH);
5459
+ const match = shape(graph, origin, walk10);
5122
5460
  if (match) {
5123
5461
  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
- });
5462
+ return {
5463
+ source: "compat",
5464
+ result: RootCauseResultSchema.parse({
5465
+ rootCauseNode: match.rootCauseNode,
5466
+ rootCauseReason: reason,
5467
+ traversalPath: walk10.path,
5468
+ edgeProvenances: walk10.edges.map((e) => e.provenance),
5469
+ confidence: confidenceFromMix(walk10.edges),
5470
+ fixRecommendation: match.fixRecommendation
5471
+ })
5472
+ };
5132
5473
  }
5133
5474
  }
5134
5475
  if (origin.type === NodeType5.ServiceNode) {
5135
5476
  const crossService = crossServiceRootCause(graph, errorNodeId, incidents, errorEvent);
5136
- if (crossService) return crossService;
5477
+ if (crossService) return { result: crossService, source: "cross-service" };
5137
5478
  }
5138
- return rootCauseFromIncidents(errorNodeId, incidents, errorEvent);
5479
+ const incident = rootCauseFromIncidents(errorNodeId, incidents, errorEvent);
5480
+ return incident ? { result: incident, source: "incident" } : null;
5139
5481
  }
5140
5482
  var INCIDENT_ROOT_CAUSE_CONFIDENCE = 0.6;
5141
5483
  function incidentMatchesNode(ev, nodeId) {
@@ -5231,26 +5573,75 @@ function dominantFailingCall(graph, serviceId15, visited) {
5231
5573
  return best;
5232
5574
  }
5233
5575
  function followFailingCallChain(graph, originServiceId, maxDepth) {
5234
- const path70 = [originServiceId];
5576
+ const path72 = [originServiceId];
5235
5577
  const edges = [];
5236
5578
  const visited = /* @__PURE__ */ new Set([originServiceId]);
5237
5579
  let current = originServiceId;
5238
5580
  for (let depth = 0; depth < maxDepth; depth++) {
5239
5581
  const hop = dominantFailingCall(graph, current, visited);
5240
5582
  if (!hop) break;
5241
- path70.push(hop.nextService);
5583
+ path72.push(hop.nextService);
5242
5584
  edges.push(hop.edge);
5243
5585
  visited.add(hop.nextService);
5244
5586
  current = hop.nextService;
5245
5587
  }
5246
5588
  if (edges.length === 0) return null;
5247
- return { path: path70, edges, culprit: current };
5589
+ return { path: path72, edges, culprit: current };
5590
+ }
5591
+ function isStaleCallEdge(e) {
5592
+ return e.type === EdgeType6.CALLS && e.provenance === Provenance6.STALE;
5593
+ }
5594
+ function staleCallDominates(e, id, curEdge, curId) {
5595
+ const ev = e.signal?.spanCount ?? e.callCount ?? 0;
5596
+ const cv = curEdge.signal?.spanCount ?? curEdge.callCount ?? 0;
5597
+ if (ev !== cv) return ev > cv;
5598
+ return id < curId;
5599
+ }
5600
+ function dominantStaleCall(graph, serviceId15, visited) {
5601
+ const bestByCallee = /* @__PURE__ */ new Map();
5602
+ for (const src of callSourcesForService(graph, serviceId15)) {
5603
+ for (const edgeId of graph.outboundEdges(src)) {
5604
+ const e = graph.getEdgeAttributes(edgeId);
5605
+ if (e.type !== EdgeType6.CALLS) continue;
5606
+ if (isFrontierNode(graph, e.target)) continue;
5607
+ const owner = resolveOwningService(graph, e.target);
5608
+ if (!owner || visited.has(owner.id)) continue;
5609
+ const cur = bestByCallee.get(owner.id);
5610
+ if (!cur || PROV_RANK[e.provenance] > PROV_RANK[cur.provenance]) {
5611
+ bestByCallee.set(owner.id, e);
5612
+ }
5613
+ }
5614
+ }
5615
+ let best = null;
5616
+ for (const [id, edge] of bestByCallee) {
5617
+ if (!isStaleCallEdge(edge)) continue;
5618
+ if (!best || staleCallDominates(edge, id, best.edge, best.nextService)) {
5619
+ best = { nextService: id, edge };
5620
+ }
5621
+ }
5622
+ return best;
5623
+ }
5624
+ function followStaleCallChain(graph, originServiceId, maxDepth) {
5625
+ const path72 = [originServiceId];
5626
+ const edges = [];
5627
+ const visited = /* @__PURE__ */ new Set([originServiceId]);
5628
+ let current = originServiceId;
5629
+ for (let depth = 0; depth < maxDepth; depth++) {
5630
+ const hop = dominantStaleCall(graph, current, visited);
5631
+ if (!hop) break;
5632
+ path72.push(hop.nextService);
5633
+ edges.push(hop.edge);
5634
+ visited.add(hop.nextService);
5635
+ current = hop.nextService;
5636
+ }
5637
+ if (edges.length === 0) return null;
5638
+ return { path: path72, edges, culprit: current };
5248
5639
  }
5249
5640
  function crossServiceRootCause(graph, originId, incidents, errorEvent) {
5250
5641
  const chain = followFailingCallChain(graph, originId, ROOT_CAUSE_MAX_DEPTH);
5251
5642
  if (!chain) return null;
5252
5643
  const culprit = chain.culprit;
5253
- const path70 = [...chain.path];
5644
+ const path72 = [...chain.path];
5254
5645
  const edgeProvenances = chain.edges.map((e) => e.provenance);
5255
5646
  const baseConfidence = confidenceFromMix(chain.edges);
5256
5647
  const confidence = Math.max(0, Math.min(1, baseConfidence * INCIDENT_ROOT_CAUSE_CONFIDENCE));
@@ -5258,14 +5649,14 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
5258
5649
  if (loc) {
5259
5650
  let rootCauseNode = culprit;
5260
5651
  if (loc.fileNode) {
5261
- path70.push(loc.fileNode);
5652
+ path72.push(loc.fileNode);
5262
5653
  edgeProvenances.push(Provenance6.OBSERVED);
5263
5654
  rootCauseNode = loc.fileNode;
5264
5655
  }
5265
5656
  return RootCauseResultSchema.parse({
5266
5657
  rootCauseNode,
5267
5658
  rootCauseReason: loc.rootCauseReason,
5268
- traversalPath: path70,
5659
+ traversalPath: path72,
5269
5660
  edgeProvenances,
5270
5661
  confidence,
5271
5662
  ...loc.fixRecommendation ? { fixRecommendation: loc.fixRecommendation } : {}
@@ -5277,7 +5668,7 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
5277
5668
  return RootCauseResultSchema.parse({
5278
5669
  rootCauseNode: culprit,
5279
5670
  rootCauseReason: `${culpritName} is failing downstream calls (${errs} observed error${errs === 1 ? "" : "s"})`,
5280
- traversalPath: path70,
5671
+ traversalPath: path72,
5281
5672
  edgeProvenances,
5282
5673
  confidence,
5283
5674
  fixRecommendation: `Inspect ${culpritName}'s failing handler`
@@ -5664,17 +6055,20 @@ function displayNameOf(nodeId) {
5664
6055
  return nodeId.replace(/^[a-z]+:/, "");
5665
6056
  }
5666
6057
  function getRootCause(graph, errorNodeId, errorEvent, incidents, opts) {
5667
- const legacy = legacyRootCause(graph, errorNodeId, errorEvent, incidents);
5668
- if (!legacy) return null;
6058
+ const tagged = legacyRootCause(graph, errorNodeId, errorEvent, incidents);
6059
+ if (!tagged) return null;
5669
6060
  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());
6061
+ if (!navigation) return tagged.result;
6062
+ return enrichWithNavigation(graph, errorNodeId, tagged, incidents, opts?.now ?? Date.now());
5672
6063
  }
5673
- function enrichWithNavigation(graph, errorNodeId, legacy, incidents, now) {
6064
+ function enrichWithNavigation(graph, errorNodeId, tagged, incidents, now) {
6065
+ const legacy = tagged.result;
5674
6066
  const seedNode = legacy.rootCauseNode;
5675
6067
  const seedCtx = graph.hasNode(seedNode) ? nodeContext(graph, seedNode, incidents, now) : null;
5676
6068
  const lastProv = legacy.edgeProvenances[legacy.edgeProvenances.length - 1];
5677
6069
  const candidates = [];
6070
+ const deadEndOnSymptom = tagged.source === "incident" && seedNode === errorNodeId && legacy.traversalPath.length === 1;
6071
+ const staleChain = deadEndOnSymptom && !(seedCtx && isVictimSeed(seedCtx)) ? followStaleCallChain(graph, errorNodeId, ROOT_CAUSE_MAX_DEPTH) : null;
5678
6072
  if (seedCtx && isVictimSeed(seedCtx)) {
5679
6073
  const origin = findLoadOrigin(graph, errorNodeId, incidents, now);
5680
6074
  const staleNote = seedCtx.stale ? "; it has gone STALE under load" : "";
@@ -5699,6 +6093,27 @@ function enrichWithNavigation(graph, errorNodeId, legacy, incidents, now) {
5699
6093
  confidence: Math.min(legacy.confidence, 0.4),
5700
6094
  ...lastProv ? { provenance: lastProv } : {}
5701
6095
  });
6096
+ } else if (staleChain) {
6097
+ const culprit = staleChain.culprit;
6098
+ const culpritName = displayNameOf(culprit);
6099
+ const seedName = displayNameOf(seedNode);
6100
+ const staleConfidence = confidenceFromMix(staleChain.edges, now);
6101
+ candidates.push({
6102
+ node: culprit,
6103
+ classification: "primary-failure",
6104
+ 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.`,
6105
+ context: nodeContext(graph, culprit, incidents, now),
6106
+ confidence: staleConfidence,
6107
+ provenance: Provenance6.STALE
6108
+ });
6109
+ candidates.push({
6110
+ node: seedNode,
6111
+ classification: "symptom-only",
6112
+ 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.`,
6113
+ context: seedCtx ?? EMPTY_CONTEXT,
6114
+ confidence: Math.min(legacy.confidence, PROVENANCE_CEILING.STALE),
6115
+ ...lastProv ? { provenance: lastProv } : {}
6116
+ });
5702
6117
  } else {
5703
6118
  candidates.push({
5704
6119
  node: seedNode,
@@ -5712,11 +6127,14 @@ function enrichWithNavigation(graph, errorNodeId, legacy, incidents, now) {
5712
6127
  const top = candidates[0];
5713
6128
  let traversalPath = legacy.traversalPath;
5714
6129
  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);
6130
+ if (staleChain && top.node === staleChain.culprit) {
6131
+ traversalPath = staleChain.path;
6132
+ edgeProvenances = staleChain.edges.map((e) => e.provenance);
6133
+ } else if (top.node !== seedNode) {
6134
+ const path72 = findPath(graph, errorNodeId, top.node, "up", ROOT_CAUSE_MAX_DEPTH);
6135
+ if (path72) {
6136
+ traversalPath = path72.nodes;
6137
+ edgeProvenances = path72.edges.map((e) => e.provenance);
5720
6138
  } else {
5721
6139
  traversalPath = [errorNodeId, top.node];
5722
6140
  edgeProvenances = [top.provenance ?? Provenance6.OBSERVED];
@@ -5738,6 +6156,9 @@ function fixRecommendationForTop(top, seedNode, legacy) {
5738
6156
  return legacy.fixRecommendation;
5739
6157
  }
5740
6158
  const name = top.node.replace(/^service:/, "");
6159
+ if (top.provenance === Provenance6.STALE) {
6160
+ 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}.`;
6161
+ }
5741
6162
  if (top.classification === "primary-failure") {
5742
6163
  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
6164
  }
@@ -6827,7 +7248,7 @@ import Php2 from "tree-sitter-php";
6827
7248
  import CSharp from "tree-sitter-c-sharp";
6828
7249
  import Java from "tree-sitter-java";
6829
7250
  import Kotlin from "tree-sitter-kotlin";
6830
- import Rust from "tree-sitter-rust";
7251
+ import Rust2 from "tree-sitter-rust";
6831
7252
  import Cpp from "tree-sitter-cpp";
6832
7253
  import {
6833
7254
  EdgeType as EdgeType7,
@@ -6855,7 +7276,7 @@ var SYMBOL_GRAMMAR_BY_EXT = {
6855
7276
  ".cs": CSharp,
6856
7277
  ".java": Java,
6857
7278
  ".kt": Kotlin,
6858
- ".rs": Rust,
7279
+ ".rs": Rust2,
6859
7280
  // C++ (ADR-202) — only the UNAMBIGUOUS extensions. `.cpp` / `.cc` / `.cxx` /
6860
7281
  // `.c++` are implementation files; `.hpp` / `.hh` / `.hxx` / `.h++` are C++-only
6861
7282
  // headers. `.h` and `.c` are deliberately absent: they are shared with C (a
@@ -7295,15 +7716,15 @@ function collectKotlinSymbolDefs(root) {
7295
7716
  });
7296
7717
  };
7297
7718
  const join = (prefix, name) => prefix ? `${prefix}.${name}` : name;
7298
- const firstChildOfType = (node, types) => {
7719
+ const firstChildOfType2 = (node, types) => {
7299
7720
  for (let i = 0; i < node.namedChildCount; i++) {
7300
7721
  const child = node.namedChild(i);
7301
7722
  if (child && types.includes(child.type)) return child;
7302
7723
  }
7303
7724
  return void 0;
7304
7725
  };
7305
- const nameOf = (node, ...types) => firstChildOfType(node, types)?.text;
7306
- const bodyOf = (node) => firstChildOfType(node, ["class_body", "enum_class_body"]);
7726
+ const nameOf = (node, ...types) => firstChildOfType2(node, types)?.text;
7727
+ const bodyOf = (node) => firstChildOfType2(node, ["class_body", "enum_class_body"]);
7307
7728
  let pkg;
7308
7729
  for (let i = 0; i < root.namedChildCount; i++) {
7309
7730
  const child = root.namedChild(i);
@@ -7810,7 +8231,7 @@ async function addSymbolEdges(graph, services) {
7810
8231
  return best;
7811
8232
  };
7812
8233
  const requests = [];
7813
- const walk9 = (node) => {
8234
+ const walk10 = (node) => {
7814
8235
  if (node.type === "class_declaration" || node.type === "abstract_class_declaration" || node.type === "class") {
7815
8236
  const self = localBySpan.get(`${node.startPosition.row + 1}:${node.endPosition.row + 1}`);
7816
8237
  if (self && self.kind === "class") {
@@ -7856,10 +8277,10 @@ async function addSymbolEdges(graph, services) {
7856
8277
  }
7857
8278
  for (let i = 0; i < node.namedChildCount; i++) {
7858
8279
  const child = node.namedChild(i);
7859
- if (child) walk9(child);
8280
+ if (child) walk10(child);
7860
8281
  }
7861
8282
  };
7862
- walk9(root);
8283
+ walk10(root);
7863
8284
  for (const req of requests) {
7864
8285
  const targetSid = resolveTarget(req.targetName, req.wantKind);
7865
8286
  if (!targetSid) continue;
@@ -8160,7 +8581,7 @@ async function addServerActions(graph, services) {
8160
8581
  }
8161
8582
 
8162
8583
  // src/extract/databases/index.ts
8163
- import path33 from "path";
8584
+ import path34 from "path";
8164
8585
  import {
8165
8586
  EdgeType as EdgeType10,
8166
8587
  NodeType as NodeType20,
@@ -8593,8 +9014,161 @@ async function parse8(serviceDir) {
8593
9014
  }
8594
9015
  var sequelizeParser = { name: "sequelize", parse: parse8 };
8595
9016
 
8596
- // src/extract/databases/docker-compose.ts
9017
+ // src/extract/databases/csharp.ts
9018
+ import { promises as fs22 } from "fs";
8597
9019
  import path32 from "path";
9020
+ var CS_EXT = ".cs";
9021
+ var NPGSQL_GATE = /\bUseNpgsql\b|\bNpgsql\b/;
9022
+ var REDIS_GATE = /\bConnectionMultiplexer\b|\bStackExchange\.Redis\b|\bConfigurationOptions\.Parse\b|\bAddStackExchangeRedisCache\b/;
9023
+ var ENV_READ_RE = /(?:GetEnvironmentVariable|GetConnectionString)\(\s*"([^"]+)"\s*\)|Configuration\s*\[\s*"([^"]+)"\s*\]/g;
9024
+ var STRING_LITERAL_RE = /@?"([^"\\]*(?:\\.[^"\\]*)*)"/g;
9025
+ function hostIsUnresolved(host) {
9026
+ return host === "" || /[${}]/.test(host);
9027
+ }
9028
+ function looksLikePostgres(s) {
9029
+ return /(?:^|;)\s*(?:host|server|data\s*source)\s*=/i.test(s) || /^postgres(?:ql)?:\/\//i.test(s);
9030
+ }
9031
+ function looksLikeRedis(s) {
9032
+ return /^rediss?:\/\//i.test(s) || /^[A-Za-z0-9_.-]+:\d+(?:$|,)/.test(s) || /,\s*(?:ssl|abortconnect|allowadmin|connecttimeout|password|user)\s*=/i.test(s);
9033
+ }
9034
+ function parsePostgresConnection(raw) {
9035
+ const s = raw.trim();
9036
+ if (/^postgres(?:ql)?:\/\//i.test(s)) return parseConnectionString(s);
9037
+ const fields = /* @__PURE__ */ new Map();
9038
+ for (const part of s.split(";")) {
9039
+ const eq = part.indexOf("=");
9040
+ if (eq < 0) continue;
9041
+ const key = part.slice(0, eq).trim().toLowerCase().replace(/\s+/g, " ");
9042
+ const value = part.slice(eq + 1).trim();
9043
+ if (value && !fields.has(key)) fields.set(key, value);
9044
+ }
9045
+ const hostRaw = fields.get("host") ?? fields.get("server") ?? fields.get("data source");
9046
+ if (!hostRaw) return null;
9047
+ const host = hostRaw.split(",")[0].trim();
9048
+ if (hostIsUnresolved(host)) return null;
9049
+ const portRaw = fields.get("port");
9050
+ const port = portRaw && /^\d+$/.test(portRaw) ? Number(portRaw) : void 0;
9051
+ const database = fields.get("database") ?? fields.get("db") ?? "";
9052
+ return { host, port, database, engine: "postgresql", engineVersion: "unknown" };
9053
+ }
9054
+ function parseRedisEndpoint(raw) {
9055
+ const s = raw.trim();
9056
+ if (/^rediss?:\/\//i.test(s)) return parseConnectionString(s);
9057
+ const first = s.split(",")[0].trim();
9058
+ const m = first.match(/^([A-Za-z0-9_.-]+)(?::(\d+))?$/);
9059
+ if (!m) return null;
9060
+ const host = m[1];
9061
+ if (hostIsUnresolved(host) || host.includes("=")) return null;
9062
+ const port = m[2] ? Number(m[2]) : void 0;
9063
+ return { host, port, database: "", engine: "redis", engineVersion: "unknown" };
9064
+ }
9065
+ async function resolveEnvUpTree(startDir, name) {
9066
+ let dir = path32.resolve(startDir);
9067
+ for (let depth = 0; depth < 12; depth++) {
9068
+ const value = await resolveEnvVar(dir, name);
9069
+ if (value !== null) return value;
9070
+ const atRepoRoot = await fs22.access(path32.join(dir, ".git")).then(() => true).catch(() => false);
9071
+ const parent = path32.dirname(dir);
9072
+ if (atRepoRoot || parent === dir) break;
9073
+ dir = parent;
9074
+ }
9075
+ return null;
9076
+ }
9077
+ async function interpolateEnvRefs(value, dir) {
9078
+ const refs = /* @__PURE__ */ new Set();
9079
+ for (const m of value.matchAll(/\$\{([A-Za-z_][A-Za-z0-9_]*)\}|\$([A-Za-z_][A-Za-z0-9_]*)/g)) {
9080
+ refs.add(m[1] ?? m[2]);
9081
+ }
9082
+ let out = value;
9083
+ for (const name of refs) {
9084
+ const resolved = await resolveEnvUpTree(dir, name);
9085
+ if (resolved === null) continue;
9086
+ out = out.split(`\${${name}}`).join(resolved).replace(new RegExp(`\\$${name}\\b`, "g"), resolved);
9087
+ }
9088
+ return out;
9089
+ }
9090
+ function stringLiterals(masked) {
9091
+ const out = [];
9092
+ STRING_LITERAL_RE.lastIndex = 0;
9093
+ let m;
9094
+ while ((m = STRING_LITERAL_RE.exec(masked)) !== null) out.push(m[1]);
9095
+ return out;
9096
+ }
9097
+ function envKeys(masked) {
9098
+ const out = [];
9099
+ ENV_READ_RE.lastIndex = 0;
9100
+ let m;
9101
+ while ((m = ENV_READ_RE.exec(masked)) !== null) {
9102
+ const key = m[1] ?? m[2];
9103
+ if (key) out.push(key);
9104
+ }
9105
+ return out;
9106
+ }
9107
+ async function resolveConfigs(literals, keys, serviceDir, looksLike, parse11) {
9108
+ const out = [];
9109
+ for (const key of keys) {
9110
+ const raw = await resolveEnvUpTree(serviceDir, key);
9111
+ if (raw === null) continue;
9112
+ const value = await interpolateEnvRefs(raw, serviceDir);
9113
+ if (!looksLike(value)) continue;
9114
+ const parsed = parse11(value);
9115
+ if (parsed) out.push(parsed);
9116
+ }
9117
+ for (const lit of literals) {
9118
+ if (!looksLike(lit)) continue;
9119
+ const parsed = parse11(lit);
9120
+ if (parsed) out.push(parsed);
9121
+ }
9122
+ return out;
9123
+ }
9124
+ async function parse9(serviceDir) {
9125
+ const files = (await walkSourceFiles(serviceDir).catch(() => [])).filter(
9126
+ (f) => path32.extname(f) === CS_EXT
9127
+ );
9128
+ if (files.length === 0) return [];
9129
+ const sources = [];
9130
+ for (const file of files) {
9131
+ const content = await fs22.readFile(file, "utf8").catch(() => null);
9132
+ if (content !== null) sources.push({ file, content });
9133
+ }
9134
+ let pgGateFile = null;
9135
+ let redisGateFile = null;
9136
+ for (const { file, content } of sources) {
9137
+ if (pgGateFile === null && NPGSQL_GATE.test(content)) pgGateFile = file;
9138
+ if (redisGateFile === null && REDIS_GATE.test(content)) redisGateFile = file;
9139
+ }
9140
+ if (!pgGateFile && !redisGateFile) return [];
9141
+ const literals = [];
9142
+ const keys = [];
9143
+ for (const { content } of sources) {
9144
+ const masked = maskCommentsInSource(content);
9145
+ literals.push(...stringLiterals(masked));
9146
+ keys.push(...envKeys(masked));
9147
+ }
9148
+ const out = [];
9149
+ const seenHosts = /* @__PURE__ */ new Set();
9150
+ const push = (config, sourceFile) => {
9151
+ const dedupe = `${config.engine}:${config.host}`;
9152
+ if (seenHosts.has(dedupe)) return;
9153
+ seenHosts.add(dedupe);
9154
+ out.push({ ...config, sourceFile });
9155
+ };
9156
+ if (pgGateFile) {
9157
+ for (const pg3 of await resolveConfigs(literals, keys, serviceDir, looksLikePostgres, parsePostgresConnection)) {
9158
+ push(pg3, pgGateFile);
9159
+ }
9160
+ }
9161
+ if (redisGateFile) {
9162
+ for (const redis of await resolveConfigs(literals, keys, serviceDir, looksLikeRedis, parseRedisEndpoint)) {
9163
+ push(redis, redisGateFile);
9164
+ }
9165
+ }
9166
+ return out;
9167
+ }
9168
+ var csharpParser = { name: "csharp", parse: parse9 };
9169
+
9170
+ // src/extract/databases/docker-compose.ts
9171
+ import path33 from "path";
8598
9172
  function portFromService(svc) {
8599
9173
  for (const raw of svc.ports ?? []) {
8600
9174
  const str = String(raw);
@@ -8619,9 +9193,9 @@ function databaseFromEnv(svc) {
8619
9193
  };
8620
9194
  return get("POSTGRES_DB") ?? get("MYSQL_DATABASE") ?? get("MONGO_INITDB_DATABASE") ?? "";
8621
9195
  }
8622
- async function parse9(serviceDir) {
9196
+ async function parse10(serviceDir) {
8623
9197
  for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
8624
- const abs = path32.join(serviceDir, name);
9198
+ const abs = path33.join(serviceDir, name);
8625
9199
  if (!await exists(abs)) continue;
8626
9200
  const raw = await readYaml(abs);
8627
9201
  if (!raw?.services) return [];
@@ -8643,7 +9217,7 @@ async function parse9(serviceDir) {
8643
9217
  }
8644
9218
  return [];
8645
9219
  }
8646
- var dockerComposeParser = { name: "docker-compose", parse: parse9 };
9220
+ var dockerComposeParser = { name: "docker-compose", parse: parse10 };
8647
9221
 
8648
9222
  // src/extract/databases/index.ts
8649
9223
  var DB_PARSERS = [
@@ -8655,6 +9229,7 @@ var DB_PARSERS = [
8655
9229
  ormconfigParser,
8656
9230
  typeormParser,
8657
9231
  sequelizeParser,
9232
+ csharpParser,
8658
9233
  dockerComposeParser
8659
9234
  ];
8660
9235
  function compatibleDriversFor(engine) {
@@ -8793,7 +9368,7 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
8793
9368
  discoveredVia: mergedDiscoveredVia
8794
9369
  });
8795
9370
  }
8796
- const relConfigFile = toPosix(path33.relative(service.dir, config.sourceFile));
9371
+ const relConfigFile = toPosix(path34.relative(service.dir, config.sourceFile));
8797
9372
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
8798
9373
  graph,
8799
9374
  service.pkg.name,
@@ -8802,7 +9377,7 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
8802
9377
  );
8803
9378
  nodesAdded += fn;
8804
9379
  edgesAdded += fe;
8805
- const evidenceFile = toPosix(path33.relative(scanPath, config.sourceFile));
9380
+ const evidenceFile = toPosix(path34.relative(scanPath, config.sourceFile));
8806
9381
  const edge = {
8807
9382
  id: extractedEdgeId(fileNodeId, dbNode.id, EdgeType10.CONNECTS_TO),
8808
9383
  source: fileNodeId,
@@ -8820,15 +9395,15 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
8820
9395
  if (allConfigs.length === 1) {
8821
9396
  const primary = allConfigs[0];
8822
9397
  service.node.dbConnectionTarget = primary.port ? `${primary.host}:${primary.port}` : primary.host;
8823
- const relPath = path33.relative(scanPath, primary.sourceFile);
9398
+ const relPath = path34.relative(scanPath, primary.sourceFile);
8824
9399
  const cfgId = configId(relPath);
8825
9400
  if (!graph.hasNode(cfgId)) {
8826
9401
  const cfgNode = {
8827
9402
  id: cfgId,
8828
9403
  type: NodeType20.ConfigNode,
8829
- name: path33.basename(primary.sourceFile),
9404
+ name: path34.basename(primary.sourceFile),
8830
9405
  path: relPath,
8831
- fileType: isConfigFile(path33.basename(primary.sourceFile)).fileType || "config"
9406
+ fileType: isConfigFile(path34.basename(primary.sourceFile)).fileType || "config"
8832
9407
  };
8833
9408
  graph.addNode(cfgId, cfgNode);
8834
9409
  nodesAdded++;
@@ -8868,8 +9443,8 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
8868
9443
  }
8869
9444
 
8870
9445
  // src/extract/configs.ts
8871
- import { promises as fs22 } from "fs";
8872
- import path34 from "path";
9446
+ import { promises as fs23 } from "fs";
9447
+ import path35 from "path";
8873
9448
  import {
8874
9449
  EdgeType as EdgeType11,
8875
9450
  NodeType as NodeType21,
@@ -8878,23 +9453,23 @@ import {
8878
9453
  confidenceForExtracted as confidenceForExtracted8
8879
9454
  } from "@neat.is/types";
8880
9455
  async function walkConfigFiles(dir, excludeDirs = []) {
8881
- const excluded = new Set(excludeDirs.map((d) => path34.resolve(d)));
9456
+ const excluded = new Set(excludeDirs.map((d) => path35.resolve(d)));
8882
9457
  const out = [];
8883
- async function walk9(current) {
8884
- const entries = await fs22.readdir(current, { withFileTypes: true });
9458
+ async function walk10(current) {
9459
+ const entries = await fs23.readdir(current, { withFileTypes: true });
8885
9460
  for (const entry of entries) {
8886
- const full = path34.join(current, entry.name);
9461
+ const full = path35.join(current, entry.name);
8887
9462
  if (entry.isDirectory()) {
8888
9463
  if (IGNORED_DIRS.has(entry.name)) continue;
8889
- if (excluded.has(path34.resolve(full))) continue;
9464
+ if (excluded.has(path35.resolve(full))) continue;
8890
9465
  if (await isPythonVenvDir(full)) continue;
8891
- await walk9(full);
9466
+ await walk10(full);
8892
9467
  } else if (entry.isFile() && isConfigFile(entry.name).match) {
8893
9468
  out.push(full);
8894
9469
  }
8895
9470
  }
8896
9471
  }
8897
- await walk9(dir);
9472
+ await walk10(dir);
8898
9473
  return out;
8899
9474
  }
8900
9475
  async function addConfigNodes(graph, services, scanPath) {
@@ -8903,19 +9478,19 @@ async function addConfigNodes(graph, services, scanPath) {
8903
9478
  for (const service of services) {
8904
9479
  const configFiles = await walkConfigFiles(service.dir, service.excludeDirs);
8905
9480
  for (const file of configFiles) {
8906
- const relPath = path34.relative(scanPath, file);
9481
+ const relPath = path35.relative(scanPath, file);
8907
9482
  const node = {
8908
9483
  id: configId2(relPath),
8909
9484
  type: NodeType21.ConfigNode,
8910
- name: path34.basename(file),
9485
+ name: path35.basename(file),
8911
9486
  path: relPath,
8912
- fileType: isConfigFile(path34.basename(file)).fileType
9487
+ fileType: isConfigFile(path35.basename(file)).fileType
8913
9488
  };
8914
9489
  if (!graph.hasNode(node.id)) {
8915
9490
  graph.addNode(node.id, node);
8916
9491
  nodesAdded++;
8917
9492
  }
8918
- const relToService = toPosix(path34.relative(service.dir, file));
9493
+ const relToService = toPosix(path35.relative(service.dir, file));
8919
9494
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
8920
9495
  graph,
8921
9496
  service.pkg.name,
@@ -8931,7 +9506,7 @@ async function addConfigNodes(graph, services, scanPath) {
8931
9506
  type: EdgeType11.CONFIGURED_BY,
8932
9507
  provenance: Provenance11.EXTRACTED,
8933
9508
  confidence: confidenceForExtracted8("structural"),
8934
- evidence: { file: relPath.split(path34.sep).join("/") }
9509
+ evidence: { file: relPath.split(path35.sep).join("/") }
8935
9510
  };
8936
9511
  if (!graph.hasEdge(edge.id)) {
8937
9512
  graph.addEdgeWithKey(edge.id, edge.source, edge.target, edge);
@@ -8943,8 +9518,8 @@ async function addConfigNodes(graph, services, scanPath) {
8943
9518
  }
8944
9519
 
8945
9520
  // src/extract/proto.ts
8946
- import { promises as fs23 } from "fs";
8947
- import path35 from "path";
9521
+ import { promises as fs24 } from "fs";
9522
+ import path36 from "path";
8948
9523
  import {
8949
9524
  EdgeType as EdgeType12,
8950
9525
  NodeType as NodeType22,
@@ -8989,23 +9564,23 @@ function grpcMethodsFromProto(content, fqPackage) {
8989
9564
  return out;
8990
9565
  }
8991
9566
  async function walkProtoFiles(dir, excludeDirs = []) {
8992
- const excluded = new Set(excludeDirs.map((d) => path35.resolve(d)));
9567
+ const excluded = new Set(excludeDirs.map((d) => path36.resolve(d)));
8993
9568
  const out = [];
8994
- async function walk9(current) {
8995
- const entries = await fs23.readdir(current, { withFileTypes: true }).catch(() => []);
9569
+ async function walk10(current) {
9570
+ const entries = await fs24.readdir(current, { withFileTypes: true }).catch(() => []);
8996
9571
  for (const entry of entries) {
8997
- const full = path35.join(current, entry.name);
9572
+ const full = path36.join(current, entry.name);
8998
9573
  if (entry.isDirectory()) {
8999
9574
  if (IGNORED_DIRS.has(entry.name)) continue;
9000
- if (excluded.has(path35.resolve(full))) continue;
9575
+ if (excluded.has(path36.resolve(full))) continue;
9001
9576
  if (await isPythonVenvDir(full)) continue;
9002
- await walk9(full);
9003
- } else if (entry.isFile() && path35.extname(entry.name) === PROTO_EXTENSION) {
9577
+ await walk10(full);
9578
+ } else if (entry.isFile() && path36.extname(entry.name) === PROTO_EXTENSION) {
9004
9579
  out.push(full);
9005
9580
  }
9006
9581
  }
9007
9582
  }
9008
- await walk9(dir);
9583
+ await walk10(dir);
9009
9584
  return out;
9010
9585
  }
9011
9586
  async function addGrpcMethods(graph, services) {
@@ -9015,10 +9590,10 @@ async function addGrpcMethods(graph, services) {
9015
9590
  const protoPaths = await walkProtoFiles(service.dir, service.excludeDirs);
9016
9591
  for (const protoPath of protoPaths) {
9017
9592
  if (isTestPath(protoPath)) continue;
9018
- const relFile = toPosix(path35.relative(service.dir, protoPath));
9593
+ const relFile = toPosix(path36.relative(service.dir, protoPath));
9019
9594
  let content;
9020
9595
  try {
9021
- content = await fs23.readFile(protoPath, "utf8");
9596
+ content = await fs24.readFile(protoPath, "utf8");
9022
9597
  } catch (err) {
9023
9598
  recordExtractionError("proto extraction", protoPath, err);
9024
9599
  continue;
@@ -9081,7 +9656,7 @@ import {
9081
9656
  } from "@neat.is/types";
9082
9657
 
9083
9658
  // src/extract/calls/http.ts
9084
- import path36 from "path";
9659
+ import path37 from "path";
9085
9660
  import Parser6 from "tree-sitter";
9086
9661
  import JavaScript4 from "tree-sitter-javascript";
9087
9662
  import TypeScript2 from "tree-sitter-typescript";
@@ -9169,7 +9744,7 @@ async function addHttpCallEdges(graph, services) {
9169
9744
  const seen = /* @__PURE__ */ new Set();
9170
9745
  for (const file of files) {
9171
9746
  if (isTestPath(file.path)) continue;
9172
- const parser = parserForExt(path36.extname(file.path), parserCache);
9747
+ const parser = parserForExt(path37.extname(file.path), parserCache);
9173
9748
  let sites;
9174
9749
  try {
9175
9750
  sites = callsFromSource(file.content, parser, knownHosts);
@@ -9178,7 +9753,7 @@ async function addHttpCallEdges(graph, services) {
9178
9753
  continue;
9179
9754
  }
9180
9755
  if (sites.length === 0) continue;
9181
- const relFile = toPosix(path36.relative(service.dir, file.path));
9756
+ const relFile = toPosix(path37.relative(service.dir, file.path));
9182
9757
  for (const site of sites) {
9183
9758
  const targetId = hostToNodeId.get(site.host);
9184
9759
  if (!targetId || targetId === service.node.id) continue;
@@ -9231,7 +9806,7 @@ async function addHttpCallEdges(graph, services) {
9231
9806
  }
9232
9807
 
9233
9808
  // src/extract/calls/route-match.ts
9234
- import path37 from "path";
9809
+ import path38 from "path";
9235
9810
  import Parser7 from "tree-sitter";
9236
9811
  import JavaScript5 from "tree-sitter-javascript";
9237
9812
  import {
@@ -9437,7 +10012,7 @@ async function addRouteCallEdges(graph, services) {
9437
10012
  const seen = /* @__PURE__ */ new Set();
9438
10013
  for (const file of files) {
9439
10014
  if (isTestPath(file.path)) continue;
9440
- if (!JS_CLIENT_EXTENSIONS.has(path37.extname(file.path))) continue;
10015
+ if (!JS_CLIENT_EXTENSIONS.has(path38.extname(file.path))) continue;
9441
10016
  let sites;
9442
10017
  try {
9443
10018
  sites = clientCallSitesFromSource(file.content, jsParser, knownHosts);
@@ -9446,7 +10021,7 @@ async function addRouteCallEdges(graph, services) {
9446
10021
  continue;
9447
10022
  }
9448
10023
  if (sites.length === 0) continue;
9449
- const relFile = toPosix(path37.relative(service.dir, file.path));
10024
+ const relFile = toPosix(path38.relative(service.dir, file.path));
9450
10025
  for (const site of sites) {
9451
10026
  const serverServiceId = hostToNodeId.get(site.host);
9452
10027
  if (!serverServiceId || serverServiceId === service.node.id) continue;
@@ -9506,7 +10081,7 @@ async function addRouteCallEdges(graph, services) {
9506
10081
  }
9507
10082
 
9508
10083
  // src/extract/calls/kafka.ts
9509
- import path38 from "path";
10084
+ import path39 from "path";
9510
10085
  import { infraId as infraId2 } from "@neat.is/types";
9511
10086
  var PRODUCER_TOPIC_RE = /(?:producer|kafkaProducer)[\s\S]{0,40}?\.send\s*\(\s*\{[\s\S]{0,200}?topic\s*:\s*['"`]([^'"`]+)['"`]/g;
9512
10087
  var CONSUMER_TOPIC_RE = /(?:consumer|kafkaConsumer)[\s\S]{0,40}?\.(?:subscribe|run)\s*\(\s*\{[\s\S]{0,200}?topic[s]?\s*:\s*(?:\[\s*)?['"`]([^'"`]+)['"`]/g;
@@ -9594,13 +10169,13 @@ function kafkaEndpointsFromFile(file, serviceDir) {
9594
10169
  // call sites — verified-call-site tier (ADR-066).
9595
10170
  confidenceKind: "verified-call-site",
9596
10171
  evidence: {
9597
- file: path38.relative(serviceDir, file.path),
10172
+ file: path39.relative(serviceDir, file.path),
9598
10173
  line,
9599
10174
  snippet: snippet(file.content, line)
9600
10175
  }
9601
10176
  });
9602
10177
  };
9603
- if (path38.extname(file.path) === ".go") {
10178
+ if (path39.extname(file.path) === ".go") {
9604
10179
  goSaramaEndpoints(file.content, make);
9605
10180
  } else {
9606
10181
  for (const { topic } of findAll(PRODUCER_TOPIC_RE, file.content)) make(topic, "PUBLISHES_TO");
@@ -9610,7 +10185,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
9610
10185
  }
9611
10186
 
9612
10187
  // src/extract/calls/redis.ts
9613
- import path39 from "path";
10188
+ import path40 from "path";
9614
10189
  import { infraId as infraId3 } from "@neat.is/types";
9615
10190
  var REDIS_URL_RE = /redis(?:s)?:\/\/(?:[^@'"`\s]+@)?([^:/'"`\s]+)(?::(\d+))?/g;
9616
10191
  function redisEndpointsFromFile(file, serviceDir) {
@@ -9633,7 +10208,7 @@ function redisEndpointsFromFile(file, serviceDir) {
9633
10208
  // support tier (ADR-066).
9634
10209
  confidenceKind: "url-with-structural-support",
9635
10210
  evidence: {
9636
- file: path39.relative(serviceDir, file.path),
10211
+ file: path40.relative(serviceDir, file.path),
9637
10212
  line,
9638
10213
  snippet: snippet(file.content, line)
9639
10214
  }
@@ -9643,7 +10218,7 @@ function redisEndpointsFromFile(file, serviceDir) {
9643
10218
  }
9644
10219
 
9645
10220
  // src/extract/calls/aws.ts
9646
- import path40 from "path";
10221
+ import path41 from "path";
9647
10222
  import { infraId as infraId4 } from "@neat.is/types";
9648
10223
  var S3_BUCKET_RE = /Bucket\s*:\s*['"`]([^'"`]+)['"`]/g;
9649
10224
  var DYNAMO_TABLE_RE = /TableName\s*:\s*['"`]([^'"`]+)['"`]/g;
@@ -9677,7 +10252,7 @@ function awsEndpointsFromFile(file, serviceDir) {
9677
10252
  // (ADR-066).
9678
10253
  confidenceKind: "verified-call-site",
9679
10254
  evidence: {
9680
- file: path40.relative(serviceDir, file.path),
10255
+ file: path41.relative(serviceDir, file.path),
9681
10256
  line,
9682
10257
  snippet: snippet(file.content, line)
9683
10258
  }
@@ -9701,7 +10276,7 @@ function awsEndpointsFromFile(file, serviceDir) {
9701
10276
  }
9702
10277
 
9703
10278
  // src/extract/calls/grpc.ts
9704
- import path41 from "path";
10279
+ import path42 from "path";
9705
10280
  import { infraId as infraId5 } from "@neat.is/types";
9706
10281
  var GRPC_CLIENT_RE = /new\s+([A-Z][A-Za-z0-9_]*)Client\s*\(\s*['"`]?([^,'"`)]+)?/g;
9707
10282
  var AWS_SDK_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@aws-sdk\/client-([a-z0-9-]+)['"`]/g;
@@ -9760,7 +10335,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
9760
10335
  // tier (ADR-066).
9761
10336
  confidenceKind: "verified-call-site",
9762
10337
  evidence: {
9763
- file: path41.relative(serviceDir, file.path),
10338
+ file: path42.relative(serviceDir, file.path),
9764
10339
  line,
9765
10340
  snippet: snippet(file.content, line)
9766
10341
  }
@@ -9770,7 +10345,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
9770
10345
  }
9771
10346
 
9772
10347
  // src/extract/calls/supabase.ts
9773
- import path42 from "path";
10348
+ import path43 from "path";
9774
10349
  import { infraId as infraId6 } from "@neat.is/types";
9775
10350
  var SUPABASE_JS_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@supabase\/supabase-js['"`]/;
9776
10351
  var SUPABASE_SSR_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@supabase\/ssr['"`]/;
@@ -9829,7 +10404,7 @@ function supabaseEndpointsFromFile(file, serviceDir) {
9829
10404
  // tier (ADR-066), the same grade aws.ts / grpc.ts emit at.
9830
10405
  confidenceKind: "verified-call-site",
9831
10406
  evidence: {
9832
- file: path42.relative(serviceDir, file.path),
10407
+ file: path43.relative(serviceDir, file.path),
9833
10408
  line,
9834
10409
  snippet: snippet(file.content, line)
9835
10410
  }
@@ -9856,7 +10431,7 @@ function supabaseEndpointsFromFile(file, serviceDir) {
9856
10431
  edgeType: "CALLS",
9857
10432
  confidenceKind: "verified-call-site",
9858
10433
  evidence: {
9859
- file: path42.relative(serviceDir, file.path),
10434
+ file: path43.relative(serviceDir, file.path),
9860
10435
  line,
9861
10436
  snippet: snippet(file.content, line)
9862
10437
  }
@@ -9867,7 +10442,7 @@ function supabaseEndpointsFromFile(file, serviceDir) {
9867
10442
  }
9868
10443
 
9869
10444
  // src/extract/calls/firestore.ts
9870
- import path43 from "path";
10445
+ import path44 from "path";
9871
10446
  import Parser8 from "tree-sitter";
9872
10447
  import JavaScript6 from "tree-sitter-javascript";
9873
10448
  import { infraId as infraId7 } from "@neat.is/types";
@@ -9906,7 +10481,7 @@ function isFirestoreClientFactory(node) {
9906
10481
  }
9907
10482
  function firestoreClientVars(root) {
9908
10483
  const vars = /* @__PURE__ */ new Set();
9909
- const walk9 = (node) => {
10484
+ const walk10 = (node) => {
9910
10485
  if (node.type === "variable_declarator") {
9911
10486
  const name = node.childForFieldName("name");
9912
10487
  let value = node.childForFieldName("value");
@@ -9915,9 +10490,9 @@ function firestoreClientVars(root) {
9915
10490
  vars.add(name.text);
9916
10491
  }
9917
10492
  }
9918
- for (const c of namedChildren(node)) walk9(c);
10493
+ for (const c of namedChildren(node)) walk10(c);
9919
10494
  };
9920
- walk9(root);
10495
+ walk10(root);
9921
10496
  return vars;
9922
10497
  }
9923
10498
  function isClientExpr(node, clientVars) {
@@ -10038,7 +10613,7 @@ function firestoreEndpointsFromFile(file, serviceDir) {
10038
10613
  const hasAdmin = FIRESTORE_ADMIN_IMPORT_RE.test(file.content);
10039
10614
  if (!hasClient && !hasAdmin) return [];
10040
10615
  const fileSdk = hasClient && !hasAdmin ? "client" : hasAdmin && !hasClient ? "admin" : null;
10041
- const tree = parseSource3(parserForExt2(path43.extname(file.path)), file.content);
10616
+ const tree = parseSource3(parserForExt2(path44.extname(file.path)), file.content);
10042
10617
  const clientVars = firestoreClientVars(tree.rootNode);
10043
10618
  const collLine = /* @__PURE__ */ new Map();
10044
10619
  const writes = /* @__PURE__ */ new Map();
@@ -10072,7 +10647,7 @@ function firestoreEndpointsFromFile(file, serviceDir) {
10072
10647
  }
10073
10648
  s.add(field);
10074
10649
  };
10075
- const walk9 = (node) => {
10650
+ const walk10 = (node) => {
10076
10651
  if (node.type === "call_expression") {
10077
10652
  const fn = node.childForFieldName("function");
10078
10653
  const line = node.startPosition.row + 1;
@@ -10112,9 +10687,9 @@ function firestoreEndpointsFromFile(file, serviceDir) {
10112
10687
  }
10113
10688
  }
10114
10689
  }
10115
- for (const c of namedChildren(node)) walk9(c);
10690
+ for (const c of namedChildren(node)) walk10(c);
10116
10691
  };
10117
- walk9(tree.rootNode);
10692
+ walk10(tree.rootNode);
10118
10693
  const out = [];
10119
10694
  for (const [collPath, line] of collLine) {
10120
10695
  const byField = writes.get(collPath);
@@ -10142,7 +10717,7 @@ function firestoreEndpointsFromFile(file, serviceDir) {
10142
10717
  ...columnSet.size > 0 ? { columns: [...columnSet] } : {},
10143
10718
  ...sdkWrites ? { sdkWrites } : {},
10144
10719
  evidence: {
10145
- file: path43.relative(serviceDir, file.path),
10720
+ file: path44.relative(serviceDir, file.path),
10146
10721
  line,
10147
10722
  snippet: snippet(file.content, line)
10148
10723
  }
@@ -10152,7 +10727,7 @@ function firestoreEndpointsFromFile(file, serviceDir) {
10152
10727
  }
10153
10728
 
10154
10729
  // src/extract/calls/mongoose.ts
10155
- import path44 from "path";
10730
+ import path45 from "path";
10156
10731
  import { infraId as infraId8 } from "@neat.is/types";
10157
10732
  var MONGOOSE_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])mongoose['"`]/;
10158
10733
  var MONGODB_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])mongodb['"`]/;
@@ -10303,7 +10878,7 @@ function endpoint(r, file, serviceDir, matchText) {
10303
10878
  kind: r.kind,
10304
10879
  edgeType: "CALLS",
10305
10880
  confidenceKind: "verified-call-site",
10306
- evidence: { file: path44.relative(serviceDir, file.path), line, snippet: snippet(file.content, line) }
10881
+ evidence: { file: path45.relative(serviceDir, file.path), line, snippet: snippet(file.content, line) }
10307
10882
  };
10308
10883
  }
10309
10884
  function mongooseEndpointsFromFile(file, serviceDir) {
@@ -10393,7 +10968,7 @@ async function mongooseCrossFileEndpoints(files, serviceDir) {
10393
10968
  const registry = /* @__PURE__ */ new Map();
10394
10969
  for (const f of mongooseFiles) {
10395
10970
  const fx = fileExportsOf(f.content, pluralizeOn);
10396
- if (fx) registry.set(toPosix(path44.relative(serviceDir, f.path)), fx);
10971
+ if (fx) registry.set(toPosix(path45.relative(serviceDir, f.path)), fx);
10397
10972
  }
10398
10973
  if (registry.size === 0) return [];
10399
10974
  const out = [];
@@ -10404,7 +10979,7 @@ async function mongooseCrossFileEndpoints(files, serviceDir) {
10404
10979
  const directColl = /* @__PURE__ */ new Map();
10405
10980
  const nsExports = /* @__PURE__ */ new Map();
10406
10981
  for (const b of bindings) {
10407
- const resolvedRel = await resolveJsImport(b.specifier, path44.dirname(f.path), serviceDir, null);
10982
+ const resolvedRel = await resolveJsImport(b.specifier, path45.dirname(f.path), serviceDir, null);
10408
10983
  if (!resolvedRel) continue;
10409
10984
  const fx = registry.get(resolvedRel);
10410
10985
  if (!fx) continue;
@@ -10447,7 +11022,7 @@ async function mongooseCrossFileEndpoints(files, serviceDir) {
10447
11022
  }
10448
11023
 
10449
11024
  // src/extract/calls/sqlalchemy.ts
10450
- import path45 from "path";
11025
+ import path46 from "path";
10451
11026
  import Parser9 from "tree-sitter";
10452
11027
  import Python5 from "tree-sitter-python";
10453
11028
  import { infraId as infraId9 } from "@neat.is/types";
@@ -10586,7 +11161,7 @@ function sqlalchemyForeignKeys(file, serviceDir) {
10586
11161
  childTable,
10587
11162
  parentTable,
10588
11163
  evidence: {
10589
- file: path45.relative(serviceDir, file.path),
11164
+ file: path46.relative(serviceDir, file.path),
10590
11165
  line,
10591
11166
  snippet: snippet(file.content, line)
10592
11167
  }
@@ -10611,7 +11186,7 @@ function sqlalchemyEndpointsFromFile(file, serviceDir) {
10611
11186
  confidenceKind: "verified-call-site",
10612
11187
  ...columns && columns.length > 0 ? { columns } : {},
10613
11188
  evidence: {
10614
- file: path45.relative(serviceDir, file.path),
11189
+ file: path46.relative(serviceDir, file.path),
10615
11190
  line,
10616
11191
  snippet: snippet(file.content, line)
10617
11192
  }
@@ -10718,7 +11293,7 @@ function pythonOrmCrossFileEndpoints(files, serviceDir) {
10718
11293
  edgeType: "CALLS",
10719
11294
  confidenceKind: "verified-call-site",
10720
11295
  evidence: {
10721
- file: path45.relative(serviceDir, file.path),
11296
+ file: path46.relative(serviceDir, file.path),
10722
11297
  line,
10723
11298
  snippet: snippet(file.content, line)
10724
11299
  }
@@ -10729,7 +11304,7 @@ function pythonOrmCrossFileEndpoints(files, serviceDir) {
10729
11304
  }
10730
11305
 
10731
11306
  // src/extract/calls/django-orm.ts
10732
- import path46 from "path";
11307
+ import path47 from "path";
10733
11308
  import Parser10 from "tree-sitter";
10734
11309
  import Python6 from "tree-sitter-python";
10735
11310
  import { infraId as infraId10 } from "@neat.is/types";
@@ -10799,7 +11374,7 @@ function djangoOrmEndpointsFromFile(file, serviceDir) {
10799
11374
  const tree = parseSource7(makePyParser4(), file.content);
10800
11375
  const out = [];
10801
11376
  const seen = /* @__PURE__ */ new Set();
10802
- const defaultAppLabel = path46.basename(path46.dirname(file.path));
11377
+ const defaultAppLabel = path47.basename(path47.dirname(file.path));
10803
11378
  walk4(tree.rootNode, (node) => {
10804
11379
  if (node.type !== "class_definition") return;
10805
11380
  if (!extendsDjangoModel(node)) return;
@@ -10817,14 +11392,14 @@ function djangoOrmEndpointsFromFile(file, serviceDir) {
10817
11392
  kind: "sql-table",
10818
11393
  edgeType: "CALLS",
10819
11394
  confidenceKind: "verified-call-site",
10820
- evidence: { file: path46.relative(serviceDir, file.path), line, snippet: snippet(file.content, line) }
11395
+ evidence: { file: path47.relative(serviceDir, file.path), line, snippet: snippet(file.content, line) }
10821
11396
  });
10822
11397
  });
10823
11398
  return out;
10824
11399
  }
10825
11400
 
10826
11401
  // src/extract/calls/drizzle.ts
10827
- import path47 from "path";
11402
+ import path48 from "path";
10828
11403
  import Parser11 from "tree-sitter";
10829
11404
  import JavaScript7 from "tree-sitter-javascript";
10830
11405
  import { infraId as infraId11 } from "@neat.is/types";
@@ -10902,10 +11477,10 @@ function columnsFromObject(obj) {
10902
11477
  }
10903
11478
  function drizzleEndpointsFromFile(file, serviceDir) {
10904
11479
  if (!DRIZZLE_IMPORT_RE.test(file.content)) return [];
10905
- const tree = parseSource3(parserForExt3(path47.extname(file.path)), file.content);
11480
+ const tree = parseSource3(parserForExt3(path48.extname(file.path)), file.content);
10906
11481
  const out = [];
10907
11482
  const seen = /* @__PURE__ */ new Set();
10908
- const walk9 = (node) => {
11483
+ const walk10 = (node) => {
10909
11484
  if (node.type === "call_expression") {
10910
11485
  const fn = node.childForFieldName("function");
10911
11486
  if (fn?.type === "identifier" && TABLE_BUILDERS.has(fn.text)) {
@@ -10925,7 +11500,7 @@ function drizzleEndpointsFromFile(file, serviceDir) {
10925
11500
  confidenceKind: "structural",
10926
11501
  columns,
10927
11502
  evidence: {
10928
- file: path47.relative(serviceDir, file.path),
11503
+ file: path48.relative(serviceDir, file.path),
10929
11504
  line,
10930
11505
  snippet: snippet(file.content, line)
10931
11506
  }
@@ -10933,9 +11508,9 @@ function drizzleEndpointsFromFile(file, serviceDir) {
10933
11508
  }
10934
11509
  }
10935
11510
  }
10936
- for (const c of namedChildren4(node)) walk9(c);
11511
+ for (const c of namedChildren4(node)) walk10(c);
10937
11512
  };
10938
- walk9(tree.rootNode);
11513
+ walk10(tree.rootNode);
10939
11514
  return out;
10940
11515
  }
10941
11516
  function enclosingVarName(call) {
@@ -10957,7 +11532,7 @@ function enclosingVarName(call) {
10957
11532
  function collectDrizzleTables(root) {
10958
11533
  const tables = [];
10959
11534
  const varToTable = /* @__PURE__ */ new Map();
10960
- const walk9 = (node) => {
11535
+ const walk10 = (node) => {
10961
11536
  if (node.type === "call_expression") {
10962
11537
  const fn = node.childForFieldName("function");
10963
11538
  if (fn?.type === "identifier" && TABLE_BUILDERS.has(fn.text)) {
@@ -10972,9 +11547,9 @@ function collectDrizzleTables(root) {
10972
11547
  }
10973
11548
  }
10974
11549
  }
10975
- for (const c of namedChildren4(node)) walk9(c);
11550
+ for (const c of namedChildren4(node)) walk10(c);
10976
11551
  };
10977
- walk9(root);
11552
+ walk10(root);
10978
11553
  return { tables, varToTable };
10979
11554
  }
10980
11555
  function referencesTargetVar(call) {
@@ -10991,13 +11566,13 @@ function referencesTargetVar(call) {
10991
11566
  }
10992
11567
  function drizzleForeignKeys(file, serviceDir) {
10993
11568
  if (!DRIZZLE_IMPORT_RE.test(file.content)) return [];
10994
- const tree = parseSource3(parserForExt3(path47.extname(file.path)), file.content);
11569
+ const tree = parseSource3(parserForExt3(path48.extname(file.path)), file.content);
10995
11570
  const { tables, varToTable } = collectDrizzleTables(tree.rootNode);
10996
11571
  const out = [];
10997
11572
  const seen = /* @__PURE__ */ new Set();
10998
11573
  for (const table of tables) {
10999
11574
  if (!table.object) continue;
11000
- const walk9 = (node) => {
11575
+ const walk10 = (node) => {
11001
11576
  if (node.type === "call_expression") {
11002
11577
  const targetVar = referencesTargetVar(node);
11003
11578
  const parentTable = targetVar ? varToTable.get(targetVar) : void 0;
@@ -11010,7 +11585,7 @@ function drizzleForeignKeys(file, serviceDir) {
11010
11585
  childTable: table.tableName,
11011
11586
  parentTable,
11012
11587
  evidence: {
11013
- file: path47.relative(serviceDir, file.path),
11588
+ file: path48.relative(serviceDir, file.path),
11014
11589
  line,
11015
11590
  snippet: snippet(file.content, line)
11016
11591
  }
@@ -11018,15 +11593,15 @@ function drizzleForeignKeys(file, serviceDir) {
11018
11593
  }
11019
11594
  }
11020
11595
  }
11021
- for (const c of namedChildren4(node)) walk9(c);
11596
+ for (const c of namedChildren4(node)) walk10(c);
11022
11597
  };
11023
- walk9(table.object);
11598
+ walk10(table.object);
11024
11599
  }
11025
11600
  return out;
11026
11601
  }
11027
11602
 
11028
11603
  // src/extract/calls/prisma.ts
11029
- import path48 from "path";
11604
+ import path49 from "path";
11030
11605
  import { infraId as infraId12 } from "@neat.is/types";
11031
11606
  var SCALAR_TYPES = /* @__PURE__ */ new Set([
11032
11607
  "Int",
@@ -11082,7 +11657,7 @@ function prismaColumnsFromSchema(file, serviceDir) {
11082
11657
  confidenceKind: "structural",
11083
11658
  columns: b.columns,
11084
11659
  evidence: {
11085
- file: path48.relative(serviceDir, file.path),
11660
+ file: path49.relative(serviceDir, file.path),
11086
11661
  line: b.startLine,
11087
11662
  snippet: snippet(content, b.startLine)
11088
11663
  }
@@ -11143,7 +11718,7 @@ function prismaColumnsFromSchema(file, serviceDir) {
11143
11718
  }
11144
11719
  async function prismaColumnEndpoints(serviceDir) {
11145
11720
  const schemaPath = await findFirst(serviceDir, [
11146
- path48.join("prisma", "schema.prisma"),
11721
+ path49.join("prisma", "schema.prisma"),
11147
11722
  "schema.prisma"
11148
11723
  ]);
11149
11724
  if (!schemaPath) return [];
@@ -11218,7 +11793,7 @@ function prismaForeignKeysFromSchema(file, serviceDir) {
11218
11793
  childTable: current.table,
11219
11794
  parentTable,
11220
11795
  evidence: {
11221
- file: path48.relative(serviceDir, file.path),
11796
+ file: path49.relative(serviceDir, file.path),
11222
11797
  line: lineNo,
11223
11798
  snippet: snippet(content, lineNo)
11224
11799
  }
@@ -11233,7 +11808,7 @@ function prismaForeignKeysFromSchema(file, serviceDir) {
11233
11808
  }
11234
11809
  async function prismaForeignKeys(serviceDir) {
11235
11810
  const schemaPath = await findFirst(serviceDir, [
11236
- path48.join("prisma", "schema.prisma"),
11811
+ path49.join("prisma", "schema.prisma"),
11237
11812
  "schema.prisma"
11238
11813
  ]);
11239
11814
  if (!schemaPath) return [];
@@ -11243,7 +11818,7 @@ async function prismaForeignKeys(serviceDir) {
11243
11818
  }
11244
11819
 
11245
11820
  // src/extract/calls/activerecord.ts
11246
- import path49 from "path";
11821
+ import path50 from "path";
11247
11822
  import Parser12 from "tree-sitter";
11248
11823
  import Ruby3 from "tree-sitter-ruby";
11249
11824
  import { infraId as infraId13 } from "@neat.is/types";
@@ -11451,7 +12026,7 @@ function railsSchemaEndpointsFromFile(file, serviceDir) {
11451
12026
  confidenceKind: "structural",
11452
12027
  ...table.columns.length > 0 ? { columns: table.columns } : {},
11453
12028
  evidence: {
11454
- file: path49.relative(serviceDir, file.path),
12029
+ file: path50.relative(serviceDir, file.path),
11455
12030
  line: table.line,
11456
12031
  snippet: snippet(file.content, table.line)
11457
12032
  }
@@ -11473,7 +12048,7 @@ function railsSchemaForeignKeys(file, serviceDir) {
11473
12048
  childTable,
11474
12049
  parentTable,
11475
12050
  evidence: {
11476
- file: path49.relative(serviceDir, file.path),
12051
+ file: path50.relative(serviceDir, file.path),
11477
12052
  line,
11478
12053
  snippet: snippet(file.content, line)
11479
12054
  }
@@ -11556,7 +12131,7 @@ function railsModelEndpointsFromFile(file, serviceDir) {
11556
12131
  edgeType: "CALLS",
11557
12132
  confidenceKind: "verified-call-site",
11558
12133
  evidence: {
11559
- file: path49.relative(serviceDir, file.path),
12134
+ file: path50.relative(serviceDir, file.path),
11560
12135
  line,
11561
12136
  snippet: snippet(file.content, line)
11562
12137
  }
@@ -11593,7 +12168,7 @@ function railsModelForeignKeys(file, serviceDir) {
11593
12168
  childTable,
11594
12169
  parentTable,
11595
12170
  evidence: {
11596
- file: path49.relative(serviceDir, file.path),
12171
+ file: path50.relative(serviceDir, file.path),
11597
12172
  line,
11598
12173
  snippet: snippet(file.content, line)
11599
12174
  }
@@ -11604,7 +12179,7 @@ function railsModelForeignKeys(file, serviceDir) {
11604
12179
  }
11605
12180
 
11606
12181
  // src/extract/calls/eloquent.ts
11607
- import path50 from "path";
12182
+ import path51 from "path";
11608
12183
  import Parser13 from "tree-sitter";
11609
12184
  import Php3 from "tree-sitter-php";
11610
12185
  import { infraId as infraId14 } from "@neat.is/types";
@@ -11861,7 +12436,7 @@ function laravelMigrationEndpointsFromFile(file, serviceDir) {
11861
12436
  confidenceKind: "structural",
11862
12437
  ...columns.length > 0 ? { columns } : {},
11863
12438
  evidence: {
11864
- file: path50.relative(serviceDir, file.path),
12439
+ file: path51.relative(serviceDir, file.path),
11865
12440
  line: bp.line,
11866
12441
  snippet: snippet(file.content, bp.line)
11867
12442
  }
@@ -11883,7 +12458,7 @@ function laravelMigrationForeignKeys(file, serviceDir) {
11883
12458
  childTable,
11884
12459
  parentTable,
11885
12460
  evidence: {
11886
- file: path50.relative(serviceDir, file.path),
12461
+ file: path51.relative(serviceDir, file.path),
11887
12462
  line,
11888
12463
  snippet: snippet(file.content, line)
11889
12464
  }
@@ -12003,7 +12578,7 @@ function laravelModelEndpointsFromFile(file, serviceDir) {
12003
12578
  edgeType: "CALLS",
12004
12579
  confidenceKind: "verified-call-site",
12005
12580
  evidence: {
12006
- file: path50.relative(serviceDir, file.path),
12581
+ file: path51.relative(serviceDir, file.path),
12007
12582
  line,
12008
12583
  snippet: snippet(file.content, line)
12009
12584
  }
@@ -12039,7 +12614,7 @@ function laravelModelForeignKeys(file, serviceDir) {
12039
12614
  childTable,
12040
12615
  parentTable,
12041
12616
  evidence: {
12042
- file: path50.relative(serviceDir, file.path),
12617
+ file: path51.relative(serviceDir, file.path),
12043
12618
  line,
12044
12619
  snippet: snippet(file.content, line)
12045
12620
  }
@@ -12050,7 +12625,7 @@ function laravelModelForeignKeys(file, serviceDir) {
12050
12625
  }
12051
12626
 
12052
12627
  // src/extract/calls/go.ts
12053
- import path51 from "path";
12628
+ import path52 from "path";
12054
12629
  import Parser14 from "tree-sitter";
12055
12630
  import Go4 from "tree-sitter-go";
12056
12631
  import { infraId as infraId15 } from "@neat.is/types";
@@ -12124,7 +12699,7 @@ function firstStringLiteralArg(argsNode) {
12124
12699
  return null;
12125
12700
  }
12126
12701
  function goSqlEndpointsFromFile(file, serviceDir) {
12127
- if (path51.extname(file.path) !== ".go") return [];
12702
+ if (path52.extname(file.path) !== ".go") return [];
12128
12703
  if (!file.content.includes(DATABASE_SQL_IMPORT) && !file.content.includes(SQLX_IMPORT)) return [];
12129
12704
  const tree = parseSource10(makeGoParser3(), file.content);
12130
12705
  const importsDatabaseSql = goImportsAny(tree.rootNode, /* @__PURE__ */ new Set([DATABASE_SQL_IMPORT]));
@@ -12153,7 +12728,7 @@ function goSqlEndpointsFromFile(file, serviceDir) {
12153
12728
  confidenceKind: "verified-call-site",
12154
12729
  ...columns.length > 0 ? { columns } : {},
12155
12730
  evidence: {
12156
- file: toPosix(path51.relative(serviceDir, file.path)),
12731
+ file: toPosix(path52.relative(serviceDir, file.path)),
12157
12732
  line,
12158
12733
  snippet: snippet(file.content, line)
12159
12734
  }
@@ -12163,7 +12738,7 @@ function goSqlEndpointsFromFile(file, serviceDir) {
12163
12738
  }
12164
12739
 
12165
12740
  // src/extract/calls/gorm.ts
12166
- import path52 from "path";
12741
+ import path53 from "path";
12167
12742
  import Parser15 from "tree-sitter";
12168
12743
  import Go5 from "tree-sitter-go";
12169
12744
  import { infraId as infraId16 } from "@neat.is/types";
@@ -12596,7 +13171,7 @@ function collectColumns(struct, structs, seen, prefix, out, emitted) {
12596
13171
  seen.delete(struct.name);
12597
13172
  }
12598
13173
  function gormEndpointsFromFile(file, serviceDir) {
12599
- if (path52.extname(file.path) !== ".go") return [];
13174
+ if (path53.extname(file.path) !== ".go") return [];
12600
13175
  if (!GORM_IMPORT_RE.test(file.content)) return [];
12601
13176
  const tree = parseSource11(makeGoParser4(), file.content);
12602
13177
  const { structs, models, tableFor } = analyze(tree);
@@ -12618,7 +13193,7 @@ function gormEndpointsFromFile(file, serviceDir) {
12618
13193
  confidenceKind: "structural",
12619
13194
  ...columns.length > 0 ? { columns } : {},
12620
13195
  evidence: {
12621
- file: toPosix(path52.relative(serviceDir, file.path)),
13196
+ file: toPosix(path53.relative(serviceDir, file.path)),
12622
13197
  line: struct.line,
12623
13198
  snippet: snippet(file.content, struct.line)
12624
13199
  }
@@ -12627,7 +13202,7 @@ function gormEndpointsFromFile(file, serviceDir) {
12627
13202
  return out;
12628
13203
  }
12629
13204
  function gormForeignKeys(file, serviceDir) {
12630
- if (path52.extname(file.path) !== ".go") return [];
13205
+ if (path53.extname(file.path) !== ".go") return [];
12631
13206
  if (!GORM_IMPORT_RE.test(file.content)) return [];
12632
13207
  const tree = parseSource11(makeGoParser4(), file.content);
12633
13208
  const { structs, models, tableFor } = analyze(tree);
@@ -12642,7 +13217,7 @@ function gormForeignKeys(file, serviceDir) {
12642
13217
  childTable,
12643
13218
  parentTable,
12644
13219
  evidence: {
12645
- file: toPosix(path52.relative(serviceDir, file.path)),
13220
+ file: toPosix(path53.relative(serviceDir, file.path)),
12646
13221
  line,
12647
13222
  snippet: snippet(file.content, line)
12648
13223
  }
@@ -12677,6 +13252,121 @@ function gormForeignKeys(file, serviceDir) {
12677
13252
  return out;
12678
13253
  }
12679
13254
 
13255
+ // src/extract/calls/efcore.ts
13256
+ import path54 from "path";
13257
+ import Parser16 from "tree-sitter";
13258
+ import CSharp2 from "tree-sitter-c-sharp";
13259
+ import { infraId as infraId17 } from "@neat.is/types";
13260
+ var EFCORE_GATE = /Microsoft\.EntityFrameworkCore|DataAnnotations\.Schema|\bDbContext\b|\bDbSet\s*</;
13261
+ var PARSE_CHUNK12 = 16384;
13262
+ function makeCsParser() {
13263
+ const p = new Parser16();
13264
+ p.setLanguage(CSharp2);
13265
+ return p;
13266
+ }
13267
+ function parseSource12(parser, source) {
13268
+ return parser.parse(
13269
+ (index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK12)
13270
+ );
13271
+ }
13272
+ function walk9(node, visit) {
13273
+ visit(node);
13274
+ for (let i = 0; i < node.namedChildCount; i++) {
13275
+ const c = node.namedChild(i);
13276
+ if (c) walk9(c, visit);
13277
+ }
13278
+ }
13279
+ function firstChildOfType(node, type) {
13280
+ for (let i = 0; i < node.namedChildCount; i++) {
13281
+ const c = node.namedChild(i);
13282
+ if (c?.type === type) return c;
13283
+ }
13284
+ return null;
13285
+ }
13286
+ function csStringLiteral(node) {
13287
+ if (node.type === "string_literal") {
13288
+ let out = "";
13289
+ for (let i = 0; i < node.namedChildCount; i++) {
13290
+ const c = node.namedChild(i);
13291
+ if (c?.type === "string_literal_content") out += c.text;
13292
+ }
13293
+ return out;
13294
+ }
13295
+ if (node.type === "verbatim_string_literal") {
13296
+ const t = node.text;
13297
+ return t.length >= 3 ? t.slice(2, -1).replace(/""/g, '"') : "";
13298
+ }
13299
+ return null;
13300
+ }
13301
+ function attributeName(attr) {
13302
+ const nameNode = attr.childForFieldName("name") ?? attr.namedChild(0);
13303
+ if (!nameNode) return null;
13304
+ const text = nameNode.text;
13305
+ const base = text.includes(".") ? text.slice(text.lastIndexOf(".") + 1) : text;
13306
+ return base.endsWith("Attribute") ? base.slice(0, -"Attribute".length) : base;
13307
+ }
13308
+ function tableFromAttribute(attr) {
13309
+ if (attributeName(attr) !== "Table") return null;
13310
+ const args = attr.childForFieldName("arguments") ?? firstChildOfType(attr, "attribute_argument_list");
13311
+ if (!args) return null;
13312
+ for (let i = 0; i < args.namedChildCount; i++) {
13313
+ const arg = args.namedChild(i);
13314
+ if (arg?.type !== "attribute_argument") continue;
13315
+ const first = arg.namedChild(0);
13316
+ if (!first) continue;
13317
+ const value = csStringLiteral(first);
13318
+ if (value !== null) return value;
13319
+ return null;
13320
+ }
13321
+ return null;
13322
+ }
13323
+ function tableFromToTable(call) {
13324
+ const fn = call.childForFieldName("function");
13325
+ if (fn?.type !== "member_access_expression") return null;
13326
+ const method = fn.childForFieldName("name") ?? fn.namedChild(fn.namedChildCount - 1);
13327
+ if (method?.text !== "ToTable") return null;
13328
+ const args = call.childForFieldName("arguments");
13329
+ const firstArg2 = args?.namedChild(0);
13330
+ if (firstArg2?.type !== "argument") return null;
13331
+ const value = firstArg2.namedChild(0);
13332
+ return value ? csStringLiteral(value) : null;
13333
+ }
13334
+ function efcoreEndpointsFromFile(file, serviceDir) {
13335
+ if (path54.extname(file.path) !== ".cs") return [];
13336
+ if (!EFCORE_GATE.test(file.content)) return [];
13337
+ const tree = parseSource12(makeCsParser(), file.content);
13338
+ const out = [];
13339
+ const seen = /* @__PURE__ */ new Set();
13340
+ const push = (name, line) => {
13341
+ if (!name || seen.has(name)) return;
13342
+ seen.add(name);
13343
+ out.push({
13344
+ infraId: infraId17("sql-table", name),
13345
+ name,
13346
+ kind: "sql-table",
13347
+ edgeType: "CALLS",
13348
+ confidenceKind: "structural",
13349
+ evidence: {
13350
+ file: toPosix(path54.relative(serviceDir, file.path)),
13351
+ line,
13352
+ snippet: snippet(file.content, line)
13353
+ }
13354
+ });
13355
+ };
13356
+ walk9(tree.rootNode, (node) => {
13357
+ if (node.type === "attribute") {
13358
+ const table = tableFromAttribute(node);
13359
+ if (table) push(table, node.startPosition.row + 1);
13360
+ return;
13361
+ }
13362
+ if (node.type === "invocation_expression") {
13363
+ const table = tableFromToTable(node);
13364
+ if (table) push(table, node.startPosition.row + 1);
13365
+ }
13366
+ });
13367
+ return out;
13368
+ }
13369
+
12680
13370
  // src/extract/calls/index.ts
12681
13371
  function edgeTypeFromEndpoint(ep) {
12682
13372
  switch (ep.edgeType) {
@@ -12735,6 +13425,11 @@ async function addExternalEndpointEdges(graph, services) {
12735
13425
  } catch (err) {
12736
13426
  recordExtractionError("laravel eloquent extraction", file.path, err);
12737
13427
  }
13428
+ try {
13429
+ endpoints.push(...efcoreEndpointsFromFile(file, service.dir));
13430
+ } catch (err) {
13431
+ recordExtractionError("efcore data-axis extraction", file.path, err);
13432
+ }
12738
13433
  }
12739
13434
  endpoints.push(...await mongooseCrossFileEndpoints(maskedFiles, service.dir));
12740
13435
  endpoints.push(...pythonOrmCrossFileEndpoints(maskedFiles, service.dir));
@@ -12838,7 +13533,7 @@ import {
12838
13533
  Provenance as Provenance16,
12839
13534
  confidenceForExtracted as confidenceForExtracted13,
12840
13535
  extractedEdgeId as extractedEdgeId10,
12841
- infraId as infraId17
13536
+ infraId as infraId18
12842
13537
  } from "@neat.is/types";
12843
13538
  async function addTableEdges(graph, services) {
12844
13539
  let nodesAdded = 0;
@@ -12867,8 +13562,8 @@ async function addTableEdges(graph, services) {
12867
13562
  }
12868
13563
  refs.push(...modelRefs);
12869
13564
  for (const ref of refs) {
12870
- const childId = infraId17("sql-table", ref.childTable);
12871
- const parentId = infraId17("sql-table", ref.parentTable);
13565
+ const childId = infraId18("sql-table", ref.childTable);
13566
+ const parentId = infraId18("sql-table", ref.parentTable);
12872
13567
  if (childId === parentId) continue;
12873
13568
  nodesAdded += ensureTableNode(graph, childId, ref.childTable);
12874
13569
  nodesAdded += ensureTableNode(graph, parentId, ref.parentTable);
@@ -12903,14 +13598,14 @@ function ensureTableNode(graph, id, name) {
12903
13598
  }
12904
13599
 
12905
13600
  // src/extract/infra/docker-compose.ts
12906
- import path53 from "path";
13601
+ import path55 from "path";
12907
13602
  import { EdgeType as EdgeType17, Provenance as Provenance18, confidenceForExtracted as confidenceForExtracted15 } from "@neat.is/types";
12908
13603
 
12909
13604
  // src/extract/infra/shared.ts
12910
- import { NodeType as NodeType26, Provenance as Provenance17, confidenceForExtracted as confidenceForExtracted14, infraId as infraId18 } from "@neat.is/types";
13605
+ import { NodeType as NodeType26, Provenance as Provenance17, confidenceForExtracted as confidenceForExtracted14, infraId as infraId19 } from "@neat.is/types";
12911
13606
  function makeInfraNode(kind, name, provider = "self", extras) {
12912
13607
  return {
12913
- id: infraId18(kind, name),
13608
+ id: infraId19(kind, name),
12914
13609
  type: NodeType26.InfraNode,
12915
13610
  name,
12916
13611
  provider,
@@ -12973,7 +13668,7 @@ function dependsOnList(value) {
12973
13668
  }
12974
13669
  function serviceNameToServiceNode(name, services) {
12975
13670
  for (const s of services) {
12976
- if (s.node.name === name || path53.basename(s.dir) === name) return s.node.id;
13671
+ if (s.node.name === name || path55.basename(s.dir) === name) return s.node.id;
12977
13672
  }
12978
13673
  return null;
12979
13674
  }
@@ -12982,7 +13677,7 @@ async function addComposeInfra(graph, scanPath, services) {
12982
13677
  let edgesAdded = 0;
12983
13678
  let composePath = null;
12984
13679
  for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
12985
- const abs = path53.join(scanPath, name);
13680
+ const abs = path55.join(scanPath, name);
12986
13681
  if (await exists(abs)) {
12987
13682
  composePath = abs;
12988
13683
  break;
@@ -12995,13 +13690,13 @@ async function addComposeInfra(graph, scanPath, services) {
12995
13690
  } catch (err) {
12996
13691
  recordExtractionError(
12997
13692
  "infra docker-compose",
12998
- path53.relative(scanPath, composePath),
13693
+ path55.relative(scanPath, composePath),
12999
13694
  err
13000
13695
  );
13001
13696
  return { nodesAdded, edgesAdded };
13002
13697
  }
13003
13698
  if (!compose?.services) return { nodesAdded, edgesAdded };
13004
- const evidenceFile = path53.relative(scanPath, composePath).split(path53.sep).join("/");
13699
+ const evidenceFile = path55.relative(scanPath, composePath).split(path55.sep).join("/");
13005
13700
  const composeNameToNodeId = /* @__PURE__ */ new Map();
13006
13701
  for (const [composeName, svc] of Object.entries(compose.services)) {
13007
13702
  const matchedServiceId = serviceNameToServiceNode(composeName, services);
@@ -13042,8 +13737,8 @@ async function addComposeInfra(graph, scanPath, services) {
13042
13737
  }
13043
13738
 
13044
13739
  // src/extract/infra/dockerfile.ts
13045
- import path54 from "path";
13046
- import { promises as fs24 } from "fs";
13740
+ import path56 from "path";
13741
+ import { promises as fs25 } from "fs";
13047
13742
  import { EdgeType as EdgeType18, Provenance as Provenance19, confidenceForExtracted as confidenceForExtracted16 } from "@neat.is/types";
13048
13743
  function readDockerfile(content) {
13049
13744
  let image = null;
@@ -13073,15 +13768,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
13073
13768
  let nodesAdded = 0;
13074
13769
  let edgesAdded = 0;
13075
13770
  for (const service of services) {
13076
- const dockerfilePath = path54.join(service.dir, "Dockerfile");
13771
+ const dockerfilePath = path56.join(service.dir, "Dockerfile");
13077
13772
  if (!await exists(dockerfilePath)) continue;
13078
13773
  let content;
13079
13774
  try {
13080
- content = await fs24.readFile(dockerfilePath, "utf8");
13775
+ content = await fs25.readFile(dockerfilePath, "utf8");
13081
13776
  } catch (err) {
13082
13777
  recordExtractionError(
13083
13778
  "infra dockerfile",
13084
- path54.relative(scanPath, dockerfilePath),
13779
+ path56.relative(scanPath, dockerfilePath),
13085
13780
  err
13086
13781
  );
13087
13782
  continue;
@@ -13093,8 +13788,8 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
13093
13788
  graph.addNode(node.id, node);
13094
13789
  nodesAdded++;
13095
13790
  }
13096
- const relDockerfile = toPosix(path54.relative(service.dir, dockerfilePath));
13097
- const evidenceFile = toPosix(path54.relative(scanPath, dockerfilePath));
13791
+ const relDockerfile = toPosix(path56.relative(service.dir, dockerfilePath));
13792
+ const evidenceFile = toPosix(path56.relative(scanPath, dockerfilePath));
13098
13793
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
13099
13794
  graph,
13100
13795
  service.pkg.name,
@@ -13145,23 +13840,23 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
13145
13840
  }
13146
13841
 
13147
13842
  // src/extract/infra/terraform.ts
13148
- import { promises as fs25 } from "fs";
13149
- import path55 from "path";
13843
+ import { promises as fs26 } from "fs";
13844
+ import path57 from "path";
13150
13845
  import { EdgeType as EdgeType19, Provenance as Provenance20, confidenceForExtracted as confidenceForExtracted17 } from "@neat.is/types";
13151
13846
  var RESOURCE_RE = /resource\s+"(aws_[A-Za-z0-9_]+)"\s+"([A-Za-z0-9_-]+)"/g;
13152
13847
  var REFERENCE_RE = /(?<![\w.])(aws_[A-Za-z0-9_]+)\.([A-Za-z0-9_-]+)/g;
13153
13848
  async function walkTfFiles(start, depth = 0, max = 5) {
13154
13849
  if (depth > max) return [];
13155
13850
  const out = [];
13156
- const entries = await fs25.readdir(start, { withFileTypes: true }).catch(() => []);
13851
+ const entries = await fs26.readdir(start, { withFileTypes: true }).catch(() => []);
13157
13852
  for (const entry of entries) {
13158
13853
  if (entry.isDirectory()) {
13159
13854
  if (IGNORED_DIRS.has(entry.name) || entry.name === ".terraform") continue;
13160
- const child = path55.join(start, entry.name);
13855
+ const child = path57.join(start, entry.name);
13161
13856
  if (await isPythonVenvDir(child)) continue;
13162
13857
  out.push(...await walkTfFiles(child, depth + 1, max));
13163
13858
  } else if (entry.isFile() && entry.name.endsWith(".tf")) {
13164
- out.push(path55.join(start, entry.name));
13859
+ out.push(path57.join(start, entry.name));
13165
13860
  }
13166
13861
  }
13167
13862
  return out;
@@ -13192,8 +13887,8 @@ async function addTerraformResources(graph, scanPath) {
13192
13887
  let edgesAdded = 0;
13193
13888
  const files = await walkTfFiles(scanPath);
13194
13889
  for (const file of files) {
13195
- const content = await fs25.readFile(file, "utf8");
13196
- const evidenceFile = toPosix(path55.relative(scanPath, file));
13890
+ const content = await fs26.readFile(file, "utf8");
13891
+ const evidenceFile = toPosix(path57.relative(scanPath, file));
13197
13892
  const resources = [];
13198
13893
  const byKey = /* @__PURE__ */ new Map();
13199
13894
  RESOURCE_RE.lastIndex = 0;
@@ -13249,8 +13944,8 @@ async function addTerraformResources(graph, scanPath) {
13249
13944
  }
13250
13945
 
13251
13946
  // src/extract/infra/k8s.ts
13252
- import { promises as fs26 } from "fs";
13253
- import path56 from "path";
13947
+ import { promises as fs27 } from "fs";
13948
+ import path58 from "path";
13254
13949
  import { parseAllDocuments as parseAllDocuments2 } from "yaml";
13255
13950
  var K8S_KIND_TO_INFRA_KIND = {
13256
13951
  Service: "k8s-service",
@@ -13264,15 +13959,15 @@ var K8S_KIND_TO_INFRA_KIND = {
13264
13959
  async function walkYamlFiles2(start, depth = 0, max = 5) {
13265
13960
  if (depth > max) return [];
13266
13961
  const out = [];
13267
- const entries = await fs26.readdir(start, { withFileTypes: true }).catch(() => []);
13962
+ const entries = await fs27.readdir(start, { withFileTypes: true }).catch(() => []);
13268
13963
  for (const entry of entries) {
13269
13964
  if (entry.isDirectory()) {
13270
13965
  if (IGNORED_DIRS.has(entry.name)) continue;
13271
- const child = path56.join(start, entry.name);
13966
+ const child = path58.join(start, entry.name);
13272
13967
  if (await isPythonVenvDir(child)) continue;
13273
13968
  out.push(...await walkYamlFiles2(child, depth + 1, max));
13274
- } else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(path56.extname(entry.name))) {
13275
- out.push(path56.join(start, entry.name));
13969
+ } else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(path58.extname(entry.name))) {
13970
+ out.push(path58.join(start, entry.name));
13276
13971
  }
13277
13972
  }
13278
13973
  return out;
@@ -13281,7 +13976,7 @@ async function addK8sResources(graph, scanPath) {
13281
13976
  let nodesAdded = 0;
13282
13977
  const files = await walkYamlFiles2(scanPath);
13283
13978
  for (const file of files) {
13284
- const content = await fs26.readFile(file, "utf8");
13979
+ const content = await fs27.readFile(file, "utf8");
13285
13980
  let docs;
13286
13981
  try {
13287
13982
  docs = parseAllDocuments2(content).map((d) => d.toJSON());
@@ -13304,16 +13999,16 @@ async function addK8sResources(graph, scanPath) {
13304
13999
  }
13305
14000
 
13306
14001
  // src/extract/infra/cloudflare.ts
13307
- import { promises as fs27 } from "fs";
13308
- import path57 from "path";
14002
+ import { promises as fs28 } from "fs";
14003
+ import path59 from "path";
13309
14004
  import { parse as parseToml3 } from "smol-toml";
13310
14005
  import { EdgeType as EdgeType20, Provenance as Provenance21, confidenceForExtracted as confidenceForExtracted18 } from "@neat.is/types";
13311
14006
  var WRANGLER_FILENAMES = ["wrangler.toml", "wrangler.jsonc", "wrangler.json"];
13312
14007
  async function readWranglerConfig(dir) {
13313
14008
  for (const filename of WRANGLER_FILENAMES) {
13314
- const abs = path57.join(dir, filename);
14009
+ const abs = path59.join(dir, filename);
13315
14010
  if (!await exists(abs)) continue;
13316
- const raw = await fs27.readFile(abs, "utf8");
14011
+ const raw = await fs28.readFile(abs, "utf8");
13317
14012
  const config = filename === "wrangler.toml" ? parseToml3(raw) : JSON.parse(maskCommentsInSource(raw));
13318
14013
  return { config, relFile: filename, raw };
13319
14014
  }
@@ -13374,11 +14069,11 @@ async function addCloudflareWorkers(graph, services, scanPath) {
13374
14069
  try {
13375
14070
  read = await readWranglerConfig(service.dir);
13376
14071
  } catch (err) {
13377
- recordExtractionError("infra cloudflare", path57.relative(scanPath, service.dir), err);
14072
+ recordExtractionError("infra cloudflare", path59.relative(scanPath, service.dir), err);
13378
14073
  continue;
13379
14074
  }
13380
14075
  if (!read || !read.config.name) continue;
13381
- const evidenceFile = toPosix(path57.relative(scanPath, path57.join(service.dir, read.relFile)));
14076
+ const evidenceFile = toPosix(path59.relative(scanPath, path59.join(service.dir, read.relFile)));
13382
14077
  discovered.push({ service, config: read.config, relFile: read.relFile, raw: read.raw, evidenceFile });
13383
14078
  }
13384
14079
  for (const worker of discovered) {
@@ -13390,7 +14085,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
13390
14085
  }
13391
14086
  let anchorId = service.node.id;
13392
14087
  if (config.main) {
13393
- const entryRelPath = toPosix(path57.normalize(config.main));
14088
+ const entryRelPath = toPosix(path59.normalize(config.main));
13394
14089
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
13395
14090
  graph,
13396
14091
  service.pkg.name,
@@ -13536,24 +14231,24 @@ async function addCloudflareWorkers(graph, services, scanPath) {
13536
14231
  }
13537
14232
 
13538
14233
  // src/extract/infra/vercel.ts
13539
- import { promises as fs28 } from "fs";
13540
- import path58 from "path";
14234
+ import { promises as fs29 } from "fs";
14235
+ import path60 from "path";
13541
14236
  import { EdgeType as EdgeType21 } from "@neat.is/types";
13542
14237
  var VERCEL_CONFIG_FILENAMES = ["vercel.json", "vercel.jsonc"];
13543
14238
  async function readVercelConfig(dir) {
13544
14239
  for (const filename of VERCEL_CONFIG_FILENAMES) {
13545
- const abs = path58.join(dir, filename);
14240
+ const abs = path60.join(dir, filename);
13546
14241
  if (!await exists(abs)) continue;
13547
- const raw = await fs28.readFile(abs, "utf8");
14242
+ const raw = await fs29.readFile(abs, "utf8");
13548
14243
  const config = JSON.parse(maskCommentsInSource(raw));
13549
14244
  return { config, relFile: filename, raw };
13550
14245
  }
13551
14246
  return null;
13552
14247
  }
13553
14248
  async function readLinkedProjectName(dir) {
13554
- const abs = path58.join(dir, ".vercel", "project.json");
14249
+ const abs = path60.join(dir, ".vercel", "project.json");
13555
14250
  if (!await exists(abs)) return void 0;
13556
- const parsed = JSON.parse(await fs28.readFile(abs, "utf8"));
14251
+ const parsed = JSON.parse(await fs29.readFile(abs, "utf8"));
13557
14252
  return typeof parsed.projectName === "string" ? parsed.projectName : void 0;
13558
14253
  }
13559
14254
  function routeSource(route) {
@@ -13569,7 +14264,7 @@ async function addVercelServices(graph, services, scanPath) {
13569
14264
  read = await readVercelConfig(service.dir);
13570
14265
  projectName = await readLinkedProjectName(service.dir);
13571
14266
  } catch (err) {
13572
- recordExtractionError("infra vercel", path58.relative(scanPath, service.dir), err);
14267
+ recordExtractionError("infra vercel", path60.relative(scanPath, service.dir), err);
13573
14268
  continue;
13574
14269
  }
13575
14270
  if (!read && !projectName) continue;
@@ -13585,7 +14280,7 @@ async function addVercelServices(graph, services, scanPath) {
13585
14280
  const anchorId = service.node.id;
13586
14281
  if (!read) continue;
13587
14282
  const { config, relFile, raw } = read;
13588
- const evidenceFile = toPosix(path58.relative(scanPath, path58.join(service.dir, relFile)));
14283
+ const evidenceFile = toPosix(path60.relative(scanPath, path60.join(service.dir, relFile)));
13589
14284
  const add = (edgeType, kind, name) => {
13590
14285
  if (!name) return;
13591
14286
  const result = emitPlatformResourceEdge(
@@ -13613,16 +14308,16 @@ async function addVercelServices(graph, services, scanPath) {
13613
14308
  }
13614
14309
 
13615
14310
  // src/extract/infra/railway.ts
13616
- import { promises as fs29 } from "fs";
13617
- import path59 from "path";
14311
+ import { promises as fs30 } from "fs";
14312
+ import path61 from "path";
13618
14313
  import { parse as parseToml4 } from "smol-toml";
13619
14314
  import { EdgeType as EdgeType22 } from "@neat.is/types";
13620
14315
  var RAILWAY_FILENAMES = ["railway.toml", "railway.json", "railway.jsonc"];
13621
14316
  async function readRailwayConfig(dir) {
13622
14317
  for (const filename of RAILWAY_FILENAMES) {
13623
- const abs = path59.join(dir, filename);
14318
+ const abs = path61.join(dir, filename);
13624
14319
  if (!await exists(abs)) continue;
13625
- const raw = await fs29.readFile(abs, "utf8");
14320
+ const raw = await fs30.readFile(abs, "utf8");
13626
14321
  const config = filename === "railway.toml" ? parseToml4(raw) : JSON.parse(maskCommentsInSource(raw));
13627
14322
  return { config, relFile: filename, raw };
13628
14323
  }
@@ -13636,7 +14331,7 @@ async function addRailwayServices(graph, services, scanPath) {
13636
14331
  try {
13637
14332
  read = await readRailwayConfig(service.dir);
13638
14333
  } catch (err) {
13639
- recordExtractionError("infra railway", path59.relative(scanPath, service.dir), err);
14334
+ recordExtractionError("infra railway", path61.relative(scanPath, service.dir), err);
13640
14335
  continue;
13641
14336
  }
13642
14337
  if (!read) continue;
@@ -13646,7 +14341,7 @@ async function addRailwayServices(graph, services, scanPath) {
13646
14341
  }
13647
14342
  const anchorId = service.node.id;
13648
14343
  const { config, relFile, raw } = read;
13649
- const evidenceFile = toPosix(path59.relative(scanPath, path59.join(service.dir, relFile)));
14344
+ const evidenceFile = toPosix(path61.relative(scanPath, path61.join(service.dir, relFile)));
13650
14345
  const add = (edgeType, kind, name) => {
13651
14346
  if (!name) return;
13652
14347
  const result = emitPlatformResourceEdge(
@@ -13670,15 +14365,15 @@ async function addRailwayServices(graph, services, scanPath) {
13670
14365
  }
13671
14366
 
13672
14367
  // src/extract/infra/supabase.ts
13673
- import { promises as fs30 } from "fs";
13674
- import path60 from "path";
14368
+ import { promises as fs31 } from "fs";
14369
+ import path62 from "path";
13675
14370
  import { parse as parseToml5 } from "smol-toml";
13676
14371
  import { EdgeType as EdgeType23 } from "@neat.is/types";
13677
14372
  async function readSupabaseConfig(dir) {
13678
- const relFile = path60.join("supabase", "config.toml");
13679
- const abs = path60.join(dir, relFile);
14373
+ const relFile = path62.join("supabase", "config.toml");
14374
+ const abs = path62.join(dir, relFile);
13680
14375
  if (!await exists(abs)) return null;
13681
- const raw = await fs30.readFile(abs, "utf8");
14376
+ const raw = await fs31.readFile(abs, "utf8");
13682
14377
  const config = parseToml5(raw);
13683
14378
  return { config, relFile, raw };
13684
14379
  }
@@ -13690,7 +14385,7 @@ async function addSupabaseProjects(graph, services, scanPath) {
13690
14385
  try {
13691
14386
  read = await readSupabaseConfig(service.dir);
13692
14387
  } catch (err) {
13693
- recordExtractionError("infra supabase", path60.relative(scanPath, service.dir), err);
14388
+ recordExtractionError("infra supabase", path62.relative(scanPath, service.dir), err);
13694
14389
  continue;
13695
14390
  }
13696
14391
  if (!read) continue;
@@ -13705,7 +14400,7 @@ async function addSupabaseProjects(graph, services, scanPath) {
13705
14400
  });
13706
14401
  }
13707
14402
  const anchorId = service.node.id;
13708
- const evidenceFile = toPosix(path60.relative(scanPath, path60.join(service.dir, relFile)));
14403
+ const evidenceFile = toPosix(path62.relative(scanPath, path62.join(service.dir, relFile)));
13709
14404
  const add = (edgeType, kind, name) => {
13710
14405
  if (!name) return;
13711
14406
  const result = emitPlatformResourceEdge(
@@ -13746,8 +14441,8 @@ async function addInfra(graph, scanPath, services) {
13746
14441
  }
13747
14442
 
13748
14443
  // src/extract/zod-shapes.ts
13749
- import path61 from "path";
13750
- import Parser16 from "tree-sitter";
14444
+ import path63 from "path";
14445
+ import Parser17 from "tree-sitter";
13751
14446
  import JavaScript8 from "tree-sitter-javascript";
13752
14447
  import {
13753
14448
  EdgeType as EdgeType24,
@@ -13755,12 +14450,12 @@ import {
13755
14450
  Provenance as Provenance22,
13756
14451
  confidenceForExtracted as confidenceForExtracted19,
13757
14452
  extractedEdgeId as extractedEdgeId11,
13758
- infraId as infraId19
14453
+ infraId as infraId20
13759
14454
  } from "@neat.is/types";
13760
14455
  var ZOD_IMPORT_RE = /\bzod\b/;
13761
14456
  var ZOD_OBJECTS = /* @__PURE__ */ new Set(["z", "zod"]);
13762
14457
  function parserForExt4(ext) {
13763
- const p = new Parser16();
14458
+ const p = new Parser17();
13764
14459
  p.setLanguage(GRAMMAR_BY_EXT[ext] ?? JavaScript8);
13765
14460
  return p;
13766
14461
  }
@@ -13848,7 +14543,7 @@ function topLevelSchemas(root) {
13848
14543
  }
13849
14544
  function zodShapesFromFile(file, serviceDir) {
13850
14545
  if (!ZOD_IMPORT_RE.test(file.content)) return [];
13851
- const tree = parseSource3(parserForExt4(path61.extname(file.path)), file.content);
14546
+ const tree = parseSource3(parserForExt4(path63.extname(file.path)), file.content);
13852
14547
  const out = [];
13853
14548
  const seen = /* @__PURE__ */ new Set();
13854
14549
  for (const { name, call } of topLevelSchemas(tree.rootNode)) {
@@ -13862,11 +14557,11 @@ function zodShapesFromFile(file, serviceDir) {
13862
14557
  seen.add(name);
13863
14558
  const line = call.startPosition.row + 1;
13864
14559
  out.push({
13865
- infraId: infraId19("zod-schema", name),
14560
+ infraId: infraId20("zod-schema", name),
13866
14561
  name,
13867
14562
  fields,
13868
14563
  evidence: {
13869
- file: path61.relative(serviceDir, file.path),
14564
+ file: path63.relative(serviceDir, file.path),
13870
14565
  line,
13871
14566
  snippet: snippet(file.content, line)
13872
14567
  }
@@ -14102,11 +14797,11 @@ async function addFirestoreRules(graph, services) {
14102
14797
  }
14103
14798
 
14104
14799
  // src/extract/index.ts
14105
- import path63 from "path";
14800
+ import path65 from "path";
14106
14801
 
14107
14802
  // src/extract/retire.ts
14108
14803
  import { existsSync as existsSync2 } from "fs";
14109
- import path62 from "path";
14804
+ import path64 from "path";
14110
14805
  import { NodeType as NodeType29, Provenance as Provenance23 } from "@neat.is/types";
14111
14806
  function dropOrphanedFileNodes(graph) {
14112
14807
  const orphans = [];
@@ -14140,11 +14835,11 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
14140
14835
  if (edge.provenance !== Provenance23.EXTRACTED) return;
14141
14836
  const evidenceFile = edge.evidence?.file;
14142
14837
  if (!evidenceFile) return;
14143
- if (path62.isAbsolute(evidenceFile)) {
14838
+ if (path64.isAbsolute(evidenceFile)) {
14144
14839
  if (!existsSync2(evidenceFile)) toDrop.push(id);
14145
14840
  return;
14146
14841
  }
14147
- const found = bases.some((base) => existsSync2(path62.join(base, evidenceFile)));
14842
+ const found = bases.some((base) => existsSync2(path64.join(base, evidenceFile)));
14148
14843
  if (!found) toDrop.push(id);
14149
14844
  });
14150
14845
  for (const id of toDrop) graph.dropEdge(id);
@@ -14201,7 +14896,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
14201
14896
  }
14202
14897
  const droppedEntries = drainDroppedExtracted();
14203
14898
  if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
14204
- const rejectedPath = path63.join(path63.dirname(opts.errorsPath), "rejected.ndjson");
14899
+ const rejectedPath = path65.join(path65.dirname(opts.errorsPath), "rejected.ndjson");
14205
14900
  try {
14206
14901
  await writeRejectedExtracted(droppedEntries, rejectedPath);
14207
14902
  } catch (err) {
@@ -14575,8 +15270,8 @@ function computeDivergences(graph, opts = {}) {
14575
15270
  }
14576
15271
 
14577
15272
  // src/persist.ts
14578
- import { promises as fs31 } from "fs";
14579
- import path64 from "path";
15273
+ import { promises as fs32 } from "fs";
15274
+ import path66 from "path";
14580
15275
  import { NodeType as NodeType31, Provenance as Provenance25, observedEdgeId as observedEdgeId2 } from "@neat.is/types";
14581
15276
  var SCHEMA_VERSION = 7;
14582
15277
  function migrateV1ToV2(payload) {
@@ -14631,7 +15326,7 @@ function migrateV2ToV3(payload) {
14631
15326
  return { ...payload, schemaVersion: 3 };
14632
15327
  }
14633
15328
  async function ensureDir(filePath) {
14634
- await fs31.mkdir(path64.dirname(filePath), { recursive: true });
15329
+ await fs32.mkdir(path66.dirname(filePath), { recursive: true });
14635
15330
  }
14636
15331
  async function saveGraphToDisk(graph, outPath) {
14637
15332
  await ensureDir(outPath);
@@ -14641,13 +15336,13 @@ async function saveGraphToDisk(graph, outPath) {
14641
15336
  graph: graph.export()
14642
15337
  };
14643
15338
  const tmp = `${outPath}.tmp`;
14644
- await fs31.writeFile(tmp, JSON.stringify(payload), "utf8");
14645
- await fs31.rename(tmp, outPath);
15339
+ await fs32.writeFile(tmp, JSON.stringify(payload), "utf8");
15340
+ await fs32.rename(tmp, outPath);
14646
15341
  }
14647
15342
  async function loadGraphFromDisk(graph, outPath) {
14648
15343
  let raw;
14649
15344
  try {
14650
- raw = await fs31.readFile(outPath, "utf8");
15345
+ raw = await fs32.readFile(outPath, "utf8");
14651
15346
  } catch (err) {
14652
15347
  if (err.code === "ENOENT") return;
14653
15348
  throw err;
@@ -14720,7 +15415,7 @@ function startPersistLoop(graph, outPath, opts = {}) {
14720
15415
  }
14721
15416
 
14722
15417
  // src/diff.ts
14723
- import { promises as fs32 } from "fs";
15418
+ import { promises as fs33 } from "fs";
14724
15419
  async function loadSnapshotForDiff(target) {
14725
15420
  if (/^https?:\/\//i.test(target)) {
14726
15421
  const res = await fetch(target);
@@ -14729,7 +15424,7 @@ async function loadSnapshotForDiff(target) {
14729
15424
  }
14730
15425
  return await res.json();
14731
15426
  }
14732
- const raw = await fs32.readFile(target, "utf8");
15427
+ const raw = await fs33.readFile(target, "utf8");
14733
15428
  return JSON.parse(raw);
14734
15429
  }
14735
15430
  function indexEntries(entries) {
@@ -14796,23 +15491,23 @@ function canonicalJson(value) {
14796
15491
  }
14797
15492
 
14798
15493
  // src/projects.ts
14799
- import path65 from "path";
15494
+ import path67 from "path";
14800
15495
  function pathsForProject(project, baseDir) {
14801
15496
  if (project === DEFAULT_PROJECT) {
14802
15497
  return {
14803
- snapshotPath: path65.join(baseDir, "graph.json"),
14804
- errorsPath: path65.join(baseDir, "errors.ndjson"),
14805
- staleEventsPath: path65.join(baseDir, "stale-events.ndjson"),
14806
- embeddingsCachePath: path65.join(baseDir, "embeddings.json"),
14807
- policyViolationsPath: path65.join(baseDir, "policy-violations.ndjson")
15498
+ snapshotPath: path67.join(baseDir, "graph.json"),
15499
+ errorsPath: path67.join(baseDir, "errors.ndjson"),
15500
+ staleEventsPath: path67.join(baseDir, "stale-events.ndjson"),
15501
+ embeddingsCachePath: path67.join(baseDir, "embeddings.json"),
15502
+ policyViolationsPath: path67.join(baseDir, "policy-violations.ndjson")
14808
15503
  };
14809
15504
  }
14810
15505
  return {
14811
- snapshotPath: path65.join(baseDir, `${project}.json`),
14812
- errorsPath: path65.join(baseDir, `errors.${project}.ndjson`),
14813
- staleEventsPath: path65.join(baseDir, `stale-events.${project}.ndjson`),
14814
- embeddingsCachePath: path65.join(baseDir, `embeddings.${project}.json`),
14815
- policyViolationsPath: path65.join(baseDir, `policy-violations.${project}.ndjson`)
15506
+ snapshotPath: path67.join(baseDir, `${project}.json`),
15507
+ errorsPath: path67.join(baseDir, `errors.${project}.ndjson`),
15508
+ staleEventsPath: path67.join(baseDir, `stale-events.${project}.ndjson`),
15509
+ embeddingsCachePath: path67.join(baseDir, `embeddings.${project}.json`),
15510
+ policyViolationsPath: path67.join(baseDir, `policy-violations.${project}.ndjson`)
14816
15511
  };
14817
15512
  }
14818
15513
  var Projects = class {
@@ -14851,9 +15546,9 @@ function parseExtraProjects(raw) {
14851
15546
  }
14852
15547
 
14853
15548
  // src/registry.ts
14854
- import { promises as fs33 } from "fs";
15549
+ import { promises as fs34 } from "fs";
14855
15550
  import os2 from "os";
14856
- import path66 from "path";
15551
+ import path68 from "path";
14857
15552
  import {
14858
15553
  RegistryFileSchema
14859
15554
  } from "@neat.is/types";
@@ -14861,20 +15556,20 @@ var LOCK_TIMEOUT_MS = 5e3;
14861
15556
  var LOCK_RETRY_MS = 50;
14862
15557
  function neatHome() {
14863
15558
  const override = process.env.NEAT_HOME;
14864
- if (override && override.length > 0) return path66.resolve(override);
14865
- return path66.join(os2.homedir(), ".neat");
15559
+ if (override && override.length > 0) return path68.resolve(override);
15560
+ return path68.join(os2.homedir(), ".neat");
14866
15561
  }
14867
15562
  function registryPath() {
14868
- return path66.join(neatHome(), "projects.json");
15563
+ return path68.join(neatHome(), "projects.json");
14869
15564
  }
14870
15565
  function registryLockPath() {
14871
- return path66.join(neatHome(), "projects.json.lock");
15566
+ return path68.join(neatHome(), "projects.json.lock");
14872
15567
  }
14873
15568
  function daemonPidPath() {
14874
- return path66.join(neatHome(), "neatd.pid");
15569
+ return path68.join(neatHome(), "neatd.pid");
14875
15570
  }
14876
15571
  function daemonsDir() {
14877
- return path66.join(neatHome(), "daemons");
15572
+ return path68.join(neatHome(), "daemons");
14878
15573
  }
14879
15574
  function isFiniteInt(v) {
14880
15575
  return typeof v === "number" && Number.isFinite(v);
@@ -14907,7 +15602,7 @@ async function discoverDaemons(probe = defaultDiscoveryProbe) {
14907
15602
  const dir = daemonsDir();
14908
15603
  let names;
14909
15604
  try {
14910
- names = await fs33.readdir(dir);
15605
+ names = await fs34.readdir(dir);
14911
15606
  } catch (err) {
14912
15607
  if (err.code === "ENOENT") return [];
14913
15608
  throw err;
@@ -14915,10 +15610,10 @@ async function discoverDaemons(probe = defaultDiscoveryProbe) {
14915
15610
  const out = [];
14916
15611
  for (const name of names) {
14917
15612
  if (!name.endsWith(".json")) continue;
14918
- const file = path66.join(dir, name);
15613
+ const file = path68.join(dir, name);
14919
15614
  let raw;
14920
15615
  try {
14921
- raw = await fs33.readFile(file, "utf8");
15616
+ raw = await fs34.readFile(file, "utf8");
14922
15617
  } catch {
14923
15618
  continue;
14924
15619
  }
@@ -14931,7 +15626,7 @@ async function discoverDaemons(probe = defaultDiscoveryProbe) {
14931
15626
  return out;
14932
15627
  }
14933
15628
  async function removeDaemonRecord(source) {
14934
- await fs33.unlink(source).catch(() => {
15629
+ await fs34.unlink(source).catch(() => {
14935
15630
  });
14936
15631
  }
14937
15632
  async function listMachineProjects(probe = defaultDiscoveryProbe) {
@@ -14988,7 +15683,7 @@ function isPidAliveDefault(pid) {
14988
15683
  }
14989
15684
  async function readPidFile(file) {
14990
15685
  try {
14991
- const raw = await fs33.readFile(file, "utf8");
15686
+ const raw = await fs34.readFile(file, "utf8");
14992
15687
  const pid = Number.parseInt(raw.trim(), 10);
14993
15688
  return Number.isInteger(pid) && pid > 0 ? pid : void 0;
14994
15689
  } catch {
@@ -15036,32 +15731,32 @@ function lockHolderMessage(holder, lockPath, timeoutMs) {
15036
15731
  }
15037
15732
  }
15038
15733
  async function normalizeProjectPath(input) {
15039
- const resolved = path66.resolve(input);
15734
+ const resolved = path68.resolve(input);
15040
15735
  try {
15041
- return await fs33.realpath(resolved);
15736
+ return await fs34.realpath(resolved);
15042
15737
  } catch {
15043
15738
  return resolved;
15044
15739
  }
15045
15740
  }
15046
15741
  async function writeAtomically(target, contents) {
15047
- await fs33.mkdir(path66.dirname(target), { recursive: true });
15742
+ await fs34.mkdir(path68.dirname(target), { recursive: true });
15048
15743
  const tmp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
15049
- const fd = await fs33.open(tmp, "w");
15744
+ const fd = await fs34.open(tmp, "w");
15050
15745
  try {
15051
15746
  await fd.writeFile(contents, "utf8");
15052
15747
  await fd.sync();
15053
15748
  } finally {
15054
15749
  await fd.close();
15055
15750
  }
15056
- await fs33.rename(tmp, target);
15751
+ await fs34.rename(tmp, target);
15057
15752
  }
15058
15753
  async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaultLockHolderProbe) {
15059
15754
  const deadline = Date.now() + timeoutMs;
15060
- await fs33.mkdir(path66.dirname(lockPath), { recursive: true });
15755
+ await fs34.mkdir(path68.dirname(lockPath), { recursive: true });
15061
15756
  let probedHolder = false;
15062
15757
  while (true) {
15063
15758
  try {
15064
- const fd = await fs33.open(lockPath, "wx");
15759
+ const fd = await fs34.open(lockPath, "wx");
15065
15760
  try {
15066
15761
  await fd.writeFile(`${process.pid}
15067
15762
  `, "utf8");
@@ -15086,7 +15781,7 @@ async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaul
15086
15781
  }
15087
15782
  }
15088
15783
  async function releaseLock(lockPath) {
15089
- await fs33.unlink(lockPath).catch(() => {
15784
+ await fs34.unlink(lockPath).catch(() => {
15090
15785
  });
15091
15786
  }
15092
15787
  async function withLock(fn) {
@@ -15102,7 +15797,7 @@ async function readRegistry() {
15102
15797
  const file = registryPath();
15103
15798
  let raw;
15104
15799
  try {
15105
- raw = await fs33.readFile(file, "utf8");
15800
+ raw = await fs34.readFile(file, "utf8");
15106
15801
  } catch (err) {
15107
15802
  if (err.code === "ENOENT") {
15108
15803
  return { version: 1, projects: [] };
@@ -15207,7 +15902,7 @@ function pruneTtlMs() {
15207
15902
  }
15208
15903
  async function statPathStatus(p) {
15209
15904
  try {
15210
- const stat = await fs33.stat(p);
15905
+ const stat = await fs34.stat(p);
15211
15906
  return stat.isDirectory() ? "present" : "unknown";
15212
15907
  } catch (err) {
15213
15908
  return err.code === "ENOENT" ? "gone" : "unknown";
@@ -15252,14 +15947,14 @@ import cors from "@fastify/cors";
15252
15947
  import { DivergenceTypeSchema, PoliciesCheckBodySchema, PolicySeveritySchema } from "@neat.is/types";
15253
15948
 
15254
15949
  // src/extend/index.ts
15255
- import { promises as fs35 } from "fs";
15256
- import path68 from "path";
15950
+ import { promises as fs36 } from "fs";
15951
+ import path70 from "path";
15257
15952
  import os3 from "os";
15258
15953
  import { resolve as registryResolve, list as registryList } from "@neat.is/instrumentation-registry";
15259
15954
 
15260
15955
  // src/installers/package-manager.ts
15261
- import { promises as fs34 } from "fs";
15262
- import path67 from "path";
15956
+ import { promises as fs35 } from "fs";
15957
+ import path69 from "path";
15263
15958
  import { spawn } from "child_process";
15264
15959
  var LOCKFILE_PRIORITY = [
15265
15960
  { lockfile: "bun.lockb", pm: "bun", args: ["install", "--no-summary"] },
@@ -15274,29 +15969,29 @@ var LOCKFILE_PRIORITY = [
15274
15969
  var NPM_FALLBACK_ARGS = ["install", "--no-audit", "--no-fund", "--prefer-offline"];
15275
15970
  async function exists2(p) {
15276
15971
  try {
15277
- await fs34.access(p);
15972
+ await fs35.access(p);
15278
15973
  return true;
15279
15974
  } catch {
15280
15975
  return false;
15281
15976
  }
15282
15977
  }
15283
15978
  async function detectPackageManager(serviceDir) {
15284
- let dir = path67.resolve(serviceDir);
15979
+ let dir = path69.resolve(serviceDir);
15285
15980
  const stops = /* @__PURE__ */ new Set();
15286
15981
  for (let i = 0; i < 64; i++) {
15287
15982
  if (stops.has(dir)) break;
15288
15983
  stops.add(dir);
15289
15984
  for (const candidate of LOCKFILE_PRIORITY) {
15290
- const lockPath = path67.join(dir, candidate.lockfile);
15985
+ const lockPath = path69.join(dir, candidate.lockfile);
15291
15986
  if (await exists2(lockPath)) {
15292
15987
  return { pm: candidate.pm, cwd: dir, args: [...candidate.args] };
15293
15988
  }
15294
15989
  }
15295
- const parent = path67.dirname(dir);
15990
+ const parent = path69.dirname(dir);
15296
15991
  if (parent === dir) break;
15297
15992
  dir = parent;
15298
15993
  }
15299
- return { pm: "npm", cwd: path67.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
15994
+ return { pm: "npm", cwd: path69.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
15300
15995
  }
15301
15996
  async function runPackageManagerInstall(cmd) {
15302
15997
  return new Promise((resolve) => {
@@ -15338,15 +16033,15 @@ ${err.message}`
15338
16033
  // src/extend/index.ts
15339
16034
  async function fileExists2(p) {
15340
16035
  try {
15341
- await fs35.access(p);
16036
+ await fs36.access(p);
15342
16037
  return true;
15343
16038
  } catch {
15344
16039
  return false;
15345
16040
  }
15346
16041
  }
15347
16042
  async function readPackageJson(scanPath) {
15348
- const pkgPath = path68.join(scanPath, "package.json");
15349
- const raw = await fs35.readFile(pkgPath, "utf8");
16043
+ const pkgPath = path70.join(scanPath, "package.json");
16044
+ const raw = await fs36.readFile(pkgPath, "utf8");
15350
16045
  return JSON.parse(raw);
15351
16046
  }
15352
16047
  var HOOK_WALK_SKIP_DIRS = /* @__PURE__ */ new Set([
@@ -15359,27 +16054,27 @@ var HOOK_WALK_SKIP_DIRS = /* @__PURE__ */ new Set([
15359
16054
  ]);
15360
16055
  async function findHookFiles(scanPath) {
15361
16056
  const found = [];
15362
- const walk9 = async (dir) => {
15363
- const entries = await fs35.readdir(dir, { withFileTypes: true }).catch(() => []);
16057
+ const walk10 = async (dir) => {
16058
+ const entries = await fs36.readdir(dir, { withFileTypes: true }).catch(() => []);
15364
16059
  for (const entry of entries) {
15365
16060
  if (entry.isDirectory()) {
15366
16061
  if (entry.name.startsWith(".") || HOOK_WALK_SKIP_DIRS.has(entry.name)) continue;
15367
- await walk9(path68.join(dir, entry.name));
16062
+ await walk10(path70.join(dir, entry.name));
15368
16063
  } else if (entry.isFile()) {
15369
16064
  if ((entry.name.startsWith("instrumentation") || entry.name.startsWith("otel-init")) && /\.(ts|js|cjs|mjs)$/.test(entry.name)) {
15370
- const rel = path68.relative(scanPath, path68.join(dir, entry.name));
15371
- found.push(rel.split(path68.sep).join("/"));
16065
+ const rel = path70.relative(scanPath, path70.join(dir, entry.name));
16066
+ found.push(rel.split(path70.sep).join("/"));
15372
16067
  }
15373
16068
  }
15374
16069
  }
15375
16070
  };
15376
- await walk9(scanPath);
16071
+ await walk10(scanPath);
15377
16072
  return found.sort();
15378
16073
  }
15379
16074
  async function pickPrimaryHookFile(scanPath, hookFiles, snippet2) {
15380
16075
  let fallback = null;
15381
16076
  for (const file of hookFiles) {
15382
- const content = await fs35.readFile(path68.join(scanPath, file), "utf8");
16077
+ const content = await fs36.readFile(path70.join(scanPath, file), "utf8");
15383
16078
  const patched = splicedContent(content, snippet2);
15384
16079
  if (patched !== null) return { file, content, patched };
15385
16080
  if (fallback === null) fallback = { file, content };
@@ -15387,12 +16082,12 @@ async function pickPrimaryHookFile(scanPath, hookFiles, snippet2) {
15387
16082
  return { file: fallback.file, content: fallback.content, patched: null };
15388
16083
  }
15389
16084
  function extendLogPath() {
15390
- return process.env.NEAT_EXTEND_LOG ?? path68.join(os3.homedir(), ".neat", "extend-log.ndjson");
16085
+ return process.env.NEAT_EXTEND_LOG ?? path70.join(os3.homedir(), ".neat", "extend-log.ndjson");
15391
16086
  }
15392
16087
  async function appendExtendLog(entry) {
15393
16088
  const logPath = extendLogPath();
15394
- await fs35.mkdir(path68.dirname(logPath), { recursive: true });
15395
- await fs35.appendFile(logPath, JSON.stringify(entry) + "\n", "utf8");
16089
+ await fs36.mkdir(path70.dirname(logPath), { recursive: true });
16090
+ await fs36.appendFile(logPath, JSON.stringify(entry) + "\n", "utf8");
15396
16091
  }
15397
16092
  function splicedContent(fileContent, snippet2) {
15398
16093
  if (fileContent.includes("__INSTRUMENTATION_BLOCK__")) {
@@ -15450,7 +16145,7 @@ function lookupInstrumentation(library, installedVersion) {
15450
16145
  }
15451
16146
  async function describeProjectInstrumentation(ctx) {
15452
16147
  const hookFiles = await findHookFiles(ctx.scanPath);
15453
- const envNeat = await fileExists2(path68.join(ctx.scanPath, ".env.neat"));
16148
+ const envNeat = await fileExists2(path70.join(ctx.scanPath, ".env.neat"));
15454
16149
  const registryInstrPackages = new Set(
15455
16150
  registryList().map((e) => e.instrumentation_package).filter((p) => !!p)
15456
16151
  );
@@ -15472,7 +16167,7 @@ async function applyExtension(ctx, args, options) {
15472
16167
  );
15473
16168
  }
15474
16169
  for (const file of hookFiles) {
15475
- const content = await fs35.readFile(path68.join(ctx.scanPath, file), "utf8");
16170
+ const content = await fs36.readFile(path70.join(ctx.scanPath, file), "utf8");
15476
16171
  if (content.includes(args.registration_snippet)) {
15477
16172
  return { library: args.library, filesTouched: [], depsAdded: [], installOutput: "", alreadyApplied: true };
15478
16173
  }
@@ -15484,18 +16179,18 @@ async function applyExtension(ctx, args, options) {
15484
16179
  );
15485
16180
  }
15486
16181
  const primaryFile = primary.file;
15487
- const primaryPath = path68.join(ctx.scanPath, primaryFile);
16182
+ const primaryPath = path70.join(ctx.scanPath, primaryFile);
15488
16183
  const filesTouched = [];
15489
16184
  const depsAdded = [];
15490
- const pkgPath = path68.join(ctx.scanPath, "package.json");
16185
+ const pkgPath = path70.join(ctx.scanPath, "package.json");
15491
16186
  const pkg = await readPackageJson(ctx.scanPath);
15492
16187
  if (!(pkg.dependencies ?? {})[args.instrumentation_package]) {
15493
16188
  pkg.dependencies = { ...pkg.dependencies ?? {}, [args.instrumentation_package]: args.version };
15494
- await fs35.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
16189
+ await fs36.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
15495
16190
  filesTouched.push("package.json");
15496
16191
  depsAdded.push(`${args.instrumentation_package}@${args.version}`);
15497
16192
  }
15498
- await fs35.writeFile(primaryPath, primary.patched, "utf8");
16193
+ await fs36.writeFile(primaryPath, primary.patched, "utf8");
15499
16194
  filesTouched.push(primaryFile);
15500
16195
  const cmd = await detectPackageManager(ctx.scanPath);
15501
16196
  const installer = options?.runInstall ?? runPackageManagerInstall;
@@ -15526,7 +16221,7 @@ async function dryRunExtension(ctx, args) {
15526
16221
  };
15527
16222
  }
15528
16223
  for (const file of hookFiles) {
15529
- const content = await fs35.readFile(path68.join(ctx.scanPath, file), "utf8");
16224
+ const content = await fs36.readFile(path70.join(ctx.scanPath, file), "utf8");
15530
16225
  if (content.includes(args.registration_snippet)) {
15531
16226
  return {
15532
16227
  library: args.library,
@@ -15561,28 +16256,28 @@ async function rollbackExtension(ctx, args) {
15561
16256
  if (!await fileExists2(logPath)) {
15562
16257
  return { undone: false, message: "no apply found for library" };
15563
16258
  }
15564
- const raw = await fs35.readFile(logPath, "utf8");
16259
+ const raw = await fs36.readFile(logPath, "utf8");
15565
16260
  const entries = raw.trim().split("\n").filter(Boolean).map((line) => JSON.parse(line));
15566
16261
  const match = [...entries].reverse().find((e) => e.project === ctx.project && e.library === args.library);
15567
16262
  if (!match) {
15568
16263
  return { undone: false, message: "no apply found for library" };
15569
16264
  }
15570
- const pkgPath = path68.join(ctx.scanPath, "package.json");
16265
+ const pkgPath = path70.join(ctx.scanPath, "package.json");
15571
16266
  if (await fileExists2(pkgPath)) {
15572
16267
  const pkg = await readPackageJson(ctx.scanPath);
15573
16268
  if (pkg.dependencies?.[match.instrumentation_package]) {
15574
16269
  const { [match.instrumentation_package]: _removed, ...rest } = pkg.dependencies;
15575
16270
  pkg.dependencies = rest;
15576
- await fs35.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
16271
+ await fs36.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
15577
16272
  }
15578
16273
  }
15579
16274
  const hookFiles = await findHookFiles(ctx.scanPath);
15580
16275
  for (const file of hookFiles) {
15581
- const filePath = path68.join(ctx.scanPath, file);
15582
- const content = await fs35.readFile(filePath, "utf8");
16276
+ const filePath = path70.join(ctx.scanPath, file);
16277
+ const content = await fs36.readFile(filePath, "utf8");
15583
16278
  if (content.includes(match.registration_snippet)) {
15584
16279
  const filtered = content.split("\n").filter((line) => !line.includes(match.registration_snippet)).join("\n");
15585
- await fs35.writeFile(filePath, filtered, "utf8");
16280
+ await fs36.writeFile(filePath, filtered, "utf8");
15586
16281
  break;
15587
16282
  }
15588
16283
  }
@@ -15693,8 +16388,8 @@ data: ${JSON.stringify(envelope.payload)}
15693
16388
 
15694
16389
  // src/connectors-config.ts
15695
16390
  import os4 from "os";
15696
- import path69 from "path";
15697
- import { promises as fs36 } from "fs";
16391
+ import path71 from "path";
16392
+ import { promises as fs37 } from "fs";
15698
16393
  var CONNECTORS_CONFIG_VERSION = 1;
15699
16394
  var EnvRefUnsetError = class extends Error {
15700
16395
  ref;
@@ -15708,17 +16403,17 @@ var EnvRefUnsetError = class extends Error {
15708
16403
  };
15709
16404
  function neatHome2() {
15710
16405
  const override = process.env.NEAT_HOME;
15711
- if (override && override.length > 0) return path69.resolve(override);
15712
- return path69.join(os4.homedir(), ".neat");
16406
+ if (override && override.length > 0) return path71.resolve(override);
16407
+ return path71.join(os4.homedir(), ".neat");
15713
16408
  }
15714
16409
  function connectorsConfigPath(home = neatHome2()) {
15715
- return path69.join(home, "connectors.json");
16410
+ return path71.join(home, "connectors.json");
15716
16411
  }
15717
16412
  var MODE_MASK_LOOSER_THAN_0600 = 63;
15718
16413
  async function warnIfModeLooserThan0600(file) {
15719
16414
  if (process.platform === "win32") return;
15720
16415
  try {
15721
- const stat = await fs36.stat(file);
16416
+ const stat = await fs37.stat(file);
15722
16417
  if ((stat.mode & MODE_MASK_LOOSER_THAN_0600) !== 0) {
15723
16418
  const mode = (stat.mode & 511).toString(8).padStart(3, "0");
15724
16419
  console.warn(
@@ -15732,7 +16427,7 @@ async function readConnectorsConfig(home = neatHome2()) {
15732
16427
  const file = connectorsConfigPath(home);
15733
16428
  let raw;
15734
16429
  try {
15735
- raw = await fs36.readFile(file, "utf8");
16430
+ raw = await fs37.readFile(file, "utf8");
15736
16431
  } catch (err) {
15737
16432
  if (err.code === "ENOENT") {
15738
16433
  return { version: CONNECTORS_CONFIG_VERSION, connectors: [] };
@@ -15843,7 +16538,7 @@ function connectorMatchesProject(entry, project) {
15843
16538
  var CONNECTORS_LOCK_TIMEOUT_MS = 5e3;
15844
16539
  var CONNECTORS_LOCK_RETRY_MS = 50;
15845
16540
  function connectorsConfigLockPath(home = neatHome2()) {
15846
- return path69.join(home, "connectors.json.lock");
16541
+ return path71.join(home, "connectors.json.lock");
15847
16542
  }
15848
16543
  function isEnvRef(value) {
15849
16544
  return value.length > 1 && value.startsWith("$");
@@ -15856,9 +16551,9 @@ function redactCredentialRef(ref) {
15856
16551
  return out;
15857
16552
  }
15858
16553
  async function writeConfigAtomically0600(file, contents) {
15859
- await fs36.mkdir(path69.dirname(file), { recursive: true });
16554
+ await fs37.mkdir(path71.dirname(file), { recursive: true });
15860
16555
  const tmp = `${file}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
15861
- const fd = await fs36.open(tmp, "w", 384);
16556
+ const fd = await fs37.open(tmp, "w", 384);
15862
16557
  try {
15863
16558
  await fd.writeFile(contents, "utf8");
15864
16559
  await fd.chmod(384);
@@ -15866,14 +16561,14 @@ async function writeConfigAtomically0600(file, contents) {
15866
16561
  } finally {
15867
16562
  await fd.close();
15868
16563
  }
15869
- await fs36.rename(tmp, file);
16564
+ await fs37.rename(tmp, file);
15870
16565
  }
15871
16566
  async function acquireConnectorsLock(lockPath, timeoutMs = CONNECTORS_LOCK_TIMEOUT_MS) {
15872
16567
  const deadline = Date.now() + timeoutMs;
15873
- await fs36.mkdir(path69.dirname(lockPath), { recursive: true });
16568
+ await fs37.mkdir(path71.dirname(lockPath), { recursive: true });
15874
16569
  for (; ; ) {
15875
16570
  try {
15876
- const fd = await fs36.open(lockPath, "wx");
16571
+ const fd = await fs37.open(lockPath, "wx");
15877
16572
  try {
15878
16573
  await fd.writeFile(`${process.pid}
15879
16574
  `, "utf8");
@@ -15893,7 +16588,7 @@ async function acquireConnectorsLock(lockPath, timeoutMs = CONNECTORS_LOCK_TIMEO
15893
16588
  }
15894
16589
  }
15895
16590
  async function releaseConnectorsLock(lockPath) {
15896
- await fs36.unlink(lockPath).catch(() => {
16591
+ await fs37.unlink(lockPath).catch(() => {
15897
16592
  });
15898
16593
  }
15899
16594
  async function withConnectorsLock(home, fn) {
@@ -16522,10 +17217,10 @@ var SUPABASE_RPC_TARGET_KIND = "supabase-rpc";
16522
17217
  // src/connectors/supabase/map.ts
16523
17218
  var REST_RPC_PATH_RE = /^\/rest\/v1\/rpc\/([^/?]+)/;
16524
17219
  var REST_TABLE_PATH_RE = /^\/rest\/v1\/([^/?]+)/;
16525
- function targetFromRestPath(path70) {
16526
- const rpcMatch = REST_RPC_PATH_RE.exec(path70);
17220
+ function targetFromRestPath(path72) {
17221
+ const rpcMatch = REST_RPC_PATH_RE.exec(path72);
16527
17222
  if (rpcMatch) return { targetKind: SUPABASE_RPC_TARGET_KIND, name: rpcMatch[1] };
16528
- const tableMatch = REST_TABLE_PATH_RE.exec(path70);
17223
+ const tableMatch = REST_TABLE_PATH_RE.exec(path72);
16529
17224
  if (tableMatch) return { targetKind: SUPABASE_TABLE_TARGET_KIND, name: tableMatch[1] };
16530
17225
  return null;
16531
17226
  }
@@ -16634,21 +17329,21 @@ async function fetchPgStatStatements(connectionString, limit = DEFAULT_STATEMENT
16634
17329
  }
16635
17330
 
16636
17331
  // src/connectors/supabase/resolve.ts
16637
- import { EdgeType as EdgeType26, infraId as infraId20 } from "@neat.is/types";
17332
+ import { EdgeType as EdgeType26, infraId as infraId21 } from "@neat.is/types";
16638
17333
  function createSupabaseResolveTarget(graph, config) {
16639
17334
  return (signal, _ctx) => {
16640
17335
  if (signal.targetKind !== SUPABASE_TABLE_TARGET_KIND && signal.targetKind !== SUPABASE_RPC_TARGET_KIND) {
16641
17336
  return null;
16642
17337
  }
16643
- const subResourceId = infraId20(signal.targetKind, `${config.nodeRef}/${signal.targetName}`);
17338
+ const subResourceId = infraId21(signal.targetKind, `${config.nodeRef}/${signal.targetName}`);
16644
17339
  if (graph.hasNode(subResourceId)) {
16645
17340
  return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType: EdgeType26.CALLS };
16646
17341
  }
16647
- const bareResourceId = infraId20(signal.targetKind, signal.targetName);
17342
+ const bareResourceId = infraId21(signal.targetKind, signal.targetName);
16648
17343
  if (graph.hasNode(bareResourceId)) {
16649
17344
  return { targetNodeId: bareResourceId, serviceName: config.serviceName, edgeType: EdgeType26.CALLS };
16650
17345
  }
16651
- const projectLevelId = infraId20("supabase", config.nodeRef);
17346
+ const projectLevelId = infraId21("supabase", config.nodeRef);
16652
17347
  if (graph.hasNode(projectLevelId)) {
16653
17348
  return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType: EdgeType26.CALLS };
16654
17349
  }
@@ -17120,9 +17815,9 @@ function parseFirebaseTargetName(targetName) {
17120
17815
  const secondSep = rest.indexOf(FIELD_SEP);
17121
17816
  if (secondSep === -1) return null;
17122
17817
  const method = rest.slice(0, secondSep);
17123
- const path70 = rest.slice(secondSep + 1);
17124
- if (!resourceName || !method || !path70) return null;
17125
- return { resourceName, method, path: path70 };
17818
+ const path72 = rest.slice(secondSep + 1);
17819
+ if (!resourceName || !method || !path72) return null;
17820
+ return { resourceName, method, path: path72 };
17126
17821
  }
17127
17822
  function resourceNameFor(type, labels) {
17128
17823
  if (!labels) return null;
@@ -17160,14 +17855,14 @@ function mapLogEntryToSignal(entry) {
17160
17855
  if (!req) return null;
17161
17856
  if (typeof req.requestMethod !== "string" || req.requestMethod.length === 0) return null;
17162
17857
  const method = req.requestMethod.toUpperCase();
17163
- const path70 = pathFromRequestUrl(req.requestUrl);
17164
- if (path70 === null) return null;
17858
+ const path72 = pathFromRequestUrl(req.requestUrl);
17859
+ if (path72 === null) return null;
17165
17860
  const timestamp = entry.timestamp;
17166
17861
  if (typeof timestamp !== "string" || timestamp.length === 0) return null;
17167
17862
  const isError = typeof req.status === "number" && req.status >= ERROR_STATUS_THRESHOLD2;
17168
17863
  return {
17169
17864
  targetKind: resourceType,
17170
- targetName: packFirebaseTargetName({ resourceName, method, path: path70 }),
17865
+ targetName: packFirebaseTargetName({ resourceName, method, path: path72 }),
17171
17866
  callCount: 1,
17172
17867
  errorCount: isError ? 1 : 0,
17173
17868
  lastObservedIso: timestamp
@@ -17253,7 +17948,7 @@ function createFirebaseConnector(graph, serviceMap) {
17253
17948
  }
17254
17949
 
17255
17950
  // src/connectors/cloudflare/connector.ts
17256
- import { EdgeType as EdgeType29, NodeType as NodeType35, fileId as fileId5, infraId as infraId21 } from "@neat.is/types";
17951
+ import { EdgeType as EdgeType29, NodeType as NodeType35, fileId as fileId5, infraId as infraId22 } from "@neat.is/types";
17257
17952
 
17258
17953
  // src/connectors/cloudflare/client.ts
17259
17954
  import { randomUUID } from "crypto";
@@ -17364,7 +18059,7 @@ function mapEventToSignal(event) {
17364
18059
  if (Number.isNaN(observedAt.getTime())) return null;
17365
18060
  const statusCode = metadata?.statusCode;
17366
18061
  const isError = typeof statusCode === "number" && statusCode >= ERROR_STATUS_THRESHOLD3;
17367
- const path70 = metadata?.trigger ? parsePathFromTrigger(metadata.trigger) : void 0;
18062
+ const path72 = metadata?.trigger ? parsePathFromTrigger(metadata.trigger) : void 0;
17368
18063
  return {
17369
18064
  targetKind: CLOUDFLARE_TARGET_KIND,
17370
18065
  targetName: scriptName,
@@ -17372,7 +18067,7 @@ function mapEventToSignal(event) {
17372
18067
  errorCount: isError ? 1 : 0,
17373
18068
  lastObservedIso: observedAt.toISOString(),
17374
18069
  method,
17375
- ...path70 ? { path: path70 } : {},
18070
+ ...path72 ? { path: path72 } : {},
17376
18071
  ...typeof statusCode === "number" ? { statusCode } : {},
17377
18072
  ...typeof metadata?.duration === "number" ? { duration: metadata.duration } : {}
17378
18073
  };
@@ -17418,8 +18113,8 @@ function findTaggedWorkerFileNode(graph, workerName) {
17418
18113
  });
17419
18114
  return found;
17420
18115
  }
17421
- function findMatchingRouteNode(graph, serviceName, method, path70) {
17422
- const normalizedPath = normalizePathTemplate(path70);
18116
+ function findMatchingRouteNode(graph, serviceName, method, path72) {
18117
+ const normalizedPath = normalizePathTemplate(path72);
17423
18118
  let found = null;
17424
18119
  graph.forEachNode((id, attrs) => {
17425
18120
  if (found) return;
@@ -17436,10 +18131,10 @@ function createCloudflareResolveTarget(config, graph) {
17436
18131
  return (signal) => {
17437
18132
  if (signal.targetKind !== CLOUDFLARE_TARGET_KIND) return null;
17438
18133
  const scriptName = signal.targetName;
17439
- const { method, path: path70 } = signal;
18134
+ const { method, path: path72 } = signal;
17440
18135
  const resolveRouteGrain = (serviceName, wholeFileId) => {
17441
- if (!method || !path70) return wholeFileId;
17442
- return findMatchingRouteNode(graph, serviceName, method, path70) ?? wholeFileId;
18136
+ if (!method || !path72) return wholeFileId;
18137
+ return findMatchingRouteNode(graph, serviceName, method, path72) ?? wholeFileId;
17443
18138
  };
17444
18139
  const mapping = config.workers?.[scriptName];
17445
18140
  if (mapping) {
@@ -17460,7 +18155,7 @@ function createCloudflareResolveTarget(config, graph) {
17460
18155
  };
17461
18156
  }
17462
18157
  return {
17463
- targetNodeId: infraId21("cloudflare-worker", scriptName),
18158
+ targetNodeId: infraId22("cloudflare-worker", scriptName),
17464
18159
  serviceName: scriptName,
17465
18160
  edgeType: EdgeType29.CALLS,
17466
18161
  ensureInfraNode: { kind: "cloudflare-worker", name: scriptName, provider: "cloudflare" }
@@ -17644,12 +18339,12 @@ function diffNeonStatementsToSignals(rows, previous, observedAtIso) {
17644
18339
  }
17645
18340
 
17646
18341
  // src/connectors/neon/resolve.ts
17647
- import { EdgeType as EdgeType30, infraId as infraId22 } from "@neat.is/types";
18342
+ import { EdgeType as EdgeType30, infraId as infraId23 } from "@neat.is/types";
17648
18343
  function createNeonResolveTarget(config) {
17649
18344
  return (signal) => {
17650
18345
  if (signal.targetKind !== NEON_SQL_TABLE_TARGET_KIND || !signal.targetName) return null;
17651
18346
  return {
17652
- targetNodeId: infraId22("sql-table", signal.targetName),
18347
+ targetNodeId: infraId23("sql-table", signal.targetName),
17653
18348
  serviceName: config.serviceName,
17654
18349
  edgeType: EdgeType30.CALLS,
17655
18350
  ensureInfraNode: { kind: "sql-table", name: signal.targetName, provider: "neon" }
@@ -17769,9 +18464,9 @@ function parseCloudRunTargetName(targetName) {
17769
18464
  const secondSep = rest.indexOf(FIELD_SEP2);
17770
18465
  if (secondSep === -1) return null;
17771
18466
  const method = rest.slice(0, secondSep);
17772
- const path70 = rest.slice(secondSep + 1);
17773
- if (!serviceName || !method || !path70) return null;
17774
- return { serviceName, method, path: path70 };
18467
+ const path72 = rest.slice(secondSep + 1);
18468
+ if (!serviceName || !method || !path72) return null;
18469
+ return { serviceName, method, path: path72 };
17775
18470
  }
17776
18471
 
17777
18472
  // src/connectors/cloud-run/map.ts
@@ -17800,14 +18495,14 @@ function mapLogEntryToSignal2(entry) {
17800
18495
  if (!req) return null;
17801
18496
  if (typeof req.requestMethod !== "string" || req.requestMethod.length === 0) return null;
17802
18497
  const method = req.requestMethod.toUpperCase();
17803
- const path70 = pathFromRequestUrl2(req.requestUrl);
17804
- if (path70 === null) return null;
18498
+ const path72 = pathFromRequestUrl2(req.requestUrl);
18499
+ if (path72 === null) return null;
17805
18500
  const timestamp = entry.timestamp;
17806
18501
  if (typeof timestamp !== "string" || timestamp.length === 0) return null;
17807
18502
  const isError = typeof req.status === "number" && req.status >= ERROR_STATUS_THRESHOLD4;
17808
18503
  return {
17809
18504
  targetKind: CLOUD_RUN_TARGET_KIND,
17810
- targetName: packCloudRunTargetName({ serviceName, method, path: path70 }),
18505
+ targetName: packCloudRunTargetName({ serviceName, method, path: path72 }),
17811
18506
  callCount: 1,
17812
18507
  errorCount: isError ? 1 : 0,
17813
18508
  lastObservedIso: timestamp
@@ -17823,7 +18518,7 @@ function mapLogEntriesToSignals2(entries) {
17823
18518
  }
17824
18519
 
17825
18520
  // src/connectors/cloud-run/resolve.ts
17826
- import { EdgeType as EdgeType31, NodeType as NodeType36, infraId as infraId23 } from "@neat.is/types";
18521
+ import { EdgeType as EdgeType31, NodeType as NodeType36, infraId as infraId24 } from "@neat.is/types";
17827
18522
  var CLOUD_RUN_SERVICE_INFRA_KIND = "cloud-run-service";
17828
18523
  function findMatchingRouteNode2(graph, serviceName, method, normalizedPath) {
17829
18524
  let found = null;
@@ -17845,21 +18540,21 @@ function createCloudRunResolveTarget(graph, config) {
17845
18540
  if (signal.targetKind !== CLOUD_RUN_TARGET_KIND) return null;
17846
18541
  const identity = parseCloudRunTargetName(signal.targetName);
17847
18542
  if (!identity) return null;
17848
- const { serviceName: gcpServiceName, method, path: path70 } = identity;
18543
+ const { serviceName: gcpServiceName, method, path: path72 } = identity;
17849
18544
  const mappedService = config.serviceMap?.[gcpServiceName];
17850
18545
  if (mappedService) {
17851
18546
  const routeNodeId = findMatchingRouteNode2(
17852
18547
  graph,
17853
18548
  mappedService,
17854
18549
  method,
17855
- normalizePathTemplate(path70)
18550
+ normalizePathTemplate(path72)
17856
18551
  );
17857
18552
  if (routeNodeId) {
17858
18553
  return { targetNodeId: routeNodeId, serviceName: mappedService, edgeType: EdgeType31.CALLS };
17859
18554
  }
17860
18555
  }
17861
18556
  return {
17862
- targetNodeId: infraId23(CLOUD_RUN_SERVICE_INFRA_KIND, gcpServiceName),
18557
+ targetNodeId: infraId24(CLOUD_RUN_SERVICE_INFRA_KIND, gcpServiceName),
17863
18558
  serviceName: mappedService ?? gcpServiceName,
17864
18559
  edgeType: EdgeType31.CALLS,
17865
18560
  ensureInfraNode: {
@@ -18191,17 +18886,17 @@ function mapInsightsToSignals(rows, observedAtIso) {
18191
18886
  }
18192
18887
 
18193
18888
  // src/connectors/planetscale/resolve.ts
18194
- import { EdgeType as EdgeType33, infraId as infraId24 } from "@neat.is/types";
18889
+ import { EdgeType as EdgeType33, infraId as infraId25 } from "@neat.is/types";
18195
18890
  var PLANETSCALE_DATABASE_KIND = "planetscale-database";
18196
18891
  function createPlanetscaleResolveTarget(graph, config) {
18197
18892
  const databaseName = `${config.organization}/${config.database}`;
18198
18893
  return (signal, _ctx) => {
18199
18894
  if (signal.targetKind !== PLANETSCALE_SQL_TABLE_TARGET_KIND || !signal.targetName) return null;
18200
- const tableId = infraId24("sql-table", signal.targetName);
18895
+ const tableId = infraId25("sql-table", signal.targetName);
18201
18896
  if (graph.hasNode(tableId)) {
18202
18897
  return { targetNodeId: tableId, serviceName: config.serviceName, edgeType: EdgeType33.CALLS };
18203
18898
  }
18204
- const providerId = infraId24(PLANETSCALE_DATABASE_KIND, databaseName);
18899
+ const providerId = infraId25(PLANETSCALE_DATABASE_KIND, databaseName);
18205
18900
  return {
18206
18901
  targetNodeId: providerId,
18207
18902
  serviceName: config.serviceName,
@@ -20101,4 +20796,4 @@ export {
20101
20796
  deprovisionConnector,
20102
20797
  buildApi
20103
20798
  };
20104
- //# sourceMappingURL=chunk-JUVJBH23.js.map
20799
+ //# sourceMappingURL=chunk-RQQUI3NQ.js.map