@neat.is/core 0.8.1 → 0.8.2-dev.20260818

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.
@@ -961,7 +961,7 @@ import { promises as fs3 } from "fs";
961
961
  import path3 from "path";
962
962
  import { parse as parseYaml } from "yaml";
963
963
  import { extractedEdgeId } from "@neat.is/types";
964
- var SERVICE_FILE_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".mjs", ".cjs", ".ts", ".tsx", ".py", ".go", ".rb", ".php"]);
964
+ var SERVICE_FILE_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".mjs", ".cjs", ".ts", ".tsx", ".py", ".go", ".rb", ".php", ".cs", ".java"]);
965
965
  var CONFIG_FILE_EXTENSIONS = /* @__PURE__ */ new Set([".yaml", ".yml"]);
966
966
  var JSON_CONFIG_FILENAMES = /* @__PURE__ */ new Set(["app.json", "app.config.json", "eas.json"]);
967
967
  var IGNORED_DIRS = /* @__PURE__ */ new Set([
@@ -1301,6 +1301,10 @@ function languageForPath(relPath) {
1301
1301
  return "ruby";
1302
1302
  case ".php":
1303
1303
  return "php";
1304
+ case ".cs":
1305
+ return "csharp";
1306
+ case ".java":
1307
+ return "java";
1304
1308
  case ".ts":
1305
1309
  case ".tsx":
1306
1310
  return "typescript";
@@ -1842,8 +1846,8 @@ function chiRoutesFromSource(source, parser) {
1842
1846
  chiWalk(tree.rootNode, "", out);
1843
1847
  return out;
1844
1848
  }
1845
- function stripChiRegex(path64) {
1846
- return path64.replace(/\{([^{}:]+):[^{}]*\}/g, "{$1}");
1849
+ function stripChiRegex(path67) {
1850
+ return path67.replace(/\{([^{}:]+):[^{}]*\}/g, "{$1}");
1847
1851
  }
1848
1852
  function chiWalk(node, prefix, out) {
1849
1853
  for (let i = 0; i < node.namedChildCount; i++) {
@@ -2515,9 +2519,9 @@ function rubyRocketRoute(args) {
2515
2519
  if (!pair || pair.type !== "pair") continue;
2516
2520
  const k = pair.childForFieldName("key");
2517
2521
  if (k?.type !== "string") continue;
2518
- const path64 = rubyLiteral(k);
2519
- if (path64 === null) continue;
2520
- return { path: path64, target: rubyLiteral(pair.childForFieldName("value")) };
2522
+ const path67 = rubyLiteral(k);
2523
+ if (path67 === null) continue;
2524
+ return { path: path67, target: rubyLiteral(pair.childForFieldName("value")) };
2521
2525
  }
2522
2526
  return null;
2523
2527
  }
@@ -4634,29 +4638,29 @@ function promoteFrontierNodes(graph, opts = {}) {
4634
4638
  toPromote.push({ frontierId: id, serviceId: target });
4635
4639
  });
4636
4640
  let promoted = 0;
4637
- for (const { frontierId: frontierId2, serviceId: serviceId9 } of toPromote) {
4641
+ for (const { frontierId: frontierId2, serviceId: serviceId12 } of toPromote) {
4638
4642
  if (opts.policies && opts.policies.length > 0 && opts.policyCtx) {
4639
4643
  const gate = canPromoteFrontier(graph, frontierId2, opts.policies, opts.policyCtx);
4640
4644
  if (!gate.allowed) {
4641
4645
  continue;
4642
4646
  }
4643
4647
  }
4644
- rewireFrontierEdges(graph, frontierId2, serviceId9);
4648
+ rewireFrontierEdges(graph, frontierId2, serviceId12);
4645
4649
  graph.dropNode(frontierId2);
4646
4650
  promoted++;
4647
4651
  }
4648
4652
  return promoted;
4649
4653
  }
4650
- function rewireFrontierEdges(graph, frontierId2, serviceId9) {
4654
+ function rewireFrontierEdges(graph, frontierId2, serviceId12) {
4651
4655
  const inbound = [...graph.inboundEdges(frontierId2)];
4652
4656
  const outbound = [...graph.outboundEdges(frontierId2)];
4653
4657
  for (const edgeId of inbound) {
4654
4658
  const edge = graph.getEdgeAttributes(edgeId);
4655
- rebuildEdge(graph, edge, edge.source, serviceId9, edgeId);
4659
+ rebuildEdge(graph, edge, edge.source, serviceId12, edgeId);
4656
4660
  }
4657
4661
  for (const edgeId of outbound) {
4658
4662
  const edge = graph.getEdgeAttributes(edgeId);
4659
- rebuildEdge(graph, edge, serviceId9, edge.target, edgeId);
4663
+ rebuildEdge(graph, edge, serviceId12, edge.target, edgeId);
4660
4664
  }
4661
4665
  }
4662
4666
  function rebuildEdge(graph, edge, newSource, newTarget, oldEdgeId) {
@@ -4988,19 +4992,19 @@ function confidenceFromMix(edges, now = Date.now()) {
4988
4992
  function longestIncomingWalk(graph, start, maxDepth) {
4989
4993
  let best = { path: [start], edges: [] };
4990
4994
  const visited = /* @__PURE__ */ new Set([start]);
4991
- function step(node, path64, edges) {
4992
- if (path64.length > best.path.length) {
4993
- best = { path: [...path64], edges: [...edges] };
4995
+ function step(node, path67, edges) {
4996
+ if (path67.length > best.path.length) {
4997
+ best = { path: [...path67], edges: [...edges] };
4994
4998
  }
4995
- if (path64.length - 1 >= maxDepth) return;
4999
+ if (path67.length - 1 >= maxDepth) return;
4996
5000
  const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
4997
5001
  for (const [srcId, edge] of incoming) {
4998
5002
  if (visited.has(srcId)) continue;
4999
5003
  visited.add(srcId);
5000
- path64.push(srcId);
5004
+ path67.push(srcId);
5001
5005
  edges.push(edge);
5002
- step(srcId, path64, edges);
5003
- path64.pop();
5006
+ step(srcId, path67, edges);
5007
+ path67.pop();
5004
5008
  edges.pop();
5005
5009
  visited.delete(srcId);
5006
5010
  }
@@ -5015,7 +5019,7 @@ function databaseRootCauseShape(graph, origin, walk9) {
5015
5019
  for (const id of walk9.path) {
5016
5020
  const owner = resolveOwningService(graph, id);
5017
5021
  if (!owner) continue;
5018
- const { id: serviceId9, svc } = owner;
5022
+ const { id: serviceId12, svc } = owner;
5019
5023
  const deps = svc.dependencies ?? {};
5020
5024
  for (const pair of candidatePairs) {
5021
5025
  const declared = deps[pair.driver];
@@ -5028,7 +5032,7 @@ function databaseRootCauseShape(graph, origin, walk9) {
5028
5032
  );
5029
5033
  if (!result.compatible) {
5030
5034
  return {
5031
- rootCauseNode: serviceId9,
5035
+ rootCauseNode: serviceId12,
5032
5036
  rootCauseReason: result.reason ?? "incompatible driver",
5033
5037
  ...result.minDriverVersion ? {
5034
5038
  fixRecommendation: `Upgrade ${svc.name} ${pair.driver} driver to >= ${result.minDriverVersion}`
@@ -5043,7 +5047,7 @@ function serviceRootCauseShape(graph, _origin, walk9) {
5043
5047
  for (const id of walk9.path) {
5044
5048
  const owner = resolveOwningService(graph, id);
5045
5049
  if (!owner) continue;
5046
- const { id: serviceId9, svc } = owner;
5050
+ const { id: serviceId12, svc } = owner;
5047
5051
  const deps = svc.dependencies ?? {};
5048
5052
  const serviceNodeEngine = svc.nodeEngine;
5049
5053
  for (const constraint of nodeEngineConstraints()) {
@@ -5052,7 +5056,7 @@ function serviceRootCauseShape(graph, _origin, walk9) {
5052
5056
  const result = checkNodeEngineConstraint(constraint, declared, serviceNodeEngine);
5053
5057
  if (!result.compatible && result.reason) {
5054
5058
  return {
5055
- rootCauseNode: serviceId9,
5059
+ rootCauseNode: serviceId12,
5056
5060
  rootCauseReason: result.reason,
5057
5061
  ...result.requiredNodeVersion ? {
5058
5062
  fixRecommendation: `Bump ${svc.name}'s engines.node to >= ${result.requiredNodeVersion}`
@@ -5067,7 +5071,7 @@ function serviceRootCauseShape(graph, _origin, walk9) {
5067
5071
  const result = checkPackageConflict(conflict, declared, requiredDeclared);
5068
5072
  if (!result.compatible && result.reason) {
5069
5073
  return {
5070
- rootCauseNode: serviceId9,
5074
+ rootCauseNode: serviceId12,
5071
5075
  rootCauseReason: result.reason,
5072
5076
  fixRecommendation: `Upgrade ${svc.name}'s ${conflict.requires.name} to >= ${conflict.requires.minVersion}`
5073
5077
  };
@@ -5168,9 +5172,9 @@ function rootCauseFromIncidents(nodeId, incidents, errorEvent) {
5168
5172
  function isFailingCallEdge(e) {
5169
5173
  return e.type === EdgeType6.CALLS && (e.signal?.errorCount ?? 0) > 0;
5170
5174
  }
5171
- function callSourcesForService(graph, serviceId9) {
5172
- const ids = [serviceId9];
5173
- for (const edgeId of graph.outboundEdges(serviceId9)) {
5175
+ function callSourcesForService(graph, serviceId12) {
5176
+ const ids = [serviceId12];
5177
+ for (const edgeId of graph.outboundEdges(serviceId12)) {
5174
5178
  const e = graph.getEdgeAttributes(edgeId);
5175
5179
  if (e.type !== EdgeType6.CONTAINS) continue;
5176
5180
  const tgt = graph.getNodeAttributes(e.target);
@@ -5194,9 +5198,9 @@ function failingCallDominates(e, id, curEdge, curId) {
5194
5198
  }
5195
5199
  return id < curId;
5196
5200
  }
5197
- function dominantFailingCall(graph, serviceId9, visited) {
5201
+ function dominantFailingCall(graph, serviceId12, visited) {
5198
5202
  let best = null;
5199
- for (const src of callSourcesForService(graph, serviceId9)) {
5203
+ for (const src of callSourcesForService(graph, serviceId12)) {
5200
5204
  for (const edgeId of graph.outboundEdges(src)) {
5201
5205
  const e = graph.getEdgeAttributes(edgeId);
5202
5206
  if (!isFailingCallEdge(e)) continue;
@@ -5211,26 +5215,26 @@ function dominantFailingCall(graph, serviceId9, visited) {
5211
5215
  return best;
5212
5216
  }
5213
5217
  function followFailingCallChain(graph, originServiceId, maxDepth) {
5214
- const path64 = [originServiceId];
5218
+ const path67 = [originServiceId];
5215
5219
  const edges = [];
5216
5220
  const visited = /* @__PURE__ */ new Set([originServiceId]);
5217
5221
  let current = originServiceId;
5218
5222
  for (let depth = 0; depth < maxDepth; depth++) {
5219
5223
  const hop = dominantFailingCall(graph, current, visited);
5220
5224
  if (!hop) break;
5221
- path64.push(hop.nextService);
5225
+ path67.push(hop.nextService);
5222
5226
  edges.push(hop.edge);
5223
5227
  visited.add(hop.nextService);
5224
5228
  current = hop.nextService;
5225
5229
  }
5226
5230
  if (edges.length === 0) return null;
5227
- return { path: path64, edges, culprit: current };
5231
+ return { path: path67, edges, culprit: current };
5228
5232
  }
5229
5233
  function crossServiceRootCause(graph, originId, incidents, errorEvent) {
5230
5234
  const chain = followFailingCallChain(graph, originId, ROOT_CAUSE_MAX_DEPTH);
5231
5235
  if (!chain) return null;
5232
5236
  const culprit = chain.culprit;
5233
- const path64 = [...chain.path];
5237
+ const path67 = [...chain.path];
5234
5238
  const edgeProvenances = chain.edges.map((e) => e.provenance);
5235
5239
  const baseConfidence = confidenceFromMix(chain.edges);
5236
5240
  const confidence = Math.max(0, Math.min(1, baseConfidence * INCIDENT_ROOT_CAUSE_CONFIDENCE));
@@ -5238,14 +5242,14 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
5238
5242
  if (loc) {
5239
5243
  let rootCauseNode = culprit;
5240
5244
  if (loc.fileNode) {
5241
- path64.push(loc.fileNode);
5245
+ path67.push(loc.fileNode);
5242
5246
  edgeProvenances.push(Provenance6.OBSERVED);
5243
5247
  rootCauseNode = loc.fileNode;
5244
5248
  }
5245
5249
  return RootCauseResultSchema.parse({
5246
5250
  rootCauseNode,
5247
5251
  rootCauseReason: loc.rootCauseReason,
5248
- traversalPath: path64,
5252
+ traversalPath: path67,
5249
5253
  edgeProvenances,
5250
5254
  confidence,
5251
5255
  ...loc.fixRecommendation ? { fixRecommendation: loc.fixRecommendation } : {}
@@ -5257,7 +5261,7 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
5257
5261
  return RootCauseResultSchema.parse({
5258
5262
  rootCauseNode: culprit,
5259
5263
  rootCauseReason: `${culpritName} is failing downstream calls (${errs} observed error${errs === 1 ? "" : "s"})`,
5260
- traversalPath: path64,
5264
+ traversalPath: path67,
5261
5265
  edgeProvenances,
5262
5266
  confidence,
5263
5267
  fixRecommendation: `Inspect ${culpritName}'s failing handler`
@@ -5693,10 +5697,10 @@ function enrichWithNavigation(graph, errorNodeId, legacy, incidents, now) {
5693
5697
  let traversalPath = legacy.traversalPath;
5694
5698
  let edgeProvenances = legacy.edgeProvenances;
5695
5699
  if (top.node !== seedNode) {
5696
- const path64 = findPath(graph, errorNodeId, top.node, "up", ROOT_CAUSE_MAX_DEPTH);
5697
- if (path64) {
5698
- traversalPath = path64.nodes;
5699
- edgeProvenances = path64.edges.map((e) => e.provenance);
5700
+ const path67 = findPath(graph, errorNodeId, top.node, "up", ROOT_CAUSE_MAX_DEPTH);
5701
+ if (path67) {
5702
+ traversalPath = path67.nodes;
5703
+ edgeProvenances = path67.edges.map((e) => e.provenance);
5700
5704
  } else {
5701
5705
  traversalPath = [errorNodeId, top.node];
5702
5706
  edgeProvenances = [top.provenance ?? Provenance6.OBSERVED];
@@ -5725,11 +5729,11 @@ function fixRecommendationForTop(top, seedNode, legacy) {
5725
5729
  }
5726
5730
 
5727
5731
  // src/extract/services.ts
5728
- import { promises as fs12 } from "fs";
5729
- import path14 from "path";
5732
+ import { promises as fs15 } from "fs";
5733
+ import path17 from "path";
5730
5734
  import ignore from "ignore";
5731
5735
  import { minimatch as minimatch2 } from "minimatch";
5732
- import { NodeType as NodeType9, serviceId as serviceId5 } from "@neat.is/types";
5736
+ import { NodeType as NodeType12, serviceId as serviceId8 } from "@neat.is/types";
5733
5737
 
5734
5738
  // src/extract/python.ts
5735
5739
  import { promises as fs8 } from "fs";
@@ -5918,18 +5922,230 @@ async function discoverPhpService(scanPath, dir) {
5918
5922
  return { pkg, dir, node };
5919
5923
  }
5920
5924
 
5921
- // src/extract/owners.ts
5925
+ // src/extract/csharp.ts
5922
5926
  import { promises as fs11 } from "fs";
5923
5927
  import path13 from "path";
5928
+ import { NodeType as NodeType9, serviceId as serviceId5 } from "@neat.is/types";
5929
+ var CSHARP_PROJECT_EXTS = /* @__PURE__ */ new Set([".csproj", ".sln"]);
5930
+ async function projectFiles(dir) {
5931
+ const entries = await fs11.readdir(dir, { withFileTypes: true }).catch(() => []);
5932
+ const out = [];
5933
+ for (const entry of entries) {
5934
+ if (!entry.isFile()) continue;
5935
+ if (CSHARP_PROJECT_EXTS.has(path13.extname(entry.name).toLowerCase())) out.push(entry.name);
5936
+ }
5937
+ return out.sort();
5938
+ }
5939
+ var NESTED_PROJECT_DIR = "src";
5940
+ async function resolveProjectDir(dir) {
5941
+ if ((await projectFiles(dir)).length > 0) return dir;
5942
+ const nested = path13.join(dir, NESTED_PROJECT_DIR);
5943
+ if ((await projectFiles(nested)).length > 0) return nested;
5944
+ return null;
5945
+ }
5946
+ async function hasCsharpProject(dir) {
5947
+ return await resolveProjectDir(dir) !== null;
5948
+ }
5949
+ function parseCsproj(source) {
5950
+ const dependencies = {};
5951
+ const re = /<PackageReference\b([^>]*?)\/?>/g;
5952
+ let m;
5953
+ while ((m = re.exec(source)) !== null) {
5954
+ const attrs = m[1] ?? "";
5955
+ const include = attrs.match(/\bInclude\s*=\s*"([^"]+)"/)?.[1];
5956
+ if (!include) continue;
5957
+ const version = attrs.match(/\bVersion\s*=\s*"([^"]+)"/)?.[1];
5958
+ dependencies[include] = version ?? "";
5959
+ }
5960
+ return { dependencies };
5961
+ }
5962
+ async function discoverCsharpService(scanPath, dir) {
5963
+ const projectDir = await resolveProjectDir(dir);
5964
+ if (projectDir === null) return null;
5965
+ const projects = await projectFiles(projectDir);
5966
+ const csproj = projects.find((p) => p.toLowerCase().endsWith(".csproj"));
5967
+ const name = csproj ? path13.basename(csproj, path13.extname(csproj)) : path13.basename(dir);
5968
+ let dependencies = {};
5969
+ if (csproj) {
5970
+ const raw = await fs11.readFile(path13.join(projectDir, csproj), "utf8").catch(() => "");
5971
+ dependencies = parseCsproj(raw).dependencies;
5972
+ }
5973
+ const pkg = { name, dependencies };
5974
+ const node = {
5975
+ id: serviceId5(name),
5976
+ type: NodeType9.ServiceNode,
5977
+ name,
5978
+ language: "csharp",
5979
+ dependencies,
5980
+ // Anchor on the directory that holds the project and its `.cs` files, so file
5981
+ // attribution reaches the source whether it sits at the root or under `src/`.
5982
+ repoPath: path13.relative(scanPath, projectDir)
5983
+ };
5984
+ return { pkg, dir: projectDir, node };
5985
+ }
5986
+
5987
+ // src/extract/java.ts
5988
+ import { promises as fs12 } from "fs";
5989
+ import path14 from "path";
5990
+ import { NodeType as NodeType10, serviceId as serviceId6 } from "@neat.is/types";
5991
+ var JAVA_MANIFESTS = ["pom.xml", "build.gradle", "build.gradle.kts"];
5992
+ async function hasJavaManifest(dir) {
5993
+ for (const manifest of JAVA_MANIFESTS) {
5994
+ if (await exists(path14.join(dir, manifest))) return true;
5995
+ }
5996
+ return false;
5997
+ }
5998
+ function parsePomXml(source) {
5999
+ const dependencies = {};
6000
+ const depBlock = /<dependency\b[^>]*>([\s\S]*?)<\/dependency>/g;
6001
+ let d;
6002
+ while ((d = depBlock.exec(source)) !== null) {
6003
+ const body = d[1] ?? "";
6004
+ const groupId = body.match(/<groupId>\s*([^<]+?)\s*<\/groupId>/)?.[1];
6005
+ const artifactId = body.match(/<artifactId>\s*([^<]+?)\s*<\/artifactId>/)?.[1];
6006
+ if (!groupId || !artifactId) continue;
6007
+ const version = body.match(/<version>\s*([^<]+?)\s*<\/version>/)?.[1];
6008
+ dependencies[`${groupId}:${artifactId}`] = version ?? "";
6009
+ }
6010
+ const withoutBlocks = source.replace(/<parent\b[^>]*>[\s\S]*?<\/parent>/g, "").replace(/<dependencyManagement\b[^>]*>[\s\S]*?<\/dependencyManagement>/g, "").replace(/<dependencies\b[^>]*>[\s\S]*?<\/dependencies>/g, "").replace(/<build\b[^>]*>[\s\S]*?<\/build>/g, "");
6011
+ const name = withoutBlocks.match(/<artifactId>\s*([^<]+?)\s*<\/artifactId>/)?.[1];
6012
+ return { name, dependencies };
6013
+ }
6014
+ var GRADLE_CONFIGS = "implementation|api|compileOnly|runtimeOnly|compileOnlyApi|testImplementation|testRuntimeOnly|testCompileOnly|annotationProcessor|developmentOnly|runtimeClasspath";
6015
+ function parseGradle(source) {
6016
+ const dependencies = {};
6017
+ const re = new RegExp(`\\b(?:${GRADLE_CONFIGS})\\s*\\(?\\s*(['"])([^'"]+)\\1`, "g");
6018
+ let m;
6019
+ while ((m = re.exec(source)) !== null) {
6020
+ const coord = m[2] ?? "";
6021
+ const parts = coord.split(":");
6022
+ if (parts.length < 2 || !parts[0] || !parts[1]) continue;
6023
+ const key = `${parts[0]}:${parts[1]}`;
6024
+ dependencies[key] = parts.slice(2).join(":");
6025
+ }
6026
+ return { dependencies };
6027
+ }
6028
+ async function discoverJavaService(scanPath, dir) {
6029
+ const pomPath = path14.join(dir, "pom.xml");
6030
+ const gradlePath = path14.join(dir, "build.gradle");
6031
+ const gradleKtsPath = path14.join(dir, "build.gradle.kts");
6032
+ let manifest = null;
6033
+ if (await exists(pomPath)) {
6034
+ manifest = parsePomXml(await fs12.readFile(pomPath, "utf8").catch(() => ""));
6035
+ } else if (await exists(gradlePath)) {
6036
+ manifest = parseGradle(await fs12.readFile(gradlePath, "utf8").catch(() => ""));
6037
+ } else if (await exists(gradleKtsPath)) {
6038
+ manifest = parseGradle(await fs12.readFile(gradleKtsPath, "utf8").catch(() => ""));
6039
+ }
6040
+ if (!manifest) return null;
6041
+ const name = manifest.name?.trim() || path14.basename(dir);
6042
+ const dependencies = manifest.dependencies;
6043
+ const pkg = { name, dependencies };
6044
+ const node = {
6045
+ id: serviceId6(name),
6046
+ type: NodeType10.ServiceNode,
6047
+ name,
6048
+ language: "java",
6049
+ dependencies,
6050
+ repoPath: path14.relative(scanPath, dir)
6051
+ };
6052
+ return { pkg, dir, node };
6053
+ }
6054
+
6055
+ // src/extract/dockerfile-service.ts
6056
+ import { promises as fs13 } from "fs";
6057
+ import path15 from "path";
6058
+ import { NodeType as NodeType11, serviceId as serviceId7 } from "@neat.is/types";
6059
+ function languageForSourceExt(ext) {
6060
+ switch (ext) {
6061
+ case ".py":
6062
+ return "python";
6063
+ case ".go":
6064
+ return "go";
6065
+ case ".rb":
6066
+ return "ruby";
6067
+ case ".php":
6068
+ return "php";
6069
+ case ".cs":
6070
+ return "csharp";
6071
+ case ".java":
6072
+ return "java";
6073
+ case ".ts":
6074
+ case ".tsx":
6075
+ return "typescript";
6076
+ case ".js":
6077
+ case ".mjs":
6078
+ case ".cjs":
6079
+ return "javascript";
6080
+ default:
6081
+ return null;
6082
+ }
6083
+ }
6084
+ var LANGUAGE_PRECEDENCE = ["typescript", "javascript", "python", "go", "ruby", "php", "csharp", "java"];
6085
+ async function inferServiceLanguage(dir) {
6086
+ const entries = await fs13.readdir(dir, { withFileTypes: true }).catch(() => []);
6087
+ const counts = /* @__PURE__ */ new Map();
6088
+ for (const entry of entries) {
6089
+ if (!entry.isFile()) continue;
6090
+ if (isNeatAuthoredSourceFile(entry.name)) continue;
6091
+ const ext = path15.extname(entry.name).toLowerCase();
6092
+ if (!SERVICE_FILE_EXTENSIONS.has(ext)) continue;
6093
+ const lang = languageForSourceExt(ext);
6094
+ if (!lang) continue;
6095
+ counts.set(lang, (counts.get(lang) ?? 0) + 1);
6096
+ }
6097
+ if (counts.size === 0) return null;
6098
+ let best = null;
6099
+ let bestCount = -1;
6100
+ for (const lang of LANGUAGE_PRECEDENCE) {
6101
+ const c = counts.get(lang) ?? 0;
6102
+ if (c > bestCount) {
6103
+ best = lang;
6104
+ bestCount = c;
6105
+ }
6106
+ }
6107
+ return best;
6108
+ }
6109
+ async function hasLanguageManifest(dir) {
6110
+ return await exists(path15.join(dir, "package.json")) || await exists(path15.join(dir, "pyproject.toml")) || await exists(path15.join(dir, "requirements.txt")) || await exists(path15.join(dir, "setup.py")) || await exists(path15.join(dir, "go.mod")) || await exists(path15.join(dir, "Gemfile")) || await exists(path15.join(dir, "composer.json")) || // C#'s marker is a glob (`*.csproj` / `*.sln`), so it reads the directory
6111
+ // rather than probing a fixed filename (ADR-196).
6112
+ await hasCsharpProject(dir) || // Java keys on a Maven / Gradle build manifest (ADR-197).
6113
+ await hasJavaManifest(dir);
6114
+ }
6115
+ async function hasDockerfileDeclaredService(dir) {
6116
+ if (!await exists(path15.join(dir, "Dockerfile"))) return false;
6117
+ return await inferServiceLanguage(dir) !== null;
6118
+ }
6119
+ async function discoverDockerfileService(scanPath, dir) {
6120
+ if (!await exists(path15.join(dir, "Dockerfile"))) return null;
6121
+ if (await hasLanguageManifest(dir)) return null;
6122
+ const language = await inferServiceLanguage(dir);
6123
+ if (!language) return null;
6124
+ const name = path15.basename(dir);
6125
+ const pkg = { name, dependencies: {} };
6126
+ const node = {
6127
+ id: serviceId7(name),
6128
+ type: NodeType11.ServiceNode,
6129
+ name,
6130
+ language,
6131
+ dependencies: {},
6132
+ repoPath: path15.relative(scanPath, dir)
6133
+ };
6134
+ return { pkg, dir, node };
6135
+ }
6136
+
6137
+ // src/extract/owners.ts
6138
+ import { promises as fs14 } from "fs";
6139
+ import path16 from "path";
5924
6140
  import { minimatch } from "minimatch";
5925
6141
  async function loadCodeowners(scanPath) {
5926
6142
  const candidates = [
5927
- path13.join(scanPath, "CODEOWNERS"),
5928
- path13.join(scanPath, ".github", "CODEOWNERS")
6143
+ path16.join(scanPath, "CODEOWNERS"),
6144
+ path16.join(scanPath, ".github", "CODEOWNERS")
5929
6145
  ];
5930
6146
  for (const file of candidates) {
5931
6147
  if (await exists(file)) {
5932
- const raw = await fs11.readFile(file, "utf8");
6148
+ const raw = await fs14.readFile(file, "utf8");
5933
6149
  return parseCodeowners(raw);
5934
6150
  }
5935
6151
  }
@@ -5947,7 +6163,7 @@ function parseCodeowners(raw) {
5947
6163
  return { rules };
5948
6164
  }
5949
6165
  function matchOwner(file, repoPath) {
5950
- const normalized = repoPath.split(path13.sep).join("/");
6166
+ const normalized = repoPath.split(path16.sep).join("/");
5951
6167
  for (const rule of file.rules) {
5952
6168
  if (matchesPattern(rule.pattern, normalized)) return rule.owners;
5953
6169
  }
@@ -5963,7 +6179,7 @@ function matchesPattern(rawPattern, repoPath) {
5963
6179
  return false;
5964
6180
  }
5965
6181
  async function readPackageJsonAuthor(serviceDir) {
5966
- const pkgPath = path13.join(serviceDir, "package.json");
6182
+ const pkgPath = path16.join(serviceDir, "package.json");
5967
6183
  if (!await exists(pkgPath)) return null;
5968
6184
  try {
5969
6185
  const pkg = await readJson(pkgPath);
@@ -6000,33 +6216,36 @@ function workspaceGlobs(pkg) {
6000
6216
  return null;
6001
6217
  }
6002
6218
  async function hasPythonManifest(dir) {
6003
- return await exists(path14.join(dir, "pyproject.toml")) || await exists(path14.join(dir, "requirements.txt")) || await exists(path14.join(dir, "setup.py"));
6219
+ return await exists(path17.join(dir, "pyproject.toml")) || await exists(path17.join(dir, "requirements.txt")) || await exists(path17.join(dir, "setup.py"));
6004
6220
  }
6005
6221
  async function hasGoManifest(dir) {
6006
- return exists(path14.join(dir, "go.mod"));
6222
+ return exists(path17.join(dir, "go.mod"));
6007
6223
  }
6008
6224
  async function hasRubyManifest(dir) {
6009
- return exists(path14.join(dir, "Gemfile"));
6225
+ return exists(path17.join(dir, "Gemfile"));
6010
6226
  }
6011
6227
  async function hasPhpManifest(dir) {
6012
- return exists(path14.join(dir, "composer.json"));
6228
+ return exists(path17.join(dir, "composer.json"));
6229
+ }
6230
+ async function hasCsharpManifest(dir) {
6231
+ return hasCsharpProject(dir);
6013
6232
  }
6014
6233
  async function loadGitignore(scanPath) {
6015
- const gitignorePath = path14.join(scanPath, ".gitignore");
6234
+ const gitignorePath = path17.join(scanPath, ".gitignore");
6016
6235
  if (!await exists(gitignorePath)) return null;
6017
- const raw = await fs12.readFile(gitignorePath, "utf8");
6236
+ const raw = await fs15.readFile(gitignorePath, "utf8");
6018
6237
  return ignore().add(raw);
6019
6238
  }
6020
6239
  async function walkDirs(start, scanPath, options, visit) {
6021
6240
  async function recurse(current, depth) {
6022
6241
  if (depth > options.maxDepth) return;
6023
- const entries = await fs12.readdir(current, { withFileTypes: true }).catch(() => []);
6242
+ const entries = await fs15.readdir(current, { withFileTypes: true }).catch(() => []);
6024
6243
  for (const entry of entries) {
6025
6244
  if (!entry.isDirectory()) continue;
6026
6245
  if (IGNORED_DIRS.has(entry.name)) continue;
6027
- const child = path14.join(current, entry.name);
6246
+ const child = path17.join(current, entry.name);
6028
6247
  if (options.ig) {
6029
- const rel = path14.relative(scanPath, child).split(path14.sep).join("/");
6248
+ const rel = path17.relative(scanPath, child).split(path17.sep).join("/");
6030
6249
  if (rel && options.ig.ignores(rel + "/")) continue;
6031
6250
  }
6032
6251
  if (await isPythonVenvDir(child)) continue;
@@ -6042,8 +6261,8 @@ async function expandWorkspaceGlobs(scanPath, globs) {
6042
6261
  for (const raw of globs) {
6043
6262
  const pattern = raw.replace(/^\.\//, "");
6044
6263
  if (!pattern.includes("*")) {
6045
- const candidate = path14.join(scanPath, pattern);
6046
- if (await exists(path14.join(candidate, "package.json"))) found.add(candidate);
6264
+ const candidate = path17.join(scanPath, pattern);
6265
+ if (await exists(path17.join(candidate, "package.json"))) found.add(candidate);
6047
6266
  continue;
6048
6267
  }
6049
6268
  const segments = pattern.split("/");
@@ -6052,13 +6271,13 @@ async function expandWorkspaceGlobs(scanPath, globs) {
6052
6271
  if (seg.includes("*")) break;
6053
6272
  staticSegments.push(seg);
6054
6273
  }
6055
- const start = path14.join(scanPath, ...staticSegments);
6274
+ const start = path17.join(scanPath, ...staticSegments);
6056
6275
  if (!await exists(start)) continue;
6057
6276
  const hasDoubleStar = pattern.includes("**");
6058
6277
  const walkDepth = hasDoubleStar ? scanDepth : Math.max(0, segments.length - staticSegments.length - 1);
6059
6278
  await walkDirs(start, scanPath, { maxDepth: walkDepth, ig: null }, async (dir) => {
6060
- const rel = path14.relative(scanPath, dir).split(path14.sep).join("/");
6061
- if (minimatch2(rel, pattern) && await exists(path14.join(dir, "package.json"))) {
6279
+ const rel = path17.relative(scanPath, dir).split(path17.sep).join("/");
6280
+ if (minimatch2(rel, pattern) && await exists(path17.join(dir, "package.json"))) {
6062
6281
  found.add(dir);
6063
6282
  }
6064
6283
  });
@@ -6081,31 +6300,31 @@ function detectJsFramework(pkg) {
6081
6300
  async function detectJsServiceLanguage(dir, pkg) {
6082
6301
  const deps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
6083
6302
  if (deps["typescript"] !== void 0) return "typescript";
6084
- const entries = await fs12.readdir(dir).catch(() => []);
6303
+ const entries = await fs15.readdir(dir).catch(() => []);
6085
6304
  if (entries.some((name) => /^tsconfig(\..+)?\.json$/.test(name))) return "typescript";
6086
6305
  return "javascript";
6087
6306
  }
6088
6307
  async function discoverNodeService(scanPath, dir) {
6089
- const pkgPath = path14.join(dir, "package.json");
6308
+ const pkgPath = path17.join(dir, "package.json");
6090
6309
  if (!await exists(pkgPath)) return null;
6091
6310
  let pkg;
6092
6311
  try {
6093
6312
  pkg = await readJson(pkgPath);
6094
6313
  } catch (err) {
6095
- recordExtractionError("services", path14.relative(scanPath, pkgPath), err);
6314
+ recordExtractionError("services", path17.relative(scanPath, pkgPath), err);
6096
6315
  return null;
6097
6316
  }
6098
6317
  if (!pkg.name) return null;
6099
6318
  const framework = detectJsFramework(pkg);
6100
6319
  const language = await detectJsServiceLanguage(dir, pkg);
6101
6320
  const node = {
6102
- id: serviceId5(pkg.name),
6103
- type: NodeType9.ServiceNode,
6321
+ id: serviceId8(pkg.name),
6322
+ type: NodeType12.ServiceNode,
6104
6323
  name: pkg.name,
6105
6324
  language,
6106
6325
  version: pkg.version,
6107
6326
  dependencies: pkg.dependencies ?? {},
6108
- repoPath: path14.relative(scanPath, dir),
6327
+ repoPath: path17.relative(scanPath, dir),
6109
6328
  ...pkg.engines?.node ? { nodeEngine: pkg.engines.node } : {},
6110
6329
  ...framework ? { framework } : {}
6111
6330
  };
@@ -6116,18 +6335,18 @@ async function discoverPyService(scanPath, dir) {
6116
6335
  if (!py) return null;
6117
6336
  const pkg = pythonToPackage(py);
6118
6337
  const node = {
6119
- id: serviceId5(py.name),
6120
- type: NodeType9.ServiceNode,
6338
+ id: serviceId8(py.name),
6339
+ type: NodeType12.ServiceNode,
6121
6340
  name: py.name,
6122
6341
  language: "python",
6123
6342
  version: py.version,
6124
6343
  dependencies: py.dependencies,
6125
- repoPath: path14.relative(scanPath, dir)
6344
+ repoPath: path17.relative(scanPath, dir)
6126
6345
  };
6127
6346
  return { pkg, dir, node };
6128
6347
  }
6129
6348
  async function discoverServices(scanPath) {
6130
- const rootPkgPath = path14.join(scanPath, "package.json");
6349
+ const rootPkgPath = path17.join(scanPath, "package.json");
6131
6350
  let rootPkg = null;
6132
6351
  if (await exists(rootPkgPath)) {
6133
6352
  try {
@@ -6135,7 +6354,7 @@ async function discoverServices(scanPath) {
6135
6354
  } catch (err) {
6136
6355
  recordExtractionError(
6137
6356
  "services workspaces",
6138
- path14.relative(scanPath, rootPkgPath),
6357
+ path17.relative(scanPath, rootPkgPath),
6139
6358
  err
6140
6359
  );
6141
6360
  }
@@ -6147,7 +6366,7 @@ async function discoverServices(scanPath) {
6147
6366
  } else {
6148
6367
  if (rootPkg && rootPkg.name) {
6149
6368
  candidateDirs.push(scanPath);
6150
- } else if (await hasPythonManifest(scanPath) || await hasGoManifest(scanPath) || await hasRubyManifest(scanPath) || await hasPhpManifest(scanPath)) {
6369
+ } else if (await hasPythonManifest(scanPath) || await hasGoManifest(scanPath) || await hasRubyManifest(scanPath) || await hasPhpManifest(scanPath) || await hasCsharpManifest(scanPath) || await hasJavaManifest(scanPath)) {
6151
6370
  candidateDirs.push(scanPath);
6152
6371
  }
6153
6372
  const ig = await loadGitignore(scanPath);
@@ -6156,9 +6375,11 @@ async function discoverServices(scanPath) {
6156
6375
  scanPath,
6157
6376
  { maxDepth: parseScanDepth(), ig },
6158
6377
  async (dir) => {
6159
- if (await exists(path14.join(dir, "package.json"))) {
6378
+ if (await exists(path17.join(dir, "package.json"))) {
6160
6379
  candidateDirs.push(dir);
6161
- } else if (await hasPythonManifest(dir) || await hasGoManifest(dir) || await hasRubyManifest(dir) || await hasPhpManifest(dir)) {
6380
+ } else if (await hasPythonManifest(dir) || await hasGoManifest(dir) || await hasRubyManifest(dir) || await hasPhpManifest(dir) || await hasCsharpManifest(dir) || await hasJavaManifest(dir)) {
6381
+ candidateDirs.push(dir);
6382
+ } else if (await hasDockerfileDeclaredService(dir)) {
6162
6383
  candidateDirs.push(dir);
6163
6384
  }
6164
6385
  }
@@ -6166,14 +6387,17 @@ async function discoverServices(scanPath) {
6166
6387
  }
6167
6388
  candidateDirs.sort();
6168
6389
  const seen = /* @__PURE__ */ new Map();
6390
+ const seenDirs = /* @__PURE__ */ new Set();
6169
6391
  const out = [];
6170
6392
  for (const dir of candidateDirs) {
6171
- const service = await discoverNodeService(scanPath, dir) ?? await discoverPyService(scanPath, dir) ?? await discoverGoService(scanPath, dir) ?? await discoverRubyService(scanPath, dir) ?? await discoverPhpService(scanPath, dir);
6393
+ const service = await discoverNodeService(scanPath, dir) ?? await discoverPyService(scanPath, dir) ?? await discoverGoService(scanPath, dir) ?? await discoverRubyService(scanPath, dir) ?? await discoverPhpService(scanPath, dir) ?? await discoverCsharpService(scanPath, dir) ?? await discoverJavaService(scanPath, dir) ?? await discoverDockerfileService(scanPath, dir);
6172
6394
  if (!service) continue;
6395
+ if (seenDirs.has(service.dir)) continue;
6396
+ seenDirs.add(service.dir);
6173
6397
  const existingDir = seen.get(service.node.name);
6174
6398
  if (existingDir !== void 0) {
6175
- const a = path14.relative(scanPath, existingDir) || ".";
6176
- const b = path14.relative(scanPath, dir) || ".";
6399
+ const a = path17.relative(scanPath, existingDir) || ".";
6400
+ const b = path17.relative(scanPath, dir) || ".";
6177
6401
  console.warn(
6178
6402
  `[neat] duplicate package name "${service.node.name}" \u2014 keeping ${a}, ignoring ${b}`
6179
6403
  );
@@ -6209,20 +6433,20 @@ function addServiceNodes(graph, services) {
6209
6433
  }
6210
6434
 
6211
6435
  // src/extract/aliases.ts
6212
- import path15 from "path";
6213
- import { promises as fs13 } from "fs";
6436
+ import path18 from "path";
6437
+ import { promises as fs16 } from "fs";
6214
6438
  import { parseAllDocuments } from "yaml";
6215
- import { NodeType as NodeType10 } from "@neat.is/types";
6439
+ import { NodeType as NodeType13 } from "@neat.is/types";
6216
6440
  var K8S_KINDS_WITH_HOSTNAMES = /* @__PURE__ */ new Set([
6217
6441
  "Service",
6218
6442
  "Deployment",
6219
6443
  "StatefulSet",
6220
6444
  "DaemonSet"
6221
6445
  ]);
6222
- function addAliases(graph, serviceId9, candidates) {
6223
- if (!graph.hasNode(serviceId9)) return;
6224
- const node = graph.getNodeAttributes(serviceId9);
6225
- if (node.type !== NodeType10.ServiceNode) return;
6446
+ function addAliases(graph, serviceId12, candidates) {
6447
+ if (!graph.hasNode(serviceId12)) return;
6448
+ const node = graph.getNodeAttributes(serviceId12);
6449
+ if (node.type !== NodeType13.ServiceNode) return;
6226
6450
  const set = new Set(node.aliases ?? []);
6227
6451
  for (const c of candidates) {
6228
6452
  if (!c) continue;
@@ -6231,20 +6455,20 @@ function addAliases(graph, serviceId9, candidates) {
6231
6455
  }
6232
6456
  if (set.size === 0) return;
6233
6457
  const updated = { ...node, aliases: [...set].sort() };
6234
- graph.replaceNodeAttributes(serviceId9, updated);
6458
+ graph.replaceNodeAttributes(serviceId12, updated);
6235
6459
  }
6236
6460
  function indexServicesByName(services) {
6237
6461
  const map = /* @__PURE__ */ new Map();
6238
6462
  for (const s of services) {
6239
6463
  map.set(s.node.name, s.node.id);
6240
- map.set(path15.basename(s.dir), s.node.id);
6464
+ map.set(path18.basename(s.dir), s.node.id);
6241
6465
  }
6242
6466
  return map;
6243
6467
  }
6244
6468
  async function collectComposeAliases(graph, scanPath, serviceIndex) {
6245
6469
  let composePath = null;
6246
6470
  for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
6247
- const abs = path15.join(scanPath, name);
6471
+ const abs = path18.join(scanPath, name);
6248
6472
  if (await exists(abs)) {
6249
6473
  composePath = abs;
6250
6474
  break;
@@ -6257,19 +6481,19 @@ async function collectComposeAliases(graph, scanPath, serviceIndex) {
6257
6481
  } catch (err) {
6258
6482
  recordExtractionError(
6259
6483
  "aliases compose",
6260
- path15.relative(scanPath, composePath),
6484
+ path18.relative(scanPath, composePath),
6261
6485
  err
6262
6486
  );
6263
6487
  return;
6264
6488
  }
6265
6489
  if (!compose?.services) return;
6266
6490
  for (const [composeName, svc] of Object.entries(compose.services)) {
6267
- const serviceId9 = serviceIndex.get(composeName);
6268
- if (!serviceId9) continue;
6491
+ const serviceId12 = serviceIndex.get(composeName);
6492
+ if (!serviceId12) continue;
6269
6493
  const aliases = /* @__PURE__ */ new Set([composeName]);
6270
6494
  if (svc.container_name) aliases.add(svc.container_name);
6271
6495
  if (svc.hostname) aliases.add(svc.hostname);
6272
- addAliases(graph, serviceId9, aliases);
6496
+ addAliases(graph, serviceId12, aliases);
6273
6497
  }
6274
6498
  }
6275
6499
  var LABEL_KEYS = /* @__PURE__ */ new Set([
@@ -6300,11 +6524,11 @@ function parseDockerfileLabels(content) {
6300
6524
  }
6301
6525
  async function collectDockerfileAliases(graph, services) {
6302
6526
  for (const service of services) {
6303
- const dockerfilePath = path15.join(service.dir, "Dockerfile");
6527
+ const dockerfilePath = path18.join(service.dir, "Dockerfile");
6304
6528
  if (!await exists(dockerfilePath)) continue;
6305
6529
  let content;
6306
6530
  try {
6307
- content = await fs13.readFile(dockerfilePath, "utf8");
6531
+ content = await fs16.readFile(dockerfilePath, "utf8");
6308
6532
  } catch (err) {
6309
6533
  recordExtractionError("aliases dockerfile", dockerfilePath, err);
6310
6534
  continue;
@@ -6316,15 +6540,15 @@ async function collectDockerfileAliases(graph, services) {
6316
6540
  async function walkYamlFiles(start, depth = 0, max = 5) {
6317
6541
  if (depth > max) return [];
6318
6542
  const out = [];
6319
- const entries = await fs13.readdir(start, { withFileTypes: true }).catch(() => []);
6543
+ const entries = await fs16.readdir(start, { withFileTypes: true }).catch(() => []);
6320
6544
  for (const entry of entries) {
6321
6545
  if (entry.isDirectory()) {
6322
6546
  if (IGNORED_DIRS.has(entry.name)) continue;
6323
- const child = path15.join(start, entry.name);
6547
+ const child = path18.join(start, entry.name);
6324
6548
  if (await isPythonVenvDir(child)) continue;
6325
6549
  out.push(...await walkYamlFiles(child, depth + 1, max));
6326
- } else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(path15.extname(entry.name))) {
6327
- out.push(path15.join(start, entry.name));
6550
+ } else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(path18.extname(entry.name))) {
6551
+ out.push(path18.join(start, entry.name));
6328
6552
  }
6329
6553
  }
6330
6554
  return out;
@@ -6351,7 +6575,7 @@ function k8sServiceTarget(doc, byName) {
6351
6575
  async function collectK8sAliases(graph, scanPath, serviceIndex) {
6352
6576
  const files = await walkYamlFiles(scanPath);
6353
6577
  for (const file of files) {
6354
- const content = await fs13.readFile(file, "utf8");
6578
+ const content = await fs16.readFile(file, "utf8");
6355
6579
  let docs;
6356
6580
  try {
6357
6581
  docs = parseAllDocuments(content).map((d) => d.toJSON());
@@ -6375,14 +6599,14 @@ async function addServiceAliases(graph, scanPath, services) {
6375
6599
  }
6376
6600
 
6377
6601
  // src/extract/files.ts
6378
- import path16 from "path";
6602
+ import path19 from "path";
6379
6603
  async function addFiles(graph, services) {
6380
6604
  let nodesAdded = 0;
6381
6605
  let edgesAdded = 0;
6382
6606
  for (const service of services) {
6383
6607
  const filePaths = await walkSourceFiles(service.dir);
6384
6608
  for (const filePath of filePaths) {
6385
- const relPath = toPosix(path16.relative(service.dir, filePath));
6609
+ const relPath = toPosix(path19.relative(service.dir, filePath));
6386
6610
  const { nodesAdded: n, edgesAdded: e } = ensureFileNode(
6387
6611
  graph,
6388
6612
  service.pkg.name,
@@ -6397,13 +6621,19 @@ async function addFiles(graph, services) {
6397
6621
  }
6398
6622
 
6399
6623
  // src/extract/symbols.ts
6400
- import path17 from "path";
6624
+ import path20 from "path";
6401
6625
  import Parser3 from "tree-sitter";
6402
6626
  import JavaScript3 from "tree-sitter-javascript";
6403
6627
  import TypeScript from "tree-sitter-typescript";
6628
+ import Python3 from "tree-sitter-python";
6629
+ import Go3 from "tree-sitter-go";
6630
+ import Ruby2 from "tree-sitter-ruby";
6631
+ import Php2 from "tree-sitter-php";
6632
+ import CSharp from "tree-sitter-c-sharp";
6633
+ import Java from "tree-sitter-java";
6404
6634
  import {
6405
6635
  EdgeType as EdgeType7,
6406
- NodeType as NodeType11,
6636
+ NodeType as NodeType14,
6407
6637
  Provenance as Provenance7,
6408
6638
  confidenceForExtracted as confidenceForExtracted4,
6409
6639
  extractedEdgeId as extractedEdgeId6,
@@ -6418,6 +6648,15 @@ var GRAMMAR_BY_EXT = {
6418
6648
  ".mjs": JavaScript3,
6419
6649
  ".cjs": JavaScript3
6420
6650
  };
6651
+ var SYMBOL_GRAMMAR_BY_EXT = {
6652
+ ...GRAMMAR_BY_EXT,
6653
+ ".py": Python3,
6654
+ ".go": Go3,
6655
+ ".rb": Ruby2,
6656
+ ".php": Php2.php_only,
6657
+ ".cs": CSharp,
6658
+ ".java": Java
6659
+ };
6421
6660
  function parseSource3(parser, source) {
6422
6661
  return parser.parse(
6423
6662
  (index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK3)
@@ -6486,6 +6725,367 @@ function collectSymbolDefs(root) {
6486
6725
  visit(root, void 0);
6487
6726
  return out;
6488
6727
  }
6728
+ function collectPythonSymbolDefs(root) {
6729
+ const out = [];
6730
+ const push = (kind, qualname, node) => {
6731
+ out.push({
6732
+ kind,
6733
+ qualname,
6734
+ startLine: node.startPosition.row + 1,
6735
+ endLine: node.endPosition.row + 1
6736
+ });
6737
+ };
6738
+ const visit = (node, classCtx) => {
6739
+ switch (node.type) {
6740
+ case "function_definition": {
6741
+ const name = node.childForFieldName("name")?.text;
6742
+ if (name) {
6743
+ if (classCtx) {
6744
+ const kind = name === "__init__" ? "constructor" : "method";
6745
+ push(kind, `${classCtx}.${name}`, node);
6746
+ } else {
6747
+ push("function", name, node);
6748
+ }
6749
+ }
6750
+ const body = node.childForFieldName("body");
6751
+ if (body) {
6752
+ for (let i = 0; i < body.namedChildCount; i++) {
6753
+ const child = body.namedChild(i);
6754
+ if (child) visit(child, void 0);
6755
+ }
6756
+ }
6757
+ return;
6758
+ }
6759
+ case "class_definition": {
6760
+ const name = node.childForFieldName("name")?.text;
6761
+ if (name) push("class", name, node);
6762
+ const body = node.childForFieldName("body");
6763
+ if (body) {
6764
+ for (let i = 0; i < body.namedChildCount; i++) {
6765
+ const child = body.namedChild(i);
6766
+ if (child) visit(child, name ?? classCtx);
6767
+ }
6768
+ }
6769
+ return;
6770
+ }
6771
+ case "decorated_definition": {
6772
+ const def = node.childForFieldName("definition");
6773
+ if (def) visit(def, classCtx);
6774
+ return;
6775
+ }
6776
+ }
6777
+ for (let i = 0; i < node.namedChildCount; i++) {
6778
+ const child = node.namedChild(i);
6779
+ if (child) visit(child, classCtx);
6780
+ }
6781
+ };
6782
+ visit(root, void 0);
6783
+ return out;
6784
+ }
6785
+ function goReceiverType(node) {
6786
+ const receiver = node.childForFieldName("receiver");
6787
+ if (!receiver) return void 0;
6788
+ for (let i = 0; i < receiver.namedChildCount; i++) {
6789
+ const param = receiver.namedChild(i);
6790
+ if (param?.type !== "parameter_declaration") continue;
6791
+ let typeNode = param.childForFieldName("type");
6792
+ if (typeNode?.type === "pointer_type") typeNode = typeNode.namedChild(0) ?? typeNode;
6793
+ if (typeNode?.type === "generic_type") {
6794
+ typeNode = typeNode.childForFieldName("type") ?? typeNode.namedChild(0) ?? typeNode;
6795
+ }
6796
+ if (typeNode?.type === "type_identifier") return typeNode.text;
6797
+ return typeNode?.text;
6798
+ }
6799
+ return void 0;
6800
+ }
6801
+ function collectGoSymbolDefs(root) {
6802
+ const out = [];
6803
+ const push = (kind, qualname, node) => {
6804
+ out.push({
6805
+ kind,
6806
+ qualname,
6807
+ startLine: node.startPosition.row + 1,
6808
+ endLine: node.endPosition.row + 1
6809
+ });
6810
+ };
6811
+ const visit = (node) => {
6812
+ switch (node.type) {
6813
+ case "function_declaration": {
6814
+ const name = node.childForFieldName("name")?.text;
6815
+ if (name) push("function", name, node);
6816
+ break;
6817
+ }
6818
+ case "method_declaration": {
6819
+ const name = node.childForFieldName("name")?.text;
6820
+ if (name) {
6821
+ const recv = goReceiverType(node);
6822
+ push("method", recv ? `${recv}.${name}` : name, node);
6823
+ }
6824
+ break;
6825
+ }
6826
+ }
6827
+ for (let i = 0; i < node.namedChildCount; i++) {
6828
+ const child = node.namedChild(i);
6829
+ if (child) visit(child);
6830
+ }
6831
+ };
6832
+ visit(root);
6833
+ return out;
6834
+ }
6835
+ function collectRubySymbolDefs(root) {
6836
+ const out = [];
6837
+ const push = (kind, qualname, node) => {
6838
+ out.push({
6839
+ kind,
6840
+ qualname,
6841
+ startLine: node.startPosition.row + 1,
6842
+ endLine: node.endPosition.row + 1
6843
+ });
6844
+ };
6845
+ const nameText = (node) => node.childForFieldName("name")?.text.replace(/::/g, ".");
6846
+ const walkBody = (node, ctx) => {
6847
+ const body = node.childForFieldName("body");
6848
+ if (!body) return;
6849
+ for (let i = 0; i < body.namedChildCount; i++) {
6850
+ const child = body.namedChild(i);
6851
+ if (child) visit(child, ctx);
6852
+ }
6853
+ };
6854
+ const visit = (node, classCtx) => {
6855
+ switch (node.type) {
6856
+ case "method":
6857
+ case "singleton_method": {
6858
+ const name = node.childForFieldName("name")?.text;
6859
+ if (name) {
6860
+ if (classCtx) {
6861
+ const kind = node.type === "method" && name === "initialize" ? "constructor" : "method";
6862
+ push(kind, `${classCtx}.${name}`, node);
6863
+ } else {
6864
+ push("function", name, node);
6865
+ }
6866
+ }
6867
+ walkBody(node, void 0);
6868
+ return;
6869
+ }
6870
+ case "class":
6871
+ case "module": {
6872
+ const name = nameText(node);
6873
+ if (name) push("class", classCtx ? `${classCtx}.${name}` : name, node);
6874
+ walkBody(node, name ? classCtx ? `${classCtx}.${name}` : name : classCtx);
6875
+ return;
6876
+ }
6877
+ }
6878
+ for (let i = 0; i < node.namedChildCount; i++) {
6879
+ const child = node.namedChild(i);
6880
+ if (child) visit(child, classCtx);
6881
+ }
6882
+ };
6883
+ visit(root, void 0);
6884
+ return out;
6885
+ }
6886
+ function collectPhpSymbolDefs(root) {
6887
+ const out = [];
6888
+ const push = (kind, qualname, node) => {
6889
+ out.push({
6890
+ kind,
6891
+ qualname,
6892
+ startLine: node.startPosition.row + 1,
6893
+ endLine: node.endPosition.row + 1
6894
+ });
6895
+ };
6896
+ const dot = (s) => s.replace(/\\/g, ".").replace(/::/g, ".").replace(/^\.+/, "");
6897
+ const join = (prefix, name) => prefix ? `${prefix}.${name}` : name;
6898
+ const walkChildren = (container, nsCtx, classCtx) => {
6899
+ let ns = nsCtx;
6900
+ for (let i = 0; i < container.namedChildCount; i++) {
6901
+ const child = container.namedChild(i);
6902
+ if (!child) continue;
6903
+ if (child.type === "namespace_definition" && !child.childForFieldName("body")) {
6904
+ const nameNode = child.childForFieldName("name");
6905
+ if (nameNode) ns = dot(nameNode.text);
6906
+ continue;
6907
+ }
6908
+ visit(child, ns, classCtx);
6909
+ }
6910
+ };
6911
+ const walkBody = (node, nsCtx, classCtx) => {
6912
+ const body = node.childForFieldName("body");
6913
+ if (body) walkChildren(body, nsCtx, classCtx);
6914
+ };
6915
+ const visit = (node, nsCtx, classCtx) => {
6916
+ switch (node.type) {
6917
+ case "function_definition": {
6918
+ const name = node.childForFieldName("name")?.text;
6919
+ if (name) push("function", join(nsCtx, name), node);
6920
+ walkBody(node, nsCtx, void 0);
6921
+ return;
6922
+ }
6923
+ case "class_declaration":
6924
+ case "trait_declaration":
6925
+ case "interface_declaration": {
6926
+ const name = node.childForFieldName("name")?.text;
6927
+ const full = name ? join(nsCtx, name) : classCtx;
6928
+ if (name) push("class", full, node);
6929
+ walkBody(node, nsCtx, full);
6930
+ return;
6931
+ }
6932
+ case "method_declaration": {
6933
+ const name = node.childForFieldName("name")?.text;
6934
+ if (name) {
6935
+ const kind = name === "__construct" ? "constructor" : "method";
6936
+ push(kind, join(classCtx ?? nsCtx, name), node);
6937
+ }
6938
+ return;
6939
+ }
6940
+ case "namespace_definition": {
6941
+ const nameNode = node.childForFieldName("name");
6942
+ const ns = nameNode ? dot(nameNode.text) : nsCtx;
6943
+ walkBody(node, ns, classCtx);
6944
+ return;
6945
+ }
6946
+ }
6947
+ walkChildren(node, nsCtx, classCtx);
6948
+ };
6949
+ walkChildren(root, void 0, void 0);
6950
+ return out;
6951
+ }
6952
+ function collectCsharpSymbolDefs(root) {
6953
+ const out = [];
6954
+ const push = (kind, qualname, node) => {
6955
+ out.push({
6956
+ kind,
6957
+ qualname,
6958
+ startLine: node.startPosition.row + 1,
6959
+ endLine: node.endPosition.row + 1
6960
+ });
6961
+ };
6962
+ const join = (prefix, name) => prefix ? `${prefix}.${name}` : name;
6963
+ const walkChildren = (container, nsCtx, classCtx) => {
6964
+ let ns = nsCtx;
6965
+ for (let i = 0; i < container.namedChildCount; i++) {
6966
+ const child = container.namedChild(i);
6967
+ if (!child) continue;
6968
+ if (child.type === "file_scoped_namespace_declaration") {
6969
+ const nameNode = child.childForFieldName("name");
6970
+ if (nameNode) ns = join(nsCtx, nameNode.text);
6971
+ continue;
6972
+ }
6973
+ visit(child, ns, classCtx);
6974
+ }
6975
+ };
6976
+ const walkBody = (node, nsCtx, classCtx) => {
6977
+ const body = node.childForFieldName("body");
6978
+ if (body) walkChildren(body, nsCtx, classCtx);
6979
+ };
6980
+ const visit = (node, nsCtx, classCtx) => {
6981
+ switch (node.type) {
6982
+ case "namespace_declaration": {
6983
+ const nameNode = node.childForFieldName("name");
6984
+ const ns = nameNode ? join(nsCtx, nameNode.text) : nsCtx;
6985
+ walkBody(node, ns, classCtx);
6986
+ return;
6987
+ }
6988
+ case "class_declaration":
6989
+ case "interface_declaration":
6990
+ case "struct_declaration":
6991
+ case "record_declaration": {
6992
+ const name = node.childForFieldName("name")?.text;
6993
+ const full = name ? join(classCtx ?? nsCtx, name) : classCtx;
6994
+ if (name) push("class", full, node);
6995
+ walkBody(node, nsCtx, full);
6996
+ return;
6997
+ }
6998
+ case "method_declaration": {
6999
+ const name = node.childForFieldName("name")?.text;
7000
+ if (name) push("method", join(classCtx ?? nsCtx, name), node);
7001
+ return;
7002
+ }
7003
+ case "constructor_declaration": {
7004
+ const name = node.childForFieldName("name")?.text;
7005
+ if (name) push("constructor", join(classCtx ?? nsCtx, name), node);
7006
+ return;
7007
+ }
7008
+ }
7009
+ walkChildren(node, nsCtx, classCtx);
7010
+ };
7011
+ walkChildren(root, void 0, void 0);
7012
+ return out;
7013
+ }
7014
+ function collectJavaSymbolDefs(root) {
7015
+ const out = [];
7016
+ const push = (kind, qualname, node) => {
7017
+ out.push({
7018
+ kind,
7019
+ qualname,
7020
+ startLine: node.startPosition.row + 1,
7021
+ endLine: node.endPosition.row + 1
7022
+ });
7023
+ };
7024
+ const join = (prefix, name) => prefix ? `${prefix}.${name}` : name;
7025
+ let pkg;
7026
+ for (let i = 0; i < root.namedChildCount; i++) {
7027
+ const child = root.namedChild(i);
7028
+ if (child?.type === "package_declaration") {
7029
+ pkg = child.namedChild(0)?.text;
7030
+ break;
7031
+ }
7032
+ }
7033
+ const walkChildren = (container, classCtx) => {
7034
+ for (let i = 0; i < container.namedChildCount; i++) {
7035
+ const child = container.namedChild(i);
7036
+ if (child) visit(child, classCtx);
7037
+ }
7038
+ };
7039
+ const walkBody = (node, classCtx) => {
7040
+ const body = node.childForFieldName("body");
7041
+ if (body) walkChildren(body, classCtx);
7042
+ };
7043
+ const visit = (node, classCtx) => {
7044
+ switch (node.type) {
7045
+ case "class_declaration":
7046
+ case "interface_declaration":
7047
+ case "enum_declaration":
7048
+ case "record_declaration": {
7049
+ const name = node.childForFieldName("name")?.text;
7050
+ const full = name ? join(classCtx ?? pkg, name) : classCtx;
7051
+ if (name) push("class", full, node);
7052
+ walkBody(node, full);
7053
+ return;
7054
+ }
7055
+ case "method_declaration": {
7056
+ const name = node.childForFieldName("name")?.text;
7057
+ if (name) push("method", join(classCtx ?? pkg, name), node);
7058
+ return;
7059
+ }
7060
+ case "constructor_declaration": {
7061
+ const name = node.childForFieldName("name")?.text;
7062
+ if (name) push("constructor", join(classCtx ?? pkg, name), node);
7063
+ return;
7064
+ }
7065
+ }
7066
+ walkChildren(node, classCtx);
7067
+ };
7068
+ walkChildren(root, void 0);
7069
+ return out;
7070
+ }
7071
+ function collectSymbolDefsForExt(ext, root) {
7072
+ switch (ext) {
7073
+ case ".py":
7074
+ return collectPythonSymbolDefs(root);
7075
+ case ".go":
7076
+ return collectGoSymbolDefs(root);
7077
+ case ".rb":
7078
+ return collectRubySymbolDefs(root);
7079
+ case ".php":
7080
+ return collectPhpSymbolDefs(root);
7081
+ case ".cs":
7082
+ return collectCsharpSymbolDefs(root);
7083
+ case ".java":
7084
+ return collectJavaSymbolDefs(root);
7085
+ default:
7086
+ return collectSymbolDefs(root);
7087
+ }
7088
+ }
6489
7089
  function disambiguate(defs) {
6490
7090
  const counts = /* @__PURE__ */ new Map();
6491
7091
  for (const def of defs) counts.set(def.qualname, (counts.get(def.qualname) ?? 0) + 1);
@@ -6500,7 +7100,7 @@ function disambiguate(defs) {
6500
7100
  async function addSymbols(graph, services) {
6501
7101
  const parsers = /* @__PURE__ */ new Map();
6502
7102
  const parserForExt5 = (ext) => {
6503
- const grammar = GRAMMAR_BY_EXT[ext];
7103
+ const grammar = SYMBOL_GRAMMAR_BY_EXT[ext];
6504
7104
  if (!grammar) return null;
6505
7105
  let parser = parsers.get(ext);
6506
7106
  if (!parser) {
@@ -6515,13 +7115,14 @@ async function addSymbols(graph, services) {
6515
7115
  for (const service of services) {
6516
7116
  const files = await loadSourceFiles(service.dir);
6517
7117
  for (const file of files) {
6518
- const parser = parserForExt5(path17.extname(file.path));
7118
+ const ext = path20.extname(file.path);
7119
+ const parser = parserForExt5(ext);
6519
7120
  if (!parser) continue;
6520
- const relPath = toPosix(path17.relative(service.dir, file.path));
7121
+ const relPath = toPosix(path20.relative(service.dir, file.path));
6521
7122
  let defs;
6522
7123
  try {
6523
7124
  const tree = parseSource3(parser, file.content);
6524
- defs = collectSymbolDefs(tree.rootNode);
7125
+ defs = collectSymbolDefsForExt(ext, tree.rootNode);
6525
7126
  } catch (err) {
6526
7127
  recordExtractionError("symbol extraction", file.path, err);
6527
7128
  continue;
@@ -6540,7 +7141,7 @@ async function addSymbols(graph, services) {
6540
7141
  if (!graph.hasNode(sid)) {
6541
7142
  const node = {
6542
7143
  id: sid,
6543
- type: NodeType11.SymbolNode,
7144
+ type: NodeType14.SymbolNode,
6544
7145
  kind: def.kind,
6545
7146
  qualname: def.qualname,
6546
7147
  span: { startLine: def.startLine, endLine: def.endLine },
@@ -6576,11 +7177,11 @@ async function addSymbols(graph, services) {
6576
7177
  }
6577
7178
 
6578
7179
  // src/extract/symbol-edges.ts
6579
- import path18 from "path";
7180
+ import path21 from "path";
6580
7181
  import Parser4 from "tree-sitter";
6581
7182
  import {
6582
7183
  EdgeType as EdgeType8,
6583
- NodeType as NodeType12,
7184
+ NodeType as NodeType15,
6584
7185
  Provenance as Provenance8,
6585
7186
  confidenceForExtracted as confidenceForExtracted5,
6586
7187
  extractedEdgeId as extractedEdgeId7,
@@ -6690,10 +7291,10 @@ async function addSymbolEdges(graph, services) {
6690
7291
  const tsPaths = await loadTsPathConfig(service.dir);
6691
7292
  const files = await loadSourceFiles(service.dir);
6692
7293
  for (const file of files) {
6693
- const parser = parserForExt5(path18.extname(file.path));
7294
+ const parser = parserForExt5(path21.extname(file.path));
6694
7295
  if (!parser) continue;
6695
- const relPath = toPosix(path18.relative(service.dir, file.path));
6696
- const fileDir = path18.dirname(file.path);
7296
+ const relPath = toPosix(path21.relative(service.dir, file.path));
7297
+ const fileDir = path21.dirname(file.path);
6697
7298
  let root;
6698
7299
  try {
6699
7300
  root = parseSource3(parser, file.content).rootNode;
@@ -6735,7 +7336,7 @@ async function addSymbolEdges(graph, services) {
6735
7336
  const candidate = symbolId3(serviceName, imp.targetRelPath, imp.importedName);
6736
7337
  if (graph.hasNode(candidate)) {
6737
7338
  const node = graph.getNodeAttributes(candidate);
6738
- if (node.type === NodeType12.SymbolNode && node.kind === wantKind) return candidate;
7339
+ if (node.type === NodeType15.SymbolNode && node.kind === wantKind) return candidate;
6739
7340
  }
6740
7341
  }
6741
7342
  return null;
@@ -6827,11 +7428,11 @@ async function addSymbolEdges(graph, services) {
6827
7428
  }
6828
7429
 
6829
7430
  // src/extract/actions.ts
6830
- import path19 from "path";
7431
+ import path22 from "path";
6831
7432
  import Parser5 from "tree-sitter";
6832
7433
  import {
6833
7434
  EdgeType as EdgeType9,
6834
- NodeType as NodeType13,
7435
+ NodeType as NodeType16,
6835
7436
  Provenance as Provenance9,
6836
7437
  confidenceForExtracted as confidenceForExtracted6,
6837
7438
  extractedEdgeId as extractedEdgeId8,
@@ -6981,9 +7582,9 @@ async function addServerActions(graph, services) {
6981
7582
  const files = await loadSourceFiles(service.dir);
6982
7583
  for (const file of files) {
6983
7584
  if (isTestPath(file.path)) continue;
6984
- const parser = parserForExt5(path19.extname(file.path));
7585
+ const parser = parserForExt5(path22.extname(file.path));
6985
7586
  if (!parser) continue;
6986
- const relPath = toPosix(path19.relative(service.dir, file.path));
7587
+ const relPath = toPosix(path22.relative(service.dir, file.path));
6987
7588
  let root;
6988
7589
  try {
6989
7590
  root = parseSource3(parser, file.content).rootNode;
@@ -7010,7 +7611,7 @@ async function addServerActions(graph, services) {
7010
7611
  if (!graph.hasNode(aid)) {
7011
7612
  const node = {
7012
7613
  id: aid,
7013
- type: NodeType13.ServerActionNode,
7614
+ type: NodeType16.ServerActionNode,
7014
7615
  name: action.exportName,
7015
7616
  service: service.pkg.name,
7016
7617
  module: relPath,
@@ -7044,10 +7645,10 @@ async function addServerActions(graph, services) {
7044
7645
  }
7045
7646
  for (const file of files) {
7046
7647
  if (isTestPath(file.path)) continue;
7047
- const parser = parserForExt5(path19.extname(file.path));
7648
+ const parser = parserForExt5(path22.extname(file.path));
7048
7649
  if (!parser) continue;
7049
- const relPath = toPosix(path19.relative(service.dir, file.path));
7050
- const fileDir = path19.dirname(file.path);
7650
+ const relPath = toPosix(path22.relative(service.dir, file.path));
7651
+ const fileDir = path22.dirname(file.path);
7051
7652
  let root;
7052
7653
  try {
7053
7654
  root = parseSource3(parser, file.content).rootNode;
@@ -7099,10 +7700,10 @@ async function addServerActions(graph, services) {
7099
7700
  }
7100
7701
 
7101
7702
  // src/extract/databases/index.ts
7102
- import path27 from "path";
7703
+ import path30 from "path";
7103
7704
  import {
7104
7705
  EdgeType as EdgeType10,
7105
- NodeType as NodeType14,
7706
+ NodeType as NodeType17,
7106
7707
  Provenance as Provenance10,
7107
7708
  configId,
7108
7709
  databaseId as databaseId2,
@@ -7110,9 +7711,9 @@ import {
7110
7711
  } from "@neat.is/types";
7111
7712
 
7112
7713
  // src/extract/databases/db-config-yaml.ts
7113
- import path20 from "path";
7714
+ import path23 from "path";
7114
7715
  async function parse(serviceDir) {
7115
- const yamlPath = path20.join(serviceDir, "db-config.yaml");
7716
+ const yamlPath = path23.join(serviceDir, "db-config.yaml");
7116
7717
  if (!await exists(yamlPath)) return [];
7117
7718
  const raw = await readYaml(yamlPath);
7118
7719
  return [
@@ -7129,12 +7730,12 @@ async function parse(serviceDir) {
7129
7730
  var dbConfigYamlParser = { name: "db-config.yaml", parse };
7130
7731
 
7131
7732
  // src/extract/databases/dotenv.ts
7132
- import { promises as fs15 } from "fs";
7133
- import path22 from "path";
7733
+ import { promises as fs18 } from "fs";
7734
+ import path25 from "path";
7134
7735
 
7135
7736
  // src/extract/databases/shared.ts
7136
- import { promises as fs14 } from "fs";
7137
- import path21 from "path";
7737
+ import { promises as fs17 } from "fs";
7738
+ import path24 from "path";
7138
7739
  function schemeToEngine(scheme) {
7139
7740
  const s = scheme.toLowerCase().split("+")[0];
7140
7741
  switch (s) {
@@ -7173,18 +7774,18 @@ function parseConnectionString(url) {
7173
7774
  }
7174
7775
  async function readIfExists(filePath) {
7175
7776
  try {
7176
- return await fs14.readFile(filePath, "utf8");
7777
+ return await fs17.readFile(filePath, "utf8");
7177
7778
  } catch {
7178
7779
  return null;
7179
7780
  }
7180
7781
  }
7181
7782
  async function resolveEnvVar(serviceDir, name) {
7182
- const entries = await fs14.readdir(serviceDir, { withFileTypes: true }).catch(() => []);
7783
+ const entries = await fs17.readdir(serviceDir, { withFileTypes: true }).catch(() => []);
7183
7784
  const envNames = entries.filter((e) => e.isFile() && (e.name === ".env" || e.name.startsWith(".env."))).map((e) => e.name);
7184
7785
  const rank = (n) => n === ".env.local" ? 0 : n === ".env" ? 1 : 2;
7185
7786
  envNames.sort((a, b) => rank(a) - rank(b) || a.localeCompare(b));
7186
7787
  for (const fileName of envNames) {
7187
- const content = await readIfExists(path21.join(serviceDir, fileName));
7788
+ const content = await readIfExists(path24.join(serviceDir, fileName));
7188
7789
  if (!content) continue;
7189
7790
  for (const line of content.split("\n")) {
7190
7791
  const trimmed = line.trim();
@@ -7203,7 +7804,7 @@ async function resolveEnvVar(serviceDir, name) {
7203
7804
  }
7204
7805
  async function findFirst(serviceDir, candidates) {
7205
7806
  for (const rel of candidates) {
7206
- const abs = path21.join(serviceDir, rel);
7807
+ const abs = path24.join(serviceDir, rel);
7207
7808
  const content = await readIfExists(abs);
7208
7809
  if (content !== null) return abs;
7209
7810
  }
@@ -7261,15 +7862,15 @@ function parseDotenvLine(line) {
7261
7862
  return { key, value };
7262
7863
  }
7263
7864
  async function parse2(serviceDir) {
7264
- const entries = await fs15.readdir(serviceDir, { withFileTypes: true }).catch(() => []);
7865
+ const entries = await fs18.readdir(serviceDir, { withFileTypes: true }).catch(() => []);
7265
7866
  const configs = [];
7266
7867
  const seen = /* @__PURE__ */ new Set();
7267
7868
  for (const entry of entries) {
7268
7869
  if (!entry.isFile()) continue;
7269
7870
  const match = isConfigFile(entry.name);
7270
7871
  if (!match.match || match.fileType !== "env") continue;
7271
- const filePath = path22.join(serviceDir, entry.name);
7272
- const content = await fs15.readFile(filePath, "utf8");
7872
+ const filePath = path25.join(serviceDir, entry.name);
7873
+ const content = await fs18.readFile(filePath, "utf8");
7273
7874
  for (const line of content.split("\n")) {
7274
7875
  const parsed = parseDotenvLine(line);
7275
7876
  if (!parsed) continue;
@@ -7287,9 +7888,9 @@ async function parse2(serviceDir) {
7287
7888
  var dotenvParser = { name: ".env", parse: parse2 };
7288
7889
 
7289
7890
  // src/extract/databases/prisma.ts
7290
- import path23 from "path";
7891
+ import path26 from "path";
7291
7892
  async function parse3(serviceDir) {
7292
- const schemaPath = path23.join(serviceDir, "prisma", "schema.prisma");
7893
+ const schemaPath = path26.join(serviceDir, "prisma", "schema.prisma");
7293
7894
  const content = await readIfExists(schemaPath);
7294
7895
  if (!content) return [];
7295
7896
  const block = content.match(/datasource\s+\w+\s*\{([^}]*)\}/s);
@@ -7444,10 +8045,10 @@ async function parse5(serviceDir) {
7444
8045
  var knexParser = { name: "knex", parse: parse5 };
7445
8046
 
7446
8047
  // src/extract/databases/ormconfig.ts
7447
- import path24 from "path";
8048
+ import path27 from "path";
7448
8049
  async function parse6(serviceDir) {
7449
8050
  for (const candidate of ["ormconfig.json", "ormconfig.yaml", "ormconfig.yml"]) {
7450
- const abs = path24.join(serviceDir, candidate);
8051
+ const abs = path27.join(serviceDir, candidate);
7451
8052
  if (!await exists(abs)) continue;
7452
8053
  const raw = candidate.endsWith(".json") ? await readJson(abs) : await readYaml(abs);
7453
8054
  const entries = Array.isArray(raw) ? raw : [raw];
@@ -7505,9 +8106,9 @@ async function parse7(serviceDir) {
7505
8106
  var typeormParser = { name: "typeorm", parse: parse7 };
7506
8107
 
7507
8108
  // src/extract/databases/sequelize.ts
7508
- import path25 from "path";
8109
+ import path28 from "path";
7509
8110
  async function parse8(serviceDir) {
7510
- const configPath = path25.join(serviceDir, "config", "config.json");
8111
+ const configPath = path28.join(serviceDir, "config", "config.json");
7511
8112
  if (!await exists(configPath)) return [];
7512
8113
  const raw = await readJson(configPath);
7513
8114
  const out = [];
@@ -7533,7 +8134,7 @@ async function parse8(serviceDir) {
7533
8134
  var sequelizeParser = { name: "sequelize", parse: parse8 };
7534
8135
 
7535
8136
  // src/extract/databases/docker-compose.ts
7536
- import path26 from "path";
8137
+ import path29 from "path";
7537
8138
  function portFromService(svc) {
7538
8139
  for (const raw of svc.ports ?? []) {
7539
8140
  const str = String(raw);
@@ -7560,7 +8161,7 @@ function databaseFromEnv(svc) {
7560
8161
  }
7561
8162
  async function parse9(serviceDir) {
7562
8163
  for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
7563
- const abs = path26.join(serviceDir, name);
8164
+ const abs = path29.join(serviceDir, name);
7564
8165
  if (!await exists(abs)) continue;
7565
8166
  const raw = await readYaml(abs);
7566
8167
  if (!raw?.services) return [];
@@ -7602,7 +8203,7 @@ function compatibleDriversFor(engine) {
7602
8203
  function toDatabaseNode(config) {
7603
8204
  return {
7604
8205
  id: databaseId2(config.host),
7605
- type: NodeType14.DatabaseNode,
8206
+ type: NodeType17.DatabaseNode,
7606
8207
  name: config.database || config.host,
7607
8208
  engine: config.engine,
7608
8209
  engineVersion: config.engineVersion,
@@ -7732,7 +8333,7 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
7732
8333
  discoveredVia: mergedDiscoveredVia
7733
8334
  });
7734
8335
  }
7735
- const relConfigFile = toPosix(path27.relative(service.dir, config.sourceFile));
8336
+ const relConfigFile = toPosix(path30.relative(service.dir, config.sourceFile));
7736
8337
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
7737
8338
  graph,
7738
8339
  service.pkg.name,
@@ -7741,7 +8342,7 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
7741
8342
  );
7742
8343
  nodesAdded += fn;
7743
8344
  edgesAdded += fe;
7744
- const evidenceFile = toPosix(path27.relative(scanPath, config.sourceFile));
8345
+ const evidenceFile = toPosix(path30.relative(scanPath, config.sourceFile));
7745
8346
  const edge = {
7746
8347
  id: extractedEdgeId(fileNodeId, dbNode.id, EdgeType10.CONNECTS_TO),
7747
8348
  source: fileNodeId,
@@ -7759,15 +8360,15 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
7759
8360
  if (allConfigs.length === 1) {
7760
8361
  const primary = allConfigs[0];
7761
8362
  service.node.dbConnectionTarget = primary.port ? `${primary.host}:${primary.port}` : primary.host;
7762
- const relPath = path27.relative(scanPath, primary.sourceFile);
8363
+ const relPath = path30.relative(scanPath, primary.sourceFile);
7763
8364
  const cfgId = configId(relPath);
7764
8365
  if (!graph.hasNode(cfgId)) {
7765
8366
  const cfgNode = {
7766
8367
  id: cfgId,
7767
- type: NodeType14.ConfigNode,
7768
- name: path27.basename(primary.sourceFile),
8368
+ type: NodeType17.ConfigNode,
8369
+ name: path30.basename(primary.sourceFile),
7769
8370
  path: relPath,
7770
- fileType: isConfigFile(path27.basename(primary.sourceFile)).fileType || "config"
8371
+ fileType: isConfigFile(path30.basename(primary.sourceFile)).fileType || "config"
7771
8372
  };
7772
8373
  graph.addNode(cfgId, cfgNode);
7773
8374
  nodesAdded++;
@@ -7807,11 +8408,11 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
7807
8408
  }
7808
8409
 
7809
8410
  // src/extract/configs.ts
7810
- import { promises as fs16 } from "fs";
7811
- import path28 from "path";
8411
+ import { promises as fs19 } from "fs";
8412
+ import path31 from "path";
7812
8413
  import {
7813
8414
  EdgeType as EdgeType11,
7814
- NodeType as NodeType15,
8415
+ NodeType as NodeType18,
7815
8416
  Provenance as Provenance11,
7816
8417
  configId as configId2,
7817
8418
  confidenceForExtracted as confidenceForExtracted8
@@ -7819,9 +8420,9 @@ import {
7819
8420
  async function walkConfigFiles(dir) {
7820
8421
  const out = [];
7821
8422
  async function walk9(current) {
7822
- const entries = await fs16.readdir(current, { withFileTypes: true });
8423
+ const entries = await fs19.readdir(current, { withFileTypes: true });
7823
8424
  for (const entry of entries) {
7824
- const full = path28.join(current, entry.name);
8425
+ const full = path31.join(current, entry.name);
7825
8426
  if (entry.isDirectory()) {
7826
8427
  if (IGNORED_DIRS.has(entry.name)) continue;
7827
8428
  if (await isPythonVenvDir(full)) continue;
@@ -7840,19 +8441,19 @@ async function addConfigNodes(graph, services, scanPath) {
7840
8441
  for (const service of services) {
7841
8442
  const configFiles = await walkConfigFiles(service.dir);
7842
8443
  for (const file of configFiles) {
7843
- const relPath = path28.relative(scanPath, file);
8444
+ const relPath = path31.relative(scanPath, file);
7844
8445
  const node = {
7845
8446
  id: configId2(relPath),
7846
- type: NodeType15.ConfigNode,
7847
- name: path28.basename(file),
8447
+ type: NodeType18.ConfigNode,
8448
+ name: path31.basename(file),
7848
8449
  path: relPath,
7849
- fileType: isConfigFile(path28.basename(file)).fileType
8450
+ fileType: isConfigFile(path31.basename(file)).fileType
7850
8451
  };
7851
8452
  if (!graph.hasNode(node.id)) {
7852
8453
  graph.addNode(node.id, node);
7853
8454
  nodesAdded++;
7854
8455
  }
7855
- const relToService = toPosix(path28.relative(service.dir, file));
8456
+ const relToService = toPosix(path31.relative(service.dir, file));
7856
8457
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
7857
8458
  graph,
7858
8459
  service.pkg.name,
@@ -7868,7 +8469,7 @@ async function addConfigNodes(graph, services, scanPath) {
7868
8469
  type: EdgeType11.CONFIGURED_BY,
7869
8470
  provenance: Provenance11.EXTRACTED,
7870
8471
  confidence: confidenceForExtracted8("structural"),
7871
- evidence: { file: relPath.split(path28.sep).join("/") }
8472
+ evidence: { file: relPath.split(path31.sep).join("/") }
7872
8473
  };
7873
8474
  if (!graph.hasEdge(edge.id)) {
7874
8475
  graph.addEdgeWithKey(edge.id, edge.source, edge.target, edge);
@@ -7880,11 +8481,11 @@ async function addConfigNodes(graph, services, scanPath) {
7880
8481
  }
7881
8482
 
7882
8483
  // src/extract/proto.ts
7883
- import { promises as fs17 } from "fs";
7884
- import path29 from "path";
8484
+ import { promises as fs20 } from "fs";
8485
+ import path32 from "path";
7885
8486
  import {
7886
8487
  EdgeType as EdgeType12,
7887
- NodeType as NodeType16,
8488
+ NodeType as NodeType19,
7888
8489
  Provenance as Provenance12,
7889
8490
  confidenceForExtracted as confidenceForExtracted9,
7890
8491
  extractedEdgeId as extractedEdgeId9,
@@ -7928,14 +8529,14 @@ function grpcMethodsFromProto(content, fqPackage) {
7928
8529
  async function walkProtoFiles(dir) {
7929
8530
  const out = [];
7930
8531
  async function walk9(current) {
7931
- const entries = await fs17.readdir(current, { withFileTypes: true }).catch(() => []);
8532
+ const entries = await fs20.readdir(current, { withFileTypes: true }).catch(() => []);
7932
8533
  for (const entry of entries) {
7933
- const full = path29.join(current, entry.name);
8534
+ const full = path32.join(current, entry.name);
7934
8535
  if (entry.isDirectory()) {
7935
8536
  if (IGNORED_DIRS.has(entry.name)) continue;
7936
8537
  if (await isPythonVenvDir(full)) continue;
7937
8538
  await walk9(full);
7938
- } else if (entry.isFile() && path29.extname(entry.name) === PROTO_EXTENSION) {
8539
+ } else if (entry.isFile() && path32.extname(entry.name) === PROTO_EXTENSION) {
7939
8540
  out.push(full);
7940
8541
  }
7941
8542
  }
@@ -7950,10 +8551,10 @@ async function addGrpcMethods(graph, services) {
7950
8551
  const protoPaths = await walkProtoFiles(service.dir);
7951
8552
  for (const protoPath of protoPaths) {
7952
8553
  if (isTestPath(protoPath)) continue;
7953
- const relFile = toPosix(path29.relative(service.dir, protoPath));
8554
+ const relFile = toPosix(path32.relative(service.dir, protoPath));
7954
8555
  let content;
7955
8556
  try {
7956
- content = await fs17.readFile(protoPath, "utf8");
8557
+ content = await fs20.readFile(protoPath, "utf8");
7957
8558
  } catch (err) {
7958
8559
  recordExtractionError("proto extraction", protoPath, err);
7959
8560
  continue;
@@ -7971,7 +8572,7 @@ async function addGrpcMethods(graph, services) {
7971
8572
  if (!graph.hasNode(mid)) {
7972
8573
  const node = {
7973
8574
  id: mid,
7974
- type: NodeType16.GrpcMethodNode,
8575
+ type: NodeType19.GrpcMethodNode,
7975
8576
  name: `${method.rpcService}/${method.rpcMethod}`,
7976
8577
  rpcService: method.rpcService,
7977
8578
  rpcMethod: method.rpcMethod,
@@ -8009,18 +8610,18 @@ async function addGrpcMethods(graph, services) {
8009
8610
  // src/extract/calls/index.ts
8010
8611
  import {
8011
8612
  EdgeType as EdgeType15,
8012
- NodeType as NodeType18,
8613
+ NodeType as NodeType21,
8013
8614
  Provenance as Provenance15,
8014
8615
  confidenceForExtracted as confidenceForExtracted12,
8015
8616
  passesExtractedFloor as passesExtractedFloor3
8016
8617
  } from "@neat.is/types";
8017
8618
 
8018
8619
  // src/extract/calls/http.ts
8019
- import path30 from "path";
8620
+ import path33 from "path";
8020
8621
  import Parser6 from "tree-sitter";
8021
8622
  import JavaScript4 from "tree-sitter-javascript";
8022
8623
  import TypeScript2 from "tree-sitter-typescript";
8023
- import Python3 from "tree-sitter-python";
8624
+ import Python4 from "tree-sitter-python";
8024
8625
  import {
8025
8626
  EdgeType as EdgeType13,
8026
8627
  Provenance as Provenance13,
@@ -8082,7 +8683,7 @@ var GRAMMAR_BY_EXT2 = {
8082
8683
  ".jsx": JavaScript4,
8083
8684
  ".mjs": JavaScript4,
8084
8685
  ".cjs": JavaScript4,
8085
- ".py": Python3
8686
+ ".py": Python4
8086
8687
  };
8087
8688
  function parserForExt(ext, cache) {
8088
8689
  const grammar = GRAMMAR_BY_EXT2[ext] ?? JavaScript4;
@@ -8104,7 +8705,7 @@ async function addHttpCallEdges(graph, services) {
8104
8705
  const seen = /* @__PURE__ */ new Set();
8105
8706
  for (const file of files) {
8106
8707
  if (isTestPath(file.path)) continue;
8107
- const parser = parserForExt(path30.extname(file.path), parserCache);
8708
+ const parser = parserForExt(path33.extname(file.path), parserCache);
8108
8709
  let sites;
8109
8710
  try {
8110
8711
  sites = callsFromSource(file.content, parser, knownHosts);
@@ -8113,7 +8714,7 @@ async function addHttpCallEdges(graph, services) {
8113
8714
  continue;
8114
8715
  }
8115
8716
  if (sites.length === 0) continue;
8116
- const relFile = toPosix(path30.relative(service.dir, file.path));
8717
+ const relFile = toPosix(path33.relative(service.dir, file.path));
8117
8718
  for (const site of sites) {
8118
8719
  const targetId = hostToNodeId.get(site.host);
8119
8720
  if (!targetId || targetId === service.node.id) continue;
@@ -8166,16 +8767,16 @@ async function addHttpCallEdges(graph, services) {
8166
8767
  }
8167
8768
 
8168
8769
  // src/extract/calls/route-match.ts
8169
- import path31 from "path";
8770
+ import path34 from "path";
8170
8771
  import Parser7 from "tree-sitter";
8171
8772
  import JavaScript5 from "tree-sitter-javascript";
8172
8773
  import {
8173
8774
  EdgeType as EdgeType14,
8174
- NodeType as NodeType17,
8775
+ NodeType as NodeType20,
8175
8776
  Provenance as Provenance14,
8176
8777
  confidenceForExtracted as confidenceForExtracted11,
8177
8778
  passesExtractedFloor as passesExtractedFloor2,
8178
- serviceId as serviceId6
8779
+ serviceId as serviceId9
8179
8780
  } from "@neat.is/types";
8180
8781
  var PARSE_CHUNK5 = 16384;
8181
8782
  function parseSource5(parser, source) {
@@ -8341,9 +8942,9 @@ function buildRouteIndex(graph) {
8341
8942
  const index = /* @__PURE__ */ new Map();
8342
8943
  graph.forEachNode((_id, attrs) => {
8343
8944
  const node = attrs;
8344
- if (node.type !== NodeType17.RouteNode) return;
8945
+ if (node.type !== NodeType20.RouteNode) return;
8345
8946
  const route = attrs;
8346
- const owner = serviceId6(route.service);
8947
+ const owner = serviceId9(route.service);
8347
8948
  const entry = {
8348
8949
  method: route.method.toUpperCase(),
8349
8950
  normalizedPath: normalizePathTemplate(route.pathTemplate),
@@ -8372,7 +8973,7 @@ async function addRouteCallEdges(graph, services) {
8372
8973
  const seen = /* @__PURE__ */ new Set();
8373
8974
  for (const file of files) {
8374
8975
  if (isTestPath(file.path)) continue;
8375
- if (!JS_CLIENT_EXTENSIONS.has(path31.extname(file.path))) continue;
8976
+ if (!JS_CLIENT_EXTENSIONS.has(path34.extname(file.path))) continue;
8376
8977
  let sites;
8377
8978
  try {
8378
8979
  sites = clientCallSitesFromSource(file.content, jsParser, knownHosts);
@@ -8381,7 +8982,7 @@ async function addRouteCallEdges(graph, services) {
8381
8982
  continue;
8382
8983
  }
8383
8984
  if (sites.length === 0) continue;
8384
- const relFile = toPosix(path31.relative(service.dir, file.path));
8985
+ const relFile = toPosix(path34.relative(service.dir, file.path));
8385
8986
  for (const site of sites) {
8386
8987
  const serverServiceId = hostToNodeId.get(site.host);
8387
8988
  if (!serverServiceId || serverServiceId === service.node.id) continue;
@@ -8441,7 +9042,7 @@ async function addRouteCallEdges(graph, services) {
8441
9042
  }
8442
9043
 
8443
9044
  // src/extract/calls/kafka.ts
8444
- import path32 from "path";
9045
+ import path35 from "path";
8445
9046
  import { infraId as infraId2 } from "@neat.is/types";
8446
9047
  var PRODUCER_TOPIC_RE = /(?:producer|kafkaProducer)[\s\S]{0,40}?\.send\s*\(\s*\{[\s\S]{0,200}?topic\s*:\s*['"`]([^'"`]+)['"`]/g;
8447
9048
  var CONSUMER_TOPIC_RE = /(?:consumer|kafkaConsumer)[\s\S]{0,40}?\.(?:subscribe|run)\s*\(\s*\{[\s\S]{0,200}?topic[s]?\s*:\s*(?:\[\s*)?['"`]([^'"`]+)['"`]/g;
@@ -8472,7 +9073,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
8472
9073
  // tier (ADR-066).
8473
9074
  confidenceKind: "verified-call-site",
8474
9075
  evidence: {
8475
- file: path32.relative(serviceDir, file.path),
9076
+ file: path35.relative(serviceDir, file.path),
8476
9077
  line,
8477
9078
  snippet: snippet(file.content, line)
8478
9079
  }
@@ -8484,7 +9085,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
8484
9085
  }
8485
9086
 
8486
9087
  // src/extract/calls/redis.ts
8487
- import path33 from "path";
9088
+ import path36 from "path";
8488
9089
  import { infraId as infraId3 } from "@neat.is/types";
8489
9090
  var REDIS_URL_RE = /redis(?:s)?:\/\/(?:[^@'"`\s]+@)?([^:/'"`\s]+)(?::(\d+))?/g;
8490
9091
  function redisEndpointsFromFile(file, serviceDir) {
@@ -8507,7 +9108,7 @@ function redisEndpointsFromFile(file, serviceDir) {
8507
9108
  // support tier (ADR-066).
8508
9109
  confidenceKind: "url-with-structural-support",
8509
9110
  evidence: {
8510
- file: path33.relative(serviceDir, file.path),
9111
+ file: path36.relative(serviceDir, file.path),
8511
9112
  line,
8512
9113
  snippet: snippet(file.content, line)
8513
9114
  }
@@ -8517,7 +9118,7 @@ function redisEndpointsFromFile(file, serviceDir) {
8517
9118
  }
8518
9119
 
8519
9120
  // src/extract/calls/aws.ts
8520
- import path34 from "path";
9121
+ import path37 from "path";
8521
9122
  import { infraId as infraId4 } from "@neat.is/types";
8522
9123
  var S3_BUCKET_RE = /Bucket\s*:\s*['"`]([^'"`]+)['"`]/g;
8523
9124
  var DYNAMO_TABLE_RE = /TableName\s*:\s*['"`]([^'"`]+)['"`]/g;
@@ -8551,7 +9152,7 @@ function awsEndpointsFromFile(file, serviceDir) {
8551
9152
  // (ADR-066).
8552
9153
  confidenceKind: "verified-call-site",
8553
9154
  evidence: {
8554
- file: path34.relative(serviceDir, file.path),
9155
+ file: path37.relative(serviceDir, file.path),
8555
9156
  line,
8556
9157
  snippet: snippet(file.content, line)
8557
9158
  }
@@ -8575,7 +9176,7 @@ function awsEndpointsFromFile(file, serviceDir) {
8575
9176
  }
8576
9177
 
8577
9178
  // src/extract/calls/grpc.ts
8578
- import path35 from "path";
9179
+ import path38 from "path";
8579
9180
  import { infraId as infraId5 } from "@neat.is/types";
8580
9181
  var GRPC_CLIENT_RE = /new\s+([A-Z][A-Za-z0-9_]*)Client\s*\(\s*['"`]?([^,'"`)]+)?/g;
8581
9182
  var AWS_SDK_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@aws-sdk\/client-([a-z0-9-]+)['"`]/g;
@@ -8634,7 +9235,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
8634
9235
  // tier (ADR-066).
8635
9236
  confidenceKind: "verified-call-site",
8636
9237
  evidence: {
8637
- file: path35.relative(serviceDir, file.path),
9238
+ file: path38.relative(serviceDir, file.path),
8638
9239
  line,
8639
9240
  snippet: snippet(file.content, line)
8640
9241
  }
@@ -8644,7 +9245,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
8644
9245
  }
8645
9246
 
8646
9247
  // src/extract/calls/supabase.ts
8647
- import path36 from "path";
9248
+ import path39 from "path";
8648
9249
  import { infraId as infraId6 } from "@neat.is/types";
8649
9250
  var SUPABASE_JS_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@supabase\/supabase-js['"`]/;
8650
9251
  var SUPABASE_SSR_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@supabase\/ssr['"`]/;
@@ -8703,7 +9304,7 @@ function supabaseEndpointsFromFile(file, serviceDir) {
8703
9304
  // tier (ADR-066), the same grade aws.ts / grpc.ts emit at.
8704
9305
  confidenceKind: "verified-call-site",
8705
9306
  evidence: {
8706
- file: path36.relative(serviceDir, file.path),
9307
+ file: path39.relative(serviceDir, file.path),
8707
9308
  line,
8708
9309
  snippet: snippet(file.content, line)
8709
9310
  }
@@ -8730,7 +9331,7 @@ function supabaseEndpointsFromFile(file, serviceDir) {
8730
9331
  edgeType: "CALLS",
8731
9332
  confidenceKind: "verified-call-site",
8732
9333
  evidence: {
8733
- file: path36.relative(serviceDir, file.path),
9334
+ file: path39.relative(serviceDir, file.path),
8734
9335
  line,
8735
9336
  snippet: snippet(file.content, line)
8736
9337
  }
@@ -8741,7 +9342,7 @@ function supabaseEndpointsFromFile(file, serviceDir) {
8741
9342
  }
8742
9343
 
8743
9344
  // src/extract/calls/firestore.ts
8744
- import path37 from "path";
9345
+ import path40 from "path";
8745
9346
  import Parser8 from "tree-sitter";
8746
9347
  import JavaScript6 from "tree-sitter-javascript";
8747
9348
  import { infraId as infraId7 } from "@neat.is/types";
@@ -8912,7 +9513,7 @@ function firestoreEndpointsFromFile(file, serviceDir) {
8912
9513
  const hasAdmin = FIRESTORE_ADMIN_IMPORT_RE.test(file.content);
8913
9514
  if (!hasClient && !hasAdmin) return [];
8914
9515
  const fileSdk = hasClient && !hasAdmin ? "client" : hasAdmin && !hasClient ? "admin" : null;
8915
- const tree = parseSource3(parserForExt2(path37.extname(file.path)), file.content);
9516
+ const tree = parseSource3(parserForExt2(path40.extname(file.path)), file.content);
8916
9517
  const clientVars = firestoreClientVars(tree.rootNode);
8917
9518
  const collLine = /* @__PURE__ */ new Map();
8918
9519
  const writes = /* @__PURE__ */ new Map();
@@ -9016,7 +9617,7 @@ function firestoreEndpointsFromFile(file, serviceDir) {
9016
9617
  ...columnSet.size > 0 ? { columns: [...columnSet] } : {},
9017
9618
  ...sdkWrites ? { sdkWrites } : {},
9018
9619
  evidence: {
9019
- file: path37.relative(serviceDir, file.path),
9620
+ file: path40.relative(serviceDir, file.path),
9020
9621
  line,
9021
9622
  snippet: snippet(file.content, line)
9022
9623
  }
@@ -9026,7 +9627,7 @@ function firestoreEndpointsFromFile(file, serviceDir) {
9026
9627
  }
9027
9628
 
9028
9629
  // src/extract/calls/mongoose.ts
9029
- import path38 from "path";
9630
+ import path41 from "path";
9030
9631
  import { infraId as infraId8 } from "@neat.is/types";
9031
9632
  var MONGOOSE_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])mongoose['"`]/;
9032
9633
  var MONGODB_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])mongodb['"`]/;
@@ -9177,7 +9778,7 @@ function endpoint(r, file, serviceDir, matchText) {
9177
9778
  kind: r.kind,
9178
9779
  edgeType: "CALLS",
9179
9780
  confidenceKind: "verified-call-site",
9180
- evidence: { file: path38.relative(serviceDir, file.path), line, snippet: snippet(file.content, line) }
9781
+ evidence: { file: path41.relative(serviceDir, file.path), line, snippet: snippet(file.content, line) }
9181
9782
  };
9182
9783
  }
9183
9784
  function mongooseEndpointsFromFile(file, serviceDir) {
@@ -9267,7 +9868,7 @@ async function mongooseCrossFileEndpoints(files, serviceDir) {
9267
9868
  const registry = /* @__PURE__ */ new Map();
9268
9869
  for (const f of mongooseFiles) {
9269
9870
  const fx = fileExportsOf(f.content, pluralizeOn);
9270
- if (fx) registry.set(toPosix(path38.relative(serviceDir, f.path)), fx);
9871
+ if (fx) registry.set(toPosix(path41.relative(serviceDir, f.path)), fx);
9271
9872
  }
9272
9873
  if (registry.size === 0) return [];
9273
9874
  const out = [];
@@ -9278,7 +9879,7 @@ async function mongooseCrossFileEndpoints(files, serviceDir) {
9278
9879
  const directColl = /* @__PURE__ */ new Map();
9279
9880
  const nsExports = /* @__PURE__ */ new Map();
9280
9881
  for (const b of bindings) {
9281
- const resolvedRel = await resolveJsImport(b.specifier, path38.dirname(f.path), serviceDir, null);
9882
+ const resolvedRel = await resolveJsImport(b.specifier, path41.dirname(f.path), serviceDir, null);
9282
9883
  if (!resolvedRel) continue;
9283
9884
  const fx = registry.get(resolvedRel);
9284
9885
  if (!fx) continue;
@@ -9321,15 +9922,15 @@ async function mongooseCrossFileEndpoints(files, serviceDir) {
9321
9922
  }
9322
9923
 
9323
9924
  // src/extract/calls/sqlalchemy.ts
9324
- import path39 from "path";
9925
+ import path42 from "path";
9325
9926
  import Parser9 from "tree-sitter";
9326
- import Python4 from "tree-sitter-python";
9927
+ import Python5 from "tree-sitter-python";
9327
9928
  import { infraId as infraId9 } from "@neat.is/types";
9328
9929
  var SQLALCHEMY_IMPORT_RE = /(?:from|import)\s+(?:flask_sqlalchemy|sqlalchemy)\b/;
9329
9930
  var PARSE_CHUNK6 = 16384;
9330
9931
  function makePyParser3() {
9331
9932
  const p = new Parser9();
9332
- p.setLanguage(Python4);
9933
+ p.setLanguage(Python5);
9333
9934
  return p;
9334
9935
  }
9335
9936
  function parseSource6(parser, source) {
@@ -9460,7 +10061,7 @@ function sqlalchemyForeignKeys(file, serviceDir) {
9460
10061
  childTable,
9461
10062
  parentTable,
9462
10063
  evidence: {
9463
- file: path39.relative(serviceDir, file.path),
10064
+ file: path42.relative(serviceDir, file.path),
9464
10065
  line,
9465
10066
  snippet: snippet(file.content, line)
9466
10067
  }
@@ -9485,7 +10086,7 @@ function sqlalchemyEndpointsFromFile(file, serviceDir) {
9485
10086
  confidenceKind: "verified-call-site",
9486
10087
  ...columns && columns.length > 0 ? { columns } : {},
9487
10088
  evidence: {
9488
- file: path39.relative(serviceDir, file.path),
10089
+ file: path42.relative(serviceDir, file.path),
9489
10090
  line,
9490
10091
  snippet: snippet(file.content, line)
9491
10092
  }
@@ -9592,7 +10193,7 @@ function pythonOrmCrossFileEndpoints(files, serviceDir) {
9592
10193
  edgeType: "CALLS",
9593
10194
  confidenceKind: "verified-call-site",
9594
10195
  evidence: {
9595
- file: path39.relative(serviceDir, file.path),
10196
+ file: path42.relative(serviceDir, file.path),
9596
10197
  line,
9597
10198
  snippet: snippet(file.content, line)
9598
10199
  }
@@ -9603,15 +10204,15 @@ function pythonOrmCrossFileEndpoints(files, serviceDir) {
9603
10204
  }
9604
10205
 
9605
10206
  // src/extract/calls/django-orm.ts
9606
- import path40 from "path";
10207
+ import path43 from "path";
9607
10208
  import Parser10 from "tree-sitter";
9608
- import Python5 from "tree-sitter-python";
10209
+ import Python6 from "tree-sitter-python";
9609
10210
  import { infraId as infraId10 } from "@neat.is/types";
9610
10211
  var DJANGO_IMPORT_RE = /(?:from|import)\s+django\b/;
9611
10212
  var PARSE_CHUNK7 = 16384;
9612
10213
  function makePyParser4() {
9613
10214
  const p = new Parser10();
9614
- p.setLanguage(Python5);
10215
+ p.setLanguage(Python6);
9615
10216
  return p;
9616
10217
  }
9617
10218
  function parseSource7(parser, source) {
@@ -9673,7 +10274,7 @@ function djangoOrmEndpointsFromFile(file, serviceDir) {
9673
10274
  const tree = parseSource7(makePyParser4(), file.content);
9674
10275
  const out = [];
9675
10276
  const seen = /* @__PURE__ */ new Set();
9676
- const defaultAppLabel = path40.basename(path40.dirname(file.path));
10277
+ const defaultAppLabel = path43.basename(path43.dirname(file.path));
9677
10278
  walk4(tree.rootNode, (node) => {
9678
10279
  if (node.type !== "class_definition") return;
9679
10280
  if (!extendsDjangoModel(node)) return;
@@ -9691,14 +10292,14 @@ function djangoOrmEndpointsFromFile(file, serviceDir) {
9691
10292
  kind: "sql-table",
9692
10293
  edgeType: "CALLS",
9693
10294
  confidenceKind: "verified-call-site",
9694
- evidence: { file: path40.relative(serviceDir, file.path), line, snippet: snippet(file.content, line) }
10295
+ evidence: { file: path43.relative(serviceDir, file.path), line, snippet: snippet(file.content, line) }
9695
10296
  });
9696
10297
  });
9697
10298
  return out;
9698
10299
  }
9699
10300
 
9700
10301
  // src/extract/calls/drizzle.ts
9701
- import path41 from "path";
10302
+ import path44 from "path";
9702
10303
  import Parser11 from "tree-sitter";
9703
10304
  import JavaScript7 from "tree-sitter-javascript";
9704
10305
  import { infraId as infraId11 } from "@neat.is/types";
@@ -9776,7 +10377,7 @@ function columnsFromObject(obj) {
9776
10377
  }
9777
10378
  function drizzleEndpointsFromFile(file, serviceDir) {
9778
10379
  if (!DRIZZLE_IMPORT_RE.test(file.content)) return [];
9779
- const tree = parseSource3(parserForExt3(path41.extname(file.path)), file.content);
10380
+ const tree = parseSource3(parserForExt3(path44.extname(file.path)), file.content);
9780
10381
  const out = [];
9781
10382
  const seen = /* @__PURE__ */ new Set();
9782
10383
  const walk9 = (node) => {
@@ -9799,7 +10400,7 @@ function drizzleEndpointsFromFile(file, serviceDir) {
9799
10400
  confidenceKind: "structural",
9800
10401
  columns,
9801
10402
  evidence: {
9802
- file: path41.relative(serviceDir, file.path),
10403
+ file: path44.relative(serviceDir, file.path),
9803
10404
  line,
9804
10405
  snippet: snippet(file.content, line)
9805
10406
  }
@@ -9865,7 +10466,7 @@ function referencesTargetVar(call) {
9865
10466
  }
9866
10467
  function drizzleForeignKeys(file, serviceDir) {
9867
10468
  if (!DRIZZLE_IMPORT_RE.test(file.content)) return [];
9868
- const tree = parseSource3(parserForExt3(path41.extname(file.path)), file.content);
10469
+ const tree = parseSource3(parserForExt3(path44.extname(file.path)), file.content);
9869
10470
  const { tables, varToTable } = collectDrizzleTables(tree.rootNode);
9870
10471
  const out = [];
9871
10472
  const seen = /* @__PURE__ */ new Set();
@@ -9884,7 +10485,7 @@ function drizzleForeignKeys(file, serviceDir) {
9884
10485
  childTable: table.tableName,
9885
10486
  parentTable,
9886
10487
  evidence: {
9887
- file: path41.relative(serviceDir, file.path),
10488
+ file: path44.relative(serviceDir, file.path),
9888
10489
  line,
9889
10490
  snippet: snippet(file.content, line)
9890
10491
  }
@@ -9900,7 +10501,7 @@ function drizzleForeignKeys(file, serviceDir) {
9900
10501
  }
9901
10502
 
9902
10503
  // src/extract/calls/prisma.ts
9903
- import path42 from "path";
10504
+ import path45 from "path";
9904
10505
  import { infraId as infraId12 } from "@neat.is/types";
9905
10506
  var SCALAR_TYPES = /* @__PURE__ */ new Set([
9906
10507
  "Int",
@@ -9956,7 +10557,7 @@ function prismaColumnsFromSchema(file, serviceDir) {
9956
10557
  confidenceKind: "structural",
9957
10558
  columns: b.columns,
9958
10559
  evidence: {
9959
- file: path42.relative(serviceDir, file.path),
10560
+ file: path45.relative(serviceDir, file.path),
9960
10561
  line: b.startLine,
9961
10562
  snippet: snippet(content, b.startLine)
9962
10563
  }
@@ -10017,7 +10618,7 @@ function prismaColumnsFromSchema(file, serviceDir) {
10017
10618
  }
10018
10619
  async function prismaColumnEndpoints(serviceDir) {
10019
10620
  const schemaPath = await findFirst(serviceDir, [
10020
- path42.join("prisma", "schema.prisma"),
10621
+ path45.join("prisma", "schema.prisma"),
10021
10622
  "schema.prisma"
10022
10623
  ]);
10023
10624
  if (!schemaPath) return [];
@@ -10092,7 +10693,7 @@ function prismaForeignKeysFromSchema(file, serviceDir) {
10092
10693
  childTable: current.table,
10093
10694
  parentTable,
10094
10695
  evidence: {
10095
- file: path42.relative(serviceDir, file.path),
10696
+ file: path45.relative(serviceDir, file.path),
10096
10697
  line: lineNo,
10097
10698
  snippet: snippet(content, lineNo)
10098
10699
  }
@@ -10107,7 +10708,7 @@ function prismaForeignKeysFromSchema(file, serviceDir) {
10107
10708
  }
10108
10709
  async function prismaForeignKeys(serviceDir) {
10109
10710
  const schemaPath = await findFirst(serviceDir, [
10110
- path42.join("prisma", "schema.prisma"),
10711
+ path45.join("prisma", "schema.prisma"),
10111
10712
  "schema.prisma"
10112
10713
  ]);
10113
10714
  if (!schemaPath) return [];
@@ -10117,15 +10718,15 @@ async function prismaForeignKeys(serviceDir) {
10117
10718
  }
10118
10719
 
10119
10720
  // src/extract/calls/activerecord.ts
10120
- import path43 from "path";
10721
+ import path46 from "path";
10121
10722
  import Parser12 from "tree-sitter";
10122
- import Ruby2 from "tree-sitter-ruby";
10723
+ import Ruby3 from "tree-sitter-ruby";
10123
10724
  import { infraId as infraId13 } from "@neat.is/types";
10124
10725
  var AR_SCHEMA_RE = /ActiveRecord::Schema/;
10125
10726
  var PARSE_CHUNK8 = 16384;
10126
10727
  function makeRubyParser2() {
10127
10728
  const p = new Parser12();
10128
- p.setLanguage(Ruby2);
10729
+ p.setLanguage(Ruby3);
10129
10730
  return p;
10130
10731
  }
10131
10732
  function parseSource8(parser, source) {
@@ -10325,7 +10926,7 @@ function railsSchemaEndpointsFromFile(file, serviceDir) {
10325
10926
  confidenceKind: "structural",
10326
10927
  ...table.columns.length > 0 ? { columns: table.columns } : {},
10327
10928
  evidence: {
10328
- file: path43.relative(serviceDir, file.path),
10929
+ file: path46.relative(serviceDir, file.path),
10329
10930
  line: table.line,
10330
10931
  snippet: snippet(file.content, table.line)
10331
10932
  }
@@ -10347,7 +10948,7 @@ function railsSchemaForeignKeys(file, serviceDir) {
10347
10948
  childTable,
10348
10949
  parentTable,
10349
10950
  evidence: {
10350
- file: path43.relative(serviceDir, file.path),
10951
+ file: path46.relative(serviceDir, file.path),
10351
10952
  line,
10352
10953
  snippet: snippet(file.content, line)
10353
10954
  }
@@ -10430,7 +11031,7 @@ function railsModelEndpointsFromFile(file, serviceDir) {
10430
11031
  edgeType: "CALLS",
10431
11032
  confidenceKind: "verified-call-site",
10432
11033
  evidence: {
10433
- file: path43.relative(serviceDir, file.path),
11034
+ file: path46.relative(serviceDir, file.path),
10434
11035
  line,
10435
11036
  snippet: snippet(file.content, line)
10436
11037
  }
@@ -10467,7 +11068,7 @@ function railsModelForeignKeys(file, serviceDir) {
10467
11068
  childTable,
10468
11069
  parentTable,
10469
11070
  evidence: {
10470
- file: path43.relative(serviceDir, file.path),
11071
+ file: path46.relative(serviceDir, file.path),
10471
11072
  line,
10472
11073
  snippet: snippet(file.content, line)
10473
11074
  }
@@ -10478,15 +11079,15 @@ function railsModelForeignKeys(file, serviceDir) {
10478
11079
  }
10479
11080
 
10480
11081
  // src/extract/calls/eloquent.ts
10481
- import path44 from "path";
11082
+ import path47 from "path";
10482
11083
  import Parser13 from "tree-sitter";
10483
- import Php2 from "tree-sitter-php";
11084
+ import Php3 from "tree-sitter-php";
10484
11085
  import { infraId as infraId14 } from "@neat.is/types";
10485
11086
  var LARAVEL_SCHEMA_RE = /\bSchema::(create|createIfNotExists|table)\b/;
10486
11087
  var PARSE_CHUNK9 = 16384;
10487
11088
  function makePhpParser2() {
10488
11089
  const p = new Parser13();
10489
- p.setLanguage(Php2.php_only);
11090
+ p.setLanguage(Php3.php_only);
10490
11091
  return p;
10491
11092
  }
10492
11093
  function parseSource9(parser, source) {
@@ -10735,7 +11336,7 @@ function laravelMigrationEndpointsFromFile(file, serviceDir) {
10735
11336
  confidenceKind: "structural",
10736
11337
  ...columns.length > 0 ? { columns } : {},
10737
11338
  evidence: {
10738
- file: path44.relative(serviceDir, file.path),
11339
+ file: path47.relative(serviceDir, file.path),
10739
11340
  line: bp.line,
10740
11341
  snippet: snippet(file.content, bp.line)
10741
11342
  }
@@ -10757,7 +11358,7 @@ function laravelMigrationForeignKeys(file, serviceDir) {
10757
11358
  childTable,
10758
11359
  parentTable,
10759
11360
  evidence: {
10760
- file: path44.relative(serviceDir, file.path),
11361
+ file: path47.relative(serviceDir, file.path),
10761
11362
  line,
10762
11363
  snippet: snippet(file.content, line)
10763
11364
  }
@@ -10877,7 +11478,7 @@ function laravelModelEndpointsFromFile(file, serviceDir) {
10877
11478
  edgeType: "CALLS",
10878
11479
  confidenceKind: "verified-call-site",
10879
11480
  evidence: {
10880
- file: path44.relative(serviceDir, file.path),
11481
+ file: path47.relative(serviceDir, file.path),
10881
11482
  line,
10882
11483
  snippet: snippet(file.content, line)
10883
11484
  }
@@ -10913,7 +11514,7 @@ function laravelModelForeignKeys(file, serviceDir) {
10913
11514
  childTable,
10914
11515
  parentTable,
10915
11516
  evidence: {
10916
- file: path44.relative(serviceDir, file.path),
11517
+ file: path47.relative(serviceDir, file.path),
10917
11518
  line,
10918
11519
  snippet: snippet(file.content, line)
10919
11520
  }
@@ -10924,9 +11525,9 @@ function laravelModelForeignKeys(file, serviceDir) {
10924
11525
  }
10925
11526
 
10926
11527
  // src/extract/calls/go.ts
10927
- import path45 from "path";
11528
+ import path48 from "path";
10928
11529
  import Parser14 from "tree-sitter";
10929
- import Go3 from "tree-sitter-go";
11530
+ import Go4 from "tree-sitter-go";
10930
11531
  import { infraId as infraId15 } from "@neat.is/types";
10931
11532
  var PARSE_CHUNK10 = 16384;
10932
11533
  var DATABASE_SQL_METHODS = /* @__PURE__ */ new Set([
@@ -10955,7 +11556,7 @@ var DATABASE_SQL_IMPORT = "database/sql";
10955
11556
  var SQLX_IMPORT = "github.com/jmoiron/sqlx";
10956
11557
  function makeGoParser3() {
10957
11558
  const p = new Parser14();
10958
- p.setLanguage(Go3);
11559
+ p.setLanguage(Go4);
10959
11560
  return p;
10960
11561
  }
10961
11562
  function parseSource10(parser, source) {
@@ -10998,7 +11599,7 @@ function firstStringLiteralArg(argsNode) {
10998
11599
  return null;
10999
11600
  }
11000
11601
  function goSqlEndpointsFromFile(file, serviceDir) {
11001
- if (path45.extname(file.path) !== ".go") return [];
11602
+ if (path48.extname(file.path) !== ".go") return [];
11002
11603
  if (!file.content.includes(DATABASE_SQL_IMPORT) && !file.content.includes(SQLX_IMPORT)) return [];
11003
11604
  const tree = parseSource10(makeGoParser3(), file.content);
11004
11605
  const importsDatabaseSql = goImportsAny(tree.rootNode, /* @__PURE__ */ new Set([DATABASE_SQL_IMPORT]));
@@ -11027,7 +11628,7 @@ function goSqlEndpointsFromFile(file, serviceDir) {
11027
11628
  confidenceKind: "verified-call-site",
11028
11629
  ...columns.length > 0 ? { columns } : {},
11029
11630
  evidence: {
11030
- file: toPosix(path45.relative(serviceDir, file.path)),
11631
+ file: toPosix(path48.relative(serviceDir, file.path)),
11031
11632
  line,
11032
11633
  snippet: snippet(file.content, line)
11033
11634
  }
@@ -11037,15 +11638,15 @@ function goSqlEndpointsFromFile(file, serviceDir) {
11037
11638
  }
11038
11639
 
11039
11640
  // src/extract/calls/gorm.ts
11040
- import path46 from "path";
11641
+ import path49 from "path";
11041
11642
  import Parser15 from "tree-sitter";
11042
- import Go4 from "tree-sitter-go";
11643
+ import Go5 from "tree-sitter-go";
11043
11644
  import { infraId as infraId16 } from "@neat.is/types";
11044
11645
  var GORM_IMPORT_RE = /gorm\.io\/gorm/;
11045
11646
  var PARSE_CHUNK11 = 16384;
11046
11647
  function makeGoParser4() {
11047
11648
  const p = new Parser15();
11048
- p.setLanguage(Go4);
11649
+ p.setLanguage(Go5);
11049
11650
  return p;
11050
11651
  }
11051
11652
  function parseSource11(parser, source) {
@@ -11470,7 +12071,7 @@ function collectColumns(struct, structs, seen, prefix, out, emitted) {
11470
12071
  seen.delete(struct.name);
11471
12072
  }
11472
12073
  function gormEndpointsFromFile(file, serviceDir) {
11473
- if (path46.extname(file.path) !== ".go") return [];
12074
+ if (path49.extname(file.path) !== ".go") return [];
11474
12075
  if (!GORM_IMPORT_RE.test(file.content)) return [];
11475
12076
  const tree = parseSource11(makeGoParser4(), file.content);
11476
12077
  const { structs, models, tableFor } = analyze(tree);
@@ -11492,7 +12093,7 @@ function gormEndpointsFromFile(file, serviceDir) {
11492
12093
  confidenceKind: "structural",
11493
12094
  ...columns.length > 0 ? { columns } : {},
11494
12095
  evidence: {
11495
- file: toPosix(path46.relative(serviceDir, file.path)),
12096
+ file: toPosix(path49.relative(serviceDir, file.path)),
11496
12097
  line: struct.line,
11497
12098
  snippet: snippet(file.content, struct.line)
11498
12099
  }
@@ -11501,7 +12102,7 @@ function gormEndpointsFromFile(file, serviceDir) {
11501
12102
  return out;
11502
12103
  }
11503
12104
  function gormForeignKeys(file, serviceDir) {
11504
- if (path46.extname(file.path) !== ".go") return [];
12105
+ if (path49.extname(file.path) !== ".go") return [];
11505
12106
  if (!GORM_IMPORT_RE.test(file.content)) return [];
11506
12107
  const tree = parseSource11(makeGoParser4(), file.content);
11507
12108
  const { structs, models, tableFor } = analyze(tree);
@@ -11516,7 +12117,7 @@ function gormForeignKeys(file, serviceDir) {
11516
12117
  childTable,
11517
12118
  parentTable,
11518
12119
  evidence: {
11519
- file: toPosix(path46.relative(serviceDir, file.path)),
12120
+ file: toPosix(path49.relative(serviceDir, file.path)),
11520
12121
  line,
11521
12122
  snippet: snippet(file.content, line)
11522
12123
  }
@@ -11619,7 +12220,7 @@ async function addExternalEndpointEdges(graph, services) {
11619
12220
  if (!graph.hasNode(ep.infraId)) {
11620
12221
  const node = {
11621
12222
  id: ep.infraId,
11622
- type: NodeType18.InfraNode,
12223
+ type: NodeType21.InfraNode,
11623
12224
  name: ep.name,
11624
12225
  // #238 — `aws-*` covers AWS-SDK client kinds (aws-s3, aws-dynamodb,
11625
12226
  // aws-cognito-identity-provider, …); `s3-` / `dynamodb-` cover the
@@ -11632,7 +12233,7 @@ async function addExternalEndpointEdges(graph, services) {
11632
12233
  }
11633
12234
  if (ep.columns && ep.columns.length > 0) {
11634
12235
  const node = graph.getNodeAttributes(ep.infraId);
11635
- if (node.type === NodeType18.InfraNode) {
12236
+ if (node.type === NodeType21.InfraNode) {
11636
12237
  graph.replaceNodeAttributes(ep.infraId, {
11637
12238
  ...node,
11638
12239
  columns: foldColumns(
@@ -11646,7 +12247,7 @@ async function addExternalEndpointEdges(graph, services) {
11646
12247
  }
11647
12248
  if (ep.sdkWrites && Object.keys(ep.sdkWrites).length > 0) {
11648
12249
  const node = graph.getNodeAttributes(ep.infraId);
11649
- if (node.type === NodeType18.InfraNode) {
12250
+ if (node.type === NodeType21.InfraNode) {
11650
12251
  graph.replaceNodeAttributes(ep.infraId, {
11651
12252
  ...node,
11652
12253
  columns: foldSdkWrites(node.columns, ep.sdkWrites)
@@ -11708,7 +12309,7 @@ async function addCallEdges(graph, services) {
11708
12309
  // src/extract/table-edges.ts
11709
12310
  import {
11710
12311
  EdgeType as EdgeType16,
11711
- NodeType as NodeType19,
12312
+ NodeType as NodeType22,
11712
12313
  Provenance as Provenance16,
11713
12314
  confidenceForExtracted as confidenceForExtracted13,
11714
12315
  extractedEdgeId as extractedEdgeId10,
@@ -11767,7 +12368,7 @@ function ensureTableNode(graph, id, name) {
11767
12368
  if (graph.hasNode(id)) return 0;
11768
12369
  const node = {
11769
12370
  id,
11770
- type: NodeType19.InfraNode,
12371
+ type: NodeType22.InfraNode,
11771
12372
  name,
11772
12373
  provider: "self",
11773
12374
  kind: "sql-table"
@@ -11777,15 +12378,15 @@ function ensureTableNode(graph, id, name) {
11777
12378
  }
11778
12379
 
11779
12380
  // src/extract/infra/docker-compose.ts
11780
- import path47 from "path";
12381
+ import path50 from "path";
11781
12382
  import { EdgeType as EdgeType17, Provenance as Provenance18, confidenceForExtracted as confidenceForExtracted15 } from "@neat.is/types";
11782
12383
 
11783
12384
  // src/extract/infra/shared.ts
11784
- import { NodeType as NodeType20, Provenance as Provenance17, confidenceForExtracted as confidenceForExtracted14, infraId as infraId18 } from "@neat.is/types";
12385
+ import { NodeType as NodeType23, Provenance as Provenance17, confidenceForExtracted as confidenceForExtracted14, infraId as infraId18 } from "@neat.is/types";
11785
12386
  function makeInfraNode(kind, name, provider = "self", extras) {
11786
12387
  return {
11787
12388
  id: infraId18(kind, name),
11788
- type: NodeType20.InfraNode,
12389
+ type: NodeType23.InfraNode,
11789
12390
  name,
11790
12391
  provider,
11791
12392
  kind,
@@ -11847,7 +12448,7 @@ function dependsOnList(value) {
11847
12448
  }
11848
12449
  function serviceNameToServiceNode(name, services) {
11849
12450
  for (const s of services) {
11850
- if (s.node.name === name || path47.basename(s.dir) === name) return s.node.id;
12451
+ if (s.node.name === name || path50.basename(s.dir) === name) return s.node.id;
11851
12452
  }
11852
12453
  return null;
11853
12454
  }
@@ -11856,7 +12457,7 @@ async function addComposeInfra(graph, scanPath, services) {
11856
12457
  let edgesAdded = 0;
11857
12458
  let composePath = null;
11858
12459
  for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
11859
- const abs = path47.join(scanPath, name);
12460
+ const abs = path50.join(scanPath, name);
11860
12461
  if (await exists(abs)) {
11861
12462
  composePath = abs;
11862
12463
  break;
@@ -11869,13 +12470,13 @@ async function addComposeInfra(graph, scanPath, services) {
11869
12470
  } catch (err) {
11870
12471
  recordExtractionError(
11871
12472
  "infra docker-compose",
11872
- path47.relative(scanPath, composePath),
12473
+ path50.relative(scanPath, composePath),
11873
12474
  err
11874
12475
  );
11875
12476
  return { nodesAdded, edgesAdded };
11876
12477
  }
11877
12478
  if (!compose?.services) return { nodesAdded, edgesAdded };
11878
- const evidenceFile = path47.relative(scanPath, composePath).split(path47.sep).join("/");
12479
+ const evidenceFile = path50.relative(scanPath, composePath).split(path50.sep).join("/");
11879
12480
  const composeNameToNodeId = /* @__PURE__ */ new Map();
11880
12481
  for (const [composeName, svc] of Object.entries(compose.services)) {
11881
12482
  const matchedServiceId = serviceNameToServiceNode(composeName, services);
@@ -11916,8 +12517,8 @@ async function addComposeInfra(graph, scanPath, services) {
11916
12517
  }
11917
12518
 
11918
12519
  // src/extract/infra/dockerfile.ts
11919
- import path48 from "path";
11920
- import { promises as fs18 } from "fs";
12520
+ import path51 from "path";
12521
+ import { promises as fs21 } from "fs";
11921
12522
  import { EdgeType as EdgeType18, Provenance as Provenance19, confidenceForExtracted as confidenceForExtracted16 } from "@neat.is/types";
11922
12523
  function readDockerfile(content) {
11923
12524
  let image = null;
@@ -11947,15 +12548,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
11947
12548
  let nodesAdded = 0;
11948
12549
  let edgesAdded = 0;
11949
12550
  for (const service of services) {
11950
- const dockerfilePath = path48.join(service.dir, "Dockerfile");
12551
+ const dockerfilePath = path51.join(service.dir, "Dockerfile");
11951
12552
  if (!await exists(dockerfilePath)) continue;
11952
12553
  let content;
11953
12554
  try {
11954
- content = await fs18.readFile(dockerfilePath, "utf8");
12555
+ content = await fs21.readFile(dockerfilePath, "utf8");
11955
12556
  } catch (err) {
11956
12557
  recordExtractionError(
11957
12558
  "infra dockerfile",
11958
- path48.relative(scanPath, dockerfilePath),
12559
+ path51.relative(scanPath, dockerfilePath),
11959
12560
  err
11960
12561
  );
11961
12562
  continue;
@@ -11967,8 +12568,8 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
11967
12568
  graph.addNode(node.id, node);
11968
12569
  nodesAdded++;
11969
12570
  }
11970
- const relDockerfile = toPosix(path48.relative(service.dir, dockerfilePath));
11971
- const evidenceFile = toPosix(path48.relative(scanPath, dockerfilePath));
12571
+ const relDockerfile = toPosix(path51.relative(service.dir, dockerfilePath));
12572
+ const evidenceFile = toPosix(path51.relative(scanPath, dockerfilePath));
11972
12573
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
11973
12574
  graph,
11974
12575
  service.pkg.name,
@@ -12019,23 +12620,23 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
12019
12620
  }
12020
12621
 
12021
12622
  // src/extract/infra/terraform.ts
12022
- import { promises as fs19 } from "fs";
12023
- import path49 from "path";
12623
+ import { promises as fs22 } from "fs";
12624
+ import path52 from "path";
12024
12625
  import { EdgeType as EdgeType19, Provenance as Provenance20, confidenceForExtracted as confidenceForExtracted17 } from "@neat.is/types";
12025
12626
  var RESOURCE_RE = /resource\s+"(aws_[A-Za-z0-9_]+)"\s+"([A-Za-z0-9_-]+)"/g;
12026
12627
  var REFERENCE_RE = /(?<![\w.])(aws_[A-Za-z0-9_]+)\.([A-Za-z0-9_-]+)/g;
12027
12628
  async function walkTfFiles(start, depth = 0, max = 5) {
12028
12629
  if (depth > max) return [];
12029
12630
  const out = [];
12030
- const entries = await fs19.readdir(start, { withFileTypes: true }).catch(() => []);
12631
+ const entries = await fs22.readdir(start, { withFileTypes: true }).catch(() => []);
12031
12632
  for (const entry of entries) {
12032
12633
  if (entry.isDirectory()) {
12033
12634
  if (IGNORED_DIRS.has(entry.name) || entry.name === ".terraform") continue;
12034
- const child = path49.join(start, entry.name);
12635
+ const child = path52.join(start, entry.name);
12035
12636
  if (await isPythonVenvDir(child)) continue;
12036
12637
  out.push(...await walkTfFiles(child, depth + 1, max));
12037
12638
  } else if (entry.isFile() && entry.name.endsWith(".tf")) {
12038
- out.push(path49.join(start, entry.name));
12639
+ out.push(path52.join(start, entry.name));
12039
12640
  }
12040
12641
  }
12041
12642
  return out;
@@ -12066,8 +12667,8 @@ async function addTerraformResources(graph, scanPath) {
12066
12667
  let edgesAdded = 0;
12067
12668
  const files = await walkTfFiles(scanPath);
12068
12669
  for (const file of files) {
12069
- const content = await fs19.readFile(file, "utf8");
12070
- const evidenceFile = toPosix(path49.relative(scanPath, file));
12670
+ const content = await fs22.readFile(file, "utf8");
12671
+ const evidenceFile = toPosix(path52.relative(scanPath, file));
12071
12672
  const resources = [];
12072
12673
  const byKey = /* @__PURE__ */ new Map();
12073
12674
  RESOURCE_RE.lastIndex = 0;
@@ -12123,8 +12724,8 @@ async function addTerraformResources(graph, scanPath) {
12123
12724
  }
12124
12725
 
12125
12726
  // src/extract/infra/k8s.ts
12126
- import { promises as fs20 } from "fs";
12127
- import path50 from "path";
12727
+ import { promises as fs23 } from "fs";
12728
+ import path53 from "path";
12128
12729
  import { parseAllDocuments as parseAllDocuments2 } from "yaml";
12129
12730
  var K8S_KIND_TO_INFRA_KIND = {
12130
12731
  Service: "k8s-service",
@@ -12138,15 +12739,15 @@ var K8S_KIND_TO_INFRA_KIND = {
12138
12739
  async function walkYamlFiles2(start, depth = 0, max = 5) {
12139
12740
  if (depth > max) return [];
12140
12741
  const out = [];
12141
- const entries = await fs20.readdir(start, { withFileTypes: true }).catch(() => []);
12742
+ const entries = await fs23.readdir(start, { withFileTypes: true }).catch(() => []);
12142
12743
  for (const entry of entries) {
12143
12744
  if (entry.isDirectory()) {
12144
12745
  if (IGNORED_DIRS.has(entry.name)) continue;
12145
- const child = path50.join(start, entry.name);
12746
+ const child = path53.join(start, entry.name);
12146
12747
  if (await isPythonVenvDir(child)) continue;
12147
12748
  out.push(...await walkYamlFiles2(child, depth + 1, max));
12148
- } else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(path50.extname(entry.name))) {
12149
- out.push(path50.join(start, entry.name));
12749
+ } else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(path53.extname(entry.name))) {
12750
+ out.push(path53.join(start, entry.name));
12150
12751
  }
12151
12752
  }
12152
12753
  return out;
@@ -12155,7 +12756,7 @@ async function addK8sResources(graph, scanPath) {
12155
12756
  let nodesAdded = 0;
12156
12757
  const files = await walkYamlFiles2(scanPath);
12157
12758
  for (const file of files) {
12158
- const content = await fs20.readFile(file, "utf8");
12759
+ const content = await fs23.readFile(file, "utf8");
12159
12760
  let docs;
12160
12761
  try {
12161
12762
  docs = parseAllDocuments2(content).map((d) => d.toJSON());
@@ -12178,16 +12779,16 @@ async function addK8sResources(graph, scanPath) {
12178
12779
  }
12179
12780
 
12180
12781
  // src/extract/infra/cloudflare.ts
12181
- import { promises as fs21 } from "fs";
12182
- import path51 from "path";
12782
+ import { promises as fs24 } from "fs";
12783
+ import path54 from "path";
12183
12784
  import { parse as parseToml2 } from "smol-toml";
12184
12785
  import { EdgeType as EdgeType20, Provenance as Provenance21, confidenceForExtracted as confidenceForExtracted18 } from "@neat.is/types";
12185
12786
  var WRANGLER_FILENAMES = ["wrangler.toml", "wrangler.jsonc", "wrangler.json"];
12186
12787
  async function readWranglerConfig(dir) {
12187
12788
  for (const filename of WRANGLER_FILENAMES) {
12188
- const abs = path51.join(dir, filename);
12789
+ const abs = path54.join(dir, filename);
12189
12790
  if (!await exists(abs)) continue;
12190
- const raw = await fs21.readFile(abs, "utf8");
12791
+ const raw = await fs24.readFile(abs, "utf8");
12191
12792
  const config = filename === "wrangler.toml" ? parseToml2(raw) : JSON.parse(maskCommentsInSource(raw));
12192
12793
  return { config, relFile: filename, raw };
12193
12794
  }
@@ -12248,11 +12849,11 @@ async function addCloudflareWorkers(graph, services, scanPath) {
12248
12849
  try {
12249
12850
  read = await readWranglerConfig(service.dir);
12250
12851
  } catch (err) {
12251
- recordExtractionError("infra cloudflare", path51.relative(scanPath, service.dir), err);
12852
+ recordExtractionError("infra cloudflare", path54.relative(scanPath, service.dir), err);
12252
12853
  continue;
12253
12854
  }
12254
12855
  if (!read || !read.config.name) continue;
12255
- const evidenceFile = toPosix(path51.relative(scanPath, path51.join(service.dir, read.relFile)));
12856
+ const evidenceFile = toPosix(path54.relative(scanPath, path54.join(service.dir, read.relFile)));
12256
12857
  discovered.push({ service, config: read.config, relFile: read.relFile, raw: read.raw, evidenceFile });
12257
12858
  }
12258
12859
  for (const worker of discovered) {
@@ -12264,7 +12865,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
12264
12865
  }
12265
12866
  let anchorId = service.node.id;
12266
12867
  if (config.main) {
12267
- const entryRelPath = toPosix(path51.normalize(config.main));
12868
+ const entryRelPath = toPosix(path54.normalize(config.main));
12268
12869
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
12269
12870
  graph,
12270
12871
  service.pkg.name,
@@ -12410,24 +13011,24 @@ async function addCloudflareWorkers(graph, services, scanPath) {
12410
13011
  }
12411
13012
 
12412
13013
  // src/extract/infra/vercel.ts
12413
- import { promises as fs22 } from "fs";
12414
- import path52 from "path";
13014
+ import { promises as fs25 } from "fs";
13015
+ import path55 from "path";
12415
13016
  import { EdgeType as EdgeType21 } from "@neat.is/types";
12416
13017
  var VERCEL_CONFIG_FILENAMES = ["vercel.json", "vercel.jsonc"];
12417
13018
  async function readVercelConfig(dir) {
12418
13019
  for (const filename of VERCEL_CONFIG_FILENAMES) {
12419
- const abs = path52.join(dir, filename);
13020
+ const abs = path55.join(dir, filename);
12420
13021
  if (!await exists(abs)) continue;
12421
- const raw = await fs22.readFile(abs, "utf8");
13022
+ const raw = await fs25.readFile(abs, "utf8");
12422
13023
  const config = JSON.parse(maskCommentsInSource(raw));
12423
13024
  return { config, relFile: filename, raw };
12424
13025
  }
12425
13026
  return null;
12426
13027
  }
12427
13028
  async function readLinkedProjectName(dir) {
12428
- const abs = path52.join(dir, ".vercel", "project.json");
13029
+ const abs = path55.join(dir, ".vercel", "project.json");
12429
13030
  if (!await exists(abs)) return void 0;
12430
- const parsed = JSON.parse(await fs22.readFile(abs, "utf8"));
13031
+ const parsed = JSON.parse(await fs25.readFile(abs, "utf8"));
12431
13032
  return typeof parsed.projectName === "string" ? parsed.projectName : void 0;
12432
13033
  }
12433
13034
  function routeSource(route) {
@@ -12443,7 +13044,7 @@ async function addVercelServices(graph, services, scanPath) {
12443
13044
  read = await readVercelConfig(service.dir);
12444
13045
  projectName = await readLinkedProjectName(service.dir);
12445
13046
  } catch (err) {
12446
- recordExtractionError("infra vercel", path52.relative(scanPath, service.dir), err);
13047
+ recordExtractionError("infra vercel", path55.relative(scanPath, service.dir), err);
12447
13048
  continue;
12448
13049
  }
12449
13050
  if (!read && !projectName) continue;
@@ -12459,7 +13060,7 @@ async function addVercelServices(graph, services, scanPath) {
12459
13060
  const anchorId = service.node.id;
12460
13061
  if (!read) continue;
12461
13062
  const { config, relFile, raw } = read;
12462
- const evidenceFile = toPosix(path52.relative(scanPath, path52.join(service.dir, relFile)));
13063
+ const evidenceFile = toPosix(path55.relative(scanPath, path55.join(service.dir, relFile)));
12463
13064
  const add = (edgeType, kind, name) => {
12464
13065
  if (!name) return;
12465
13066
  const result = emitPlatformResourceEdge(
@@ -12487,16 +13088,16 @@ async function addVercelServices(graph, services, scanPath) {
12487
13088
  }
12488
13089
 
12489
13090
  // src/extract/infra/railway.ts
12490
- import { promises as fs23 } from "fs";
12491
- import path53 from "path";
13091
+ import { promises as fs26 } from "fs";
13092
+ import path56 from "path";
12492
13093
  import { parse as parseToml3 } from "smol-toml";
12493
13094
  import { EdgeType as EdgeType22 } from "@neat.is/types";
12494
13095
  var RAILWAY_FILENAMES = ["railway.toml", "railway.json", "railway.jsonc"];
12495
13096
  async function readRailwayConfig(dir) {
12496
13097
  for (const filename of RAILWAY_FILENAMES) {
12497
- const abs = path53.join(dir, filename);
13098
+ const abs = path56.join(dir, filename);
12498
13099
  if (!await exists(abs)) continue;
12499
- const raw = await fs23.readFile(abs, "utf8");
13100
+ const raw = await fs26.readFile(abs, "utf8");
12500
13101
  const config = filename === "railway.toml" ? parseToml3(raw) : JSON.parse(maskCommentsInSource(raw));
12501
13102
  return { config, relFile: filename, raw };
12502
13103
  }
@@ -12510,7 +13111,7 @@ async function addRailwayServices(graph, services, scanPath) {
12510
13111
  try {
12511
13112
  read = await readRailwayConfig(service.dir);
12512
13113
  } catch (err) {
12513
- recordExtractionError("infra railway", path53.relative(scanPath, service.dir), err);
13114
+ recordExtractionError("infra railway", path56.relative(scanPath, service.dir), err);
12514
13115
  continue;
12515
13116
  }
12516
13117
  if (!read) continue;
@@ -12520,7 +13121,7 @@ async function addRailwayServices(graph, services, scanPath) {
12520
13121
  }
12521
13122
  const anchorId = service.node.id;
12522
13123
  const { config, relFile, raw } = read;
12523
- const evidenceFile = toPosix(path53.relative(scanPath, path53.join(service.dir, relFile)));
13124
+ const evidenceFile = toPosix(path56.relative(scanPath, path56.join(service.dir, relFile)));
12524
13125
  const add = (edgeType, kind, name) => {
12525
13126
  if (!name) return;
12526
13127
  const result = emitPlatformResourceEdge(
@@ -12544,15 +13145,15 @@ async function addRailwayServices(graph, services, scanPath) {
12544
13145
  }
12545
13146
 
12546
13147
  // src/extract/infra/supabase.ts
12547
- import { promises as fs24 } from "fs";
12548
- import path54 from "path";
13148
+ import { promises as fs27 } from "fs";
13149
+ import path57 from "path";
12549
13150
  import { parse as parseToml4 } from "smol-toml";
12550
13151
  import { EdgeType as EdgeType23 } from "@neat.is/types";
12551
13152
  async function readSupabaseConfig(dir) {
12552
- const relFile = path54.join("supabase", "config.toml");
12553
- const abs = path54.join(dir, relFile);
13153
+ const relFile = path57.join("supabase", "config.toml");
13154
+ const abs = path57.join(dir, relFile);
12554
13155
  if (!await exists(abs)) return null;
12555
- const raw = await fs24.readFile(abs, "utf8");
13156
+ const raw = await fs27.readFile(abs, "utf8");
12556
13157
  const config = parseToml4(raw);
12557
13158
  return { config, relFile, raw };
12558
13159
  }
@@ -12564,7 +13165,7 @@ async function addSupabaseProjects(graph, services, scanPath) {
12564
13165
  try {
12565
13166
  read = await readSupabaseConfig(service.dir);
12566
13167
  } catch (err) {
12567
- recordExtractionError("infra supabase", path54.relative(scanPath, service.dir), err);
13168
+ recordExtractionError("infra supabase", path57.relative(scanPath, service.dir), err);
12568
13169
  continue;
12569
13170
  }
12570
13171
  if (!read) continue;
@@ -12579,7 +13180,7 @@ async function addSupabaseProjects(graph, services, scanPath) {
12579
13180
  });
12580
13181
  }
12581
13182
  const anchorId = service.node.id;
12582
- const evidenceFile = toPosix(path54.relative(scanPath, path54.join(service.dir, relFile)));
13183
+ const evidenceFile = toPosix(path57.relative(scanPath, path57.join(service.dir, relFile)));
12583
13184
  const add = (edgeType, kind, name) => {
12584
13185
  if (!name) return;
12585
13186
  const result = emitPlatformResourceEdge(
@@ -12620,12 +13221,12 @@ async function addInfra(graph, scanPath, services) {
12620
13221
  }
12621
13222
 
12622
13223
  // src/extract/zod-shapes.ts
12623
- import path55 from "path";
13224
+ import path58 from "path";
12624
13225
  import Parser16 from "tree-sitter";
12625
13226
  import JavaScript8 from "tree-sitter-javascript";
12626
13227
  import {
12627
13228
  EdgeType as EdgeType24,
12628
- NodeType as NodeType21,
13229
+ NodeType as NodeType24,
12629
13230
  Provenance as Provenance22,
12630
13231
  confidenceForExtracted as confidenceForExtracted19,
12631
13232
  extractedEdgeId as extractedEdgeId11,
@@ -12722,7 +13323,7 @@ function topLevelSchemas(root) {
12722
13323
  }
12723
13324
  function zodShapesFromFile(file, serviceDir) {
12724
13325
  if (!ZOD_IMPORT_RE.test(file.content)) return [];
12725
- const tree = parseSource3(parserForExt4(path55.extname(file.path)), file.content);
13326
+ const tree = parseSource3(parserForExt4(path58.extname(file.path)), file.content);
12726
13327
  const out = [];
12727
13328
  const seen = /* @__PURE__ */ new Set();
12728
13329
  for (const { name, call } of topLevelSchemas(tree.rootNode)) {
@@ -12740,7 +13341,7 @@ function zodShapesFromFile(file, serviceDir) {
12740
13341
  name,
12741
13342
  fields,
12742
13343
  evidence: {
12743
- file: path55.relative(serviceDir, file.path),
13344
+ file: path58.relative(serviceDir, file.path),
12744
13345
  line,
12745
13346
  snippet: snippet(file.content, line)
12746
13347
  }
@@ -12771,7 +13372,7 @@ async function addZodShapes(graph, services) {
12771
13372
  if (!graph.hasNode(shape.infraId)) {
12772
13373
  const node = {
12773
13374
  id: shape.infraId,
12774
- type: NodeType21.InfraNode,
13375
+ type: NodeType24.InfraNode,
12775
13376
  name: shape.name,
12776
13377
  provider: "self",
12777
13378
  kind: "zod-schema"
@@ -12781,7 +13382,7 @@ async function addZodShapes(graph, services) {
12781
13382
  }
12782
13383
  if (shape.fields.length > 0) {
12783
13384
  const node = graph.getNodeAttributes(shape.infraId);
12784
- if (node.type === NodeType21.InfraNode) {
13385
+ if (node.type === NodeType24.InfraNode) {
12785
13386
  graph.replaceNodeAttributes(shape.infraId, {
12786
13387
  ...node,
12787
13388
  columns: foldColumns(
@@ -12823,7 +13424,7 @@ async function addZodShapes(graph, services) {
12823
13424
  }
12824
13425
 
12825
13426
  // src/extract/firestore-rules.ts
12826
- import { NodeType as NodeType22 } from "@neat.is/types";
13427
+ import { NodeType as NodeType25 } from "@neat.is/types";
12827
13428
  var FIRESTORE_COLLECTION_KIND = "firestore-collection";
12828
13429
  var WRITE_METHODS = /* @__PURE__ */ new Set(["write", "create", "update"]);
12829
13430
  function stripComments(src) {
@@ -12963,7 +13564,7 @@ async function addFirestoreRules(graph, services) {
12963
13564
  if (guards.size === 0) return { nodesAdded: 0, edgesAdded: 0 };
12964
13565
  graph.forEachNode((id, attrs) => {
12965
13566
  const node = attrs;
12966
- if (node.type !== NodeType22.InfraNode) return;
13567
+ if (node.type !== NodeType25.InfraNode) return;
12967
13568
  if (node.kind !== FIRESTORE_COLLECTION_KIND) return;
12968
13569
  const fields = guards.get(collectionKeyFromName(node.name));
12969
13570
  if (!fields || fields.size === 0) return;
@@ -12976,16 +13577,16 @@ async function addFirestoreRules(graph, services) {
12976
13577
  }
12977
13578
 
12978
13579
  // src/extract/index.ts
12979
- import path57 from "path";
13580
+ import path60 from "path";
12980
13581
 
12981
13582
  // src/extract/retire.ts
12982
13583
  import { existsSync as existsSync2 } from "fs";
12983
- import path56 from "path";
12984
- import { NodeType as NodeType23, Provenance as Provenance23 } from "@neat.is/types";
13584
+ import path59 from "path";
13585
+ import { NodeType as NodeType26, Provenance as Provenance23 } from "@neat.is/types";
12985
13586
  function dropOrphanedFileNodes(graph) {
12986
13587
  const orphans = [];
12987
13588
  graph.forEachNode((id, attrs) => {
12988
- if (attrs.type !== NodeType23.FileNode) return;
13589
+ if (attrs.type !== NodeType26.FileNode) return;
12989
13590
  if (graph.inboundEdges(id).length === 0 && graph.outboundEdges(id).length === 0) {
12990
13591
  orphans.push(id);
12991
13592
  }
@@ -13014,11 +13615,11 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
13014
13615
  if (edge.provenance !== Provenance23.EXTRACTED) return;
13015
13616
  const evidenceFile = edge.evidence?.file;
13016
13617
  if (!evidenceFile) return;
13017
- if (path56.isAbsolute(evidenceFile)) {
13618
+ if (path59.isAbsolute(evidenceFile)) {
13018
13619
  if (!existsSync2(evidenceFile)) toDrop.push(id);
13019
13620
  return;
13020
13621
  }
13021
- const found = bases.some((base) => existsSync2(path56.join(base, evidenceFile)));
13622
+ const found = bases.some((base) => existsSync2(path59.join(base, evidenceFile)));
13022
13623
  if (!found) toDrop.push(id);
13023
13624
  });
13024
13625
  for (const id of toDrop) graph.dropEdge(id);
@@ -13075,7 +13676,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
13075
13676
  }
13076
13677
  const droppedEntries = drainDroppedExtracted();
13077
13678
  if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
13078
- const rejectedPath = path57.join(path57.dirname(opts.errorsPath), "rejected.ndjson");
13679
+ const rejectedPath = path60.join(path60.dirname(opts.errorsPath), "rejected.ndjson");
13079
13680
  try {
13080
13681
  await writeRejectedExtracted(droppedEntries, rejectedPath);
13081
13682
  } catch (err) {
@@ -13112,11 +13713,11 @@ import {
13112
13713
  databaseId as databaseId3,
13113
13714
  DivergenceResultSchema,
13114
13715
  EdgeType as EdgeType25,
13115
- NodeType as NodeType24,
13716
+ NodeType as NodeType27,
13116
13717
  parseEdgeId,
13117
13718
  parseFileId,
13118
13719
  Provenance as Provenance24,
13119
- serviceId as serviceId7
13720
+ serviceId as serviceId10
13120
13721
  } from "@neat.is/types";
13121
13722
  function bucketKey(source, target, type) {
13122
13723
  return `${type}|${source}|${target}`;
@@ -13126,8 +13727,8 @@ function bucketSourceFor(graph, edge) {
13126
13727
  const parsed = parseFileId(edge.source);
13127
13728
  if (!parsed || !graph.hasNode(edge.target)) return edge.source;
13128
13729
  const target = graph.getNodeAttributes(edge.target);
13129
- if (target.type !== NodeType24.DatabaseNode) return edge.source;
13130
- return serviceId7(parsed.service);
13730
+ if (target.type !== NodeType27.DatabaseNode) return edge.source;
13731
+ return serviceId10(parsed.service);
13131
13732
  }
13132
13733
  function bucketEdges(graph) {
13133
13734
  const buckets2 = /* @__PURE__ */ new Map();
@@ -13158,22 +13759,22 @@ function bucketEdges(graph) {
13158
13759
  function nodeIsFrontier(graph, nodeId) {
13159
13760
  if (!graph.hasNode(nodeId)) return false;
13160
13761
  const attrs = graph.getNodeAttributes(nodeId);
13161
- return attrs.type === NodeType24.FrontierNode;
13762
+ return attrs.type === NodeType27.FrontierNode;
13162
13763
  }
13163
13764
  function nodeIsWebsocketChannel(graph, nodeId) {
13164
13765
  if (!graph.hasNode(nodeId)) return false;
13165
13766
  const attrs = graph.getNodeAttributes(nodeId);
13166
- return attrs.type === NodeType24.WebSocketChannelNode;
13767
+ return attrs.type === NodeType27.WebSocketChannelNode;
13167
13768
  }
13168
13769
  function nodeIsServerAction(graph, nodeId) {
13169
13770
  if (!graph.hasNode(nodeId)) return false;
13170
13771
  const attrs = graph.getNodeAttributes(nodeId);
13171
- return attrs.type === NodeType24.ServerActionNode;
13772
+ return attrs.type === NodeType27.ServerActionNode;
13172
13773
  }
13173
13774
  function nodeIsSymbol(graph, nodeId) {
13174
13775
  if (!graph.hasNode(nodeId)) return false;
13175
13776
  const attrs = graph.getNodeAttributes(nodeId);
13176
- return attrs.type === NodeType24.SymbolNode;
13777
+ return attrs.type === NodeType27.SymbolNode;
13177
13778
  }
13178
13779
  function clampConfidence(n) {
13179
13780
  if (!Number.isFinite(n)) return 0;
@@ -13258,7 +13859,7 @@ function detectHostMismatch(graph, svcId, svc) {
13258
13859
  if (edge.type !== EdgeType25.CONNECTS_TO) continue;
13259
13860
  if (edge.provenance !== Provenance24.OBSERVED) continue;
13260
13861
  const target = graph.getNodeAttributes(edge.target);
13261
- if (target.type !== NodeType24.DatabaseNode) continue;
13862
+ if (target.type !== NodeType27.DatabaseNode) continue;
13262
13863
  const observedHost = target.host?.trim();
13263
13864
  if (!observedHost) continue;
13264
13865
  if (observedHost === declaredHost) continue;
@@ -13283,7 +13884,7 @@ function detectCompatDivergences(graph, svcId, svc) {
13283
13884
  if (edge.type !== EdgeType25.CONNECTS_TO) continue;
13284
13885
  if (edge.provenance !== Provenance24.OBSERVED) continue;
13285
13886
  const target = graph.getNodeAttributes(edge.target);
13286
- if (target.type !== NodeType24.DatabaseNode) continue;
13887
+ if (target.type !== NodeType27.DatabaseNode) continue;
13287
13888
  for (const pair of compatPairs()) {
13288
13889
  if (pair.engine !== target.engine) continue;
13289
13890
  const declared = deps[pair.driver];
@@ -13399,13 +14000,13 @@ function computeDivergences(graph, opts = {}) {
13399
14000
  }
13400
14001
  graph.forEachNode((nodeId, attrs) => {
13401
14002
  const n = attrs;
13402
- if (n.type === NodeType24.ServiceNode) {
14003
+ if (n.type === NodeType27.ServiceNode) {
13403
14004
  const svc = n;
13404
14005
  for (const d of detectHostMismatch(graph, nodeId, svc)) all.push(d);
13405
14006
  for (const d of detectCompatDivergences(graph, nodeId, svc)) all.push(d);
13406
14007
  return;
13407
14008
  }
13408
- if (n.type === NodeType24.InfraNode && n.kind === "sql-table") {
14009
+ if (n.type === NodeType27.InfraNode && n.kind === "sql-table") {
13409
14010
  for (const d of detectColumnDrift(n)) all.push(d);
13410
14011
  }
13411
14012
  });
@@ -13449,9 +14050,9 @@ function computeDivergences(graph, opts = {}) {
13449
14050
  }
13450
14051
 
13451
14052
  // src/persist.ts
13452
- import { promises as fs25 } from "fs";
13453
- import path58 from "path";
13454
- import { NodeType as NodeType25, Provenance as Provenance25, observedEdgeId as observedEdgeId2 } from "@neat.is/types";
14053
+ import { promises as fs28 } from "fs";
14054
+ import path61 from "path";
14055
+ import { NodeType as NodeType28, Provenance as Provenance25, observedEdgeId as observedEdgeId2 } from "@neat.is/types";
13455
14056
  var SCHEMA_VERSION = 7;
13456
14057
  function migrateV1ToV2(payload) {
13457
14058
  const nodes = payload.graph.nodes;
@@ -13475,7 +14076,7 @@ function migrateV5ToV6(payload) {
13475
14076
  if (Array.isArray(nodes)) {
13476
14077
  for (const node of nodes) {
13477
14078
  const attrs = node.attributes;
13478
- if (!attrs || attrs.type !== NodeType25.InfraNode) continue;
14079
+ if (!attrs || attrs.type !== NodeType28.InfraNode) continue;
13479
14080
  if (attrs.kind !== "sql-table" && attrs.kind !== "supabase-table") continue;
13480
14081
  if (!Array.isArray(attrs.columns)) attrs.columns = [];
13481
14082
  }
@@ -13505,7 +14106,7 @@ function migrateV2ToV3(payload) {
13505
14106
  return { ...payload, schemaVersion: 3 };
13506
14107
  }
13507
14108
  async function ensureDir(filePath) {
13508
- await fs25.mkdir(path58.dirname(filePath), { recursive: true });
14109
+ await fs28.mkdir(path61.dirname(filePath), { recursive: true });
13509
14110
  }
13510
14111
  async function saveGraphToDisk(graph, outPath) {
13511
14112
  await ensureDir(outPath);
@@ -13515,13 +14116,13 @@ async function saveGraphToDisk(graph, outPath) {
13515
14116
  graph: graph.export()
13516
14117
  };
13517
14118
  const tmp = `${outPath}.tmp`;
13518
- await fs25.writeFile(tmp, JSON.stringify(payload), "utf8");
13519
- await fs25.rename(tmp, outPath);
14119
+ await fs28.writeFile(tmp, JSON.stringify(payload), "utf8");
14120
+ await fs28.rename(tmp, outPath);
13520
14121
  }
13521
14122
  async function loadGraphFromDisk(graph, outPath) {
13522
14123
  let raw;
13523
14124
  try {
13524
- raw = await fs25.readFile(outPath, "utf8");
14125
+ raw = await fs28.readFile(outPath, "utf8");
13525
14126
  } catch (err) {
13526
14127
  if (err.code === "ENOENT") return;
13527
14128
  throw err;
@@ -13594,7 +14195,7 @@ function startPersistLoop(graph, outPath, opts = {}) {
13594
14195
  }
13595
14196
 
13596
14197
  // src/diff.ts
13597
- import { promises as fs26 } from "fs";
14198
+ import { promises as fs29 } from "fs";
13598
14199
  async function loadSnapshotForDiff(target) {
13599
14200
  if (/^https?:\/\//i.test(target)) {
13600
14201
  const res = await fetch(target);
@@ -13603,7 +14204,7 @@ async function loadSnapshotForDiff(target) {
13603
14204
  }
13604
14205
  return await res.json();
13605
14206
  }
13606
- const raw = await fs26.readFile(target, "utf8");
14207
+ const raw = await fs29.readFile(target, "utf8");
13607
14208
  return JSON.parse(raw);
13608
14209
  }
13609
14210
  function indexEntries(entries) {
@@ -13670,23 +14271,23 @@ function canonicalJson(value) {
13670
14271
  }
13671
14272
 
13672
14273
  // src/projects.ts
13673
- import path59 from "path";
14274
+ import path62 from "path";
13674
14275
  function pathsForProject(project, baseDir) {
13675
14276
  if (project === DEFAULT_PROJECT) {
13676
14277
  return {
13677
- snapshotPath: path59.join(baseDir, "graph.json"),
13678
- errorsPath: path59.join(baseDir, "errors.ndjson"),
13679
- staleEventsPath: path59.join(baseDir, "stale-events.ndjson"),
13680
- embeddingsCachePath: path59.join(baseDir, "embeddings.json"),
13681
- policyViolationsPath: path59.join(baseDir, "policy-violations.ndjson")
14278
+ snapshotPath: path62.join(baseDir, "graph.json"),
14279
+ errorsPath: path62.join(baseDir, "errors.ndjson"),
14280
+ staleEventsPath: path62.join(baseDir, "stale-events.ndjson"),
14281
+ embeddingsCachePath: path62.join(baseDir, "embeddings.json"),
14282
+ policyViolationsPath: path62.join(baseDir, "policy-violations.ndjson")
13682
14283
  };
13683
14284
  }
13684
14285
  return {
13685
- snapshotPath: path59.join(baseDir, `${project}.json`),
13686
- errorsPath: path59.join(baseDir, `errors.${project}.ndjson`),
13687
- staleEventsPath: path59.join(baseDir, `stale-events.${project}.ndjson`),
13688
- embeddingsCachePath: path59.join(baseDir, `embeddings.${project}.json`),
13689
- policyViolationsPath: path59.join(baseDir, `policy-violations.${project}.ndjson`)
14286
+ snapshotPath: path62.join(baseDir, `${project}.json`),
14287
+ errorsPath: path62.join(baseDir, `errors.${project}.ndjson`),
14288
+ staleEventsPath: path62.join(baseDir, `stale-events.${project}.ndjson`),
14289
+ embeddingsCachePath: path62.join(baseDir, `embeddings.${project}.json`),
14290
+ policyViolationsPath: path62.join(baseDir, `policy-violations.${project}.ndjson`)
13690
14291
  };
13691
14292
  }
13692
14293
  var Projects = class {
@@ -13725,9 +14326,9 @@ function parseExtraProjects(raw) {
13725
14326
  }
13726
14327
 
13727
14328
  // src/registry.ts
13728
- import { promises as fs27 } from "fs";
14329
+ import { promises as fs30 } from "fs";
13729
14330
  import os2 from "os";
13730
- import path60 from "path";
14331
+ import path63 from "path";
13731
14332
  import {
13732
14333
  RegistryFileSchema
13733
14334
  } from "@neat.is/types";
@@ -13735,20 +14336,20 @@ var LOCK_TIMEOUT_MS = 5e3;
13735
14336
  var LOCK_RETRY_MS = 50;
13736
14337
  function neatHome() {
13737
14338
  const override = process.env.NEAT_HOME;
13738
- if (override && override.length > 0) return path60.resolve(override);
13739
- return path60.join(os2.homedir(), ".neat");
14339
+ if (override && override.length > 0) return path63.resolve(override);
14340
+ return path63.join(os2.homedir(), ".neat");
13740
14341
  }
13741
14342
  function registryPath() {
13742
- return path60.join(neatHome(), "projects.json");
14343
+ return path63.join(neatHome(), "projects.json");
13743
14344
  }
13744
14345
  function registryLockPath() {
13745
- return path60.join(neatHome(), "projects.json.lock");
14346
+ return path63.join(neatHome(), "projects.json.lock");
13746
14347
  }
13747
14348
  function daemonPidPath() {
13748
- return path60.join(neatHome(), "neatd.pid");
14349
+ return path63.join(neatHome(), "neatd.pid");
13749
14350
  }
13750
14351
  function daemonsDir() {
13751
- return path60.join(neatHome(), "daemons");
14352
+ return path63.join(neatHome(), "daemons");
13752
14353
  }
13753
14354
  function isFiniteInt(v) {
13754
14355
  return typeof v === "number" && Number.isFinite(v);
@@ -13781,7 +14382,7 @@ async function discoverDaemons(probe = defaultDiscoveryProbe) {
13781
14382
  const dir = daemonsDir();
13782
14383
  let names;
13783
14384
  try {
13784
- names = await fs27.readdir(dir);
14385
+ names = await fs30.readdir(dir);
13785
14386
  } catch (err) {
13786
14387
  if (err.code === "ENOENT") return [];
13787
14388
  throw err;
@@ -13789,10 +14390,10 @@ async function discoverDaemons(probe = defaultDiscoveryProbe) {
13789
14390
  const out = [];
13790
14391
  for (const name of names) {
13791
14392
  if (!name.endsWith(".json")) continue;
13792
- const file = path60.join(dir, name);
14393
+ const file = path63.join(dir, name);
13793
14394
  let raw;
13794
14395
  try {
13795
- raw = await fs27.readFile(file, "utf8");
14396
+ raw = await fs30.readFile(file, "utf8");
13796
14397
  } catch {
13797
14398
  continue;
13798
14399
  }
@@ -13805,7 +14406,7 @@ async function discoverDaemons(probe = defaultDiscoveryProbe) {
13805
14406
  return out;
13806
14407
  }
13807
14408
  async function removeDaemonRecord(source) {
13808
- await fs27.unlink(source).catch(() => {
14409
+ await fs30.unlink(source).catch(() => {
13809
14410
  });
13810
14411
  }
13811
14412
  async function listMachineProjects(probe = defaultDiscoveryProbe) {
@@ -13862,7 +14463,7 @@ function isPidAliveDefault(pid) {
13862
14463
  }
13863
14464
  async function readPidFile(file) {
13864
14465
  try {
13865
- const raw = await fs27.readFile(file, "utf8");
14466
+ const raw = await fs30.readFile(file, "utf8");
13866
14467
  const pid = Number.parseInt(raw.trim(), 10);
13867
14468
  return Number.isInteger(pid) && pid > 0 ? pid : void 0;
13868
14469
  } catch {
@@ -13910,32 +14511,32 @@ function lockHolderMessage(holder, lockPath, timeoutMs) {
13910
14511
  }
13911
14512
  }
13912
14513
  async function normalizeProjectPath(input) {
13913
- const resolved = path60.resolve(input);
14514
+ const resolved = path63.resolve(input);
13914
14515
  try {
13915
- return await fs27.realpath(resolved);
14516
+ return await fs30.realpath(resolved);
13916
14517
  } catch {
13917
14518
  return resolved;
13918
14519
  }
13919
14520
  }
13920
14521
  async function writeAtomically(target, contents) {
13921
- await fs27.mkdir(path60.dirname(target), { recursive: true });
14522
+ await fs30.mkdir(path63.dirname(target), { recursive: true });
13922
14523
  const tmp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
13923
- const fd = await fs27.open(tmp, "w");
14524
+ const fd = await fs30.open(tmp, "w");
13924
14525
  try {
13925
14526
  await fd.writeFile(contents, "utf8");
13926
14527
  await fd.sync();
13927
14528
  } finally {
13928
14529
  await fd.close();
13929
14530
  }
13930
- await fs27.rename(tmp, target);
14531
+ await fs30.rename(tmp, target);
13931
14532
  }
13932
14533
  async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaultLockHolderProbe) {
13933
14534
  const deadline = Date.now() + timeoutMs;
13934
- await fs27.mkdir(path60.dirname(lockPath), { recursive: true });
14535
+ await fs30.mkdir(path63.dirname(lockPath), { recursive: true });
13935
14536
  let probedHolder = false;
13936
14537
  while (true) {
13937
14538
  try {
13938
- const fd = await fs27.open(lockPath, "wx");
14539
+ const fd = await fs30.open(lockPath, "wx");
13939
14540
  try {
13940
14541
  await fd.writeFile(`${process.pid}
13941
14542
  `, "utf8");
@@ -13960,7 +14561,7 @@ async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaul
13960
14561
  }
13961
14562
  }
13962
14563
  async function releaseLock(lockPath) {
13963
- await fs27.unlink(lockPath).catch(() => {
14564
+ await fs30.unlink(lockPath).catch(() => {
13964
14565
  });
13965
14566
  }
13966
14567
  async function withLock(fn) {
@@ -13976,7 +14577,7 @@ async function readRegistry() {
13976
14577
  const file = registryPath();
13977
14578
  let raw;
13978
14579
  try {
13979
- raw = await fs27.readFile(file, "utf8");
14580
+ raw = await fs30.readFile(file, "utf8");
13980
14581
  } catch (err) {
13981
14582
  if (err.code === "ENOENT") {
13982
14583
  return { version: 1, projects: [] };
@@ -14081,7 +14682,7 @@ function pruneTtlMs() {
14081
14682
  }
14082
14683
  async function statPathStatus(p) {
14083
14684
  try {
14084
- const stat = await fs27.stat(p);
14685
+ const stat = await fs30.stat(p);
14085
14686
  return stat.isDirectory() ? "present" : "unknown";
14086
14687
  } catch (err) {
14087
14688
  return err.code === "ENOENT" ? "gone" : "unknown";
@@ -14126,14 +14727,14 @@ import cors from "@fastify/cors";
14126
14727
  import { DivergenceTypeSchema, PoliciesCheckBodySchema, PolicySeveritySchema } from "@neat.is/types";
14127
14728
 
14128
14729
  // src/extend/index.ts
14129
- import { promises as fs29 } from "fs";
14130
- import path62 from "path";
14730
+ import { promises as fs32 } from "fs";
14731
+ import path65 from "path";
14131
14732
  import os3 from "os";
14132
14733
  import { resolve as registryResolve, list as registryList } from "@neat.is/instrumentation-registry";
14133
14734
 
14134
14735
  // src/installers/package-manager.ts
14135
- import { promises as fs28 } from "fs";
14136
- import path61 from "path";
14736
+ import { promises as fs31 } from "fs";
14737
+ import path64 from "path";
14137
14738
  import { spawn } from "child_process";
14138
14739
  var LOCKFILE_PRIORITY = [
14139
14740
  { lockfile: "bun.lockb", pm: "bun", args: ["install", "--no-summary"] },
@@ -14148,29 +14749,29 @@ var LOCKFILE_PRIORITY = [
14148
14749
  var NPM_FALLBACK_ARGS = ["install", "--no-audit", "--no-fund", "--prefer-offline"];
14149
14750
  async function exists2(p) {
14150
14751
  try {
14151
- await fs28.access(p);
14752
+ await fs31.access(p);
14152
14753
  return true;
14153
14754
  } catch {
14154
14755
  return false;
14155
14756
  }
14156
14757
  }
14157
14758
  async function detectPackageManager(serviceDir) {
14158
- let dir = path61.resolve(serviceDir);
14759
+ let dir = path64.resolve(serviceDir);
14159
14760
  const stops = /* @__PURE__ */ new Set();
14160
14761
  for (let i = 0; i < 64; i++) {
14161
14762
  if (stops.has(dir)) break;
14162
14763
  stops.add(dir);
14163
14764
  for (const candidate of LOCKFILE_PRIORITY) {
14164
- const lockPath = path61.join(dir, candidate.lockfile);
14765
+ const lockPath = path64.join(dir, candidate.lockfile);
14165
14766
  if (await exists2(lockPath)) {
14166
14767
  return { pm: candidate.pm, cwd: dir, args: [...candidate.args] };
14167
14768
  }
14168
14769
  }
14169
- const parent = path61.dirname(dir);
14770
+ const parent = path64.dirname(dir);
14170
14771
  if (parent === dir) break;
14171
14772
  dir = parent;
14172
14773
  }
14173
- return { pm: "npm", cwd: path61.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
14774
+ return { pm: "npm", cwd: path64.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
14174
14775
  }
14175
14776
  async function runPackageManagerInstall(cmd) {
14176
14777
  return new Promise((resolve) => {
@@ -14212,15 +14813,15 @@ ${err.message}`
14212
14813
  // src/extend/index.ts
14213
14814
  async function fileExists2(p) {
14214
14815
  try {
14215
- await fs29.access(p);
14816
+ await fs32.access(p);
14216
14817
  return true;
14217
14818
  } catch {
14218
14819
  return false;
14219
14820
  }
14220
14821
  }
14221
14822
  async function readPackageJson(scanPath) {
14222
- const pkgPath = path62.join(scanPath, "package.json");
14223
- const raw = await fs29.readFile(pkgPath, "utf8");
14823
+ const pkgPath = path65.join(scanPath, "package.json");
14824
+ const raw = await fs32.readFile(pkgPath, "utf8");
14224
14825
  return JSON.parse(raw);
14225
14826
  }
14226
14827
  var HOOK_WALK_SKIP_DIRS = /* @__PURE__ */ new Set([
@@ -14234,15 +14835,15 @@ var HOOK_WALK_SKIP_DIRS = /* @__PURE__ */ new Set([
14234
14835
  async function findHookFiles(scanPath) {
14235
14836
  const found = [];
14236
14837
  const walk9 = async (dir) => {
14237
- const entries = await fs29.readdir(dir, { withFileTypes: true }).catch(() => []);
14838
+ const entries = await fs32.readdir(dir, { withFileTypes: true }).catch(() => []);
14238
14839
  for (const entry of entries) {
14239
14840
  if (entry.isDirectory()) {
14240
14841
  if (entry.name.startsWith(".") || HOOK_WALK_SKIP_DIRS.has(entry.name)) continue;
14241
- await walk9(path62.join(dir, entry.name));
14842
+ await walk9(path65.join(dir, entry.name));
14242
14843
  } else if (entry.isFile()) {
14243
14844
  if ((entry.name.startsWith("instrumentation") || entry.name.startsWith("otel-init")) && /\.(ts|js|cjs|mjs)$/.test(entry.name)) {
14244
- const rel = path62.relative(scanPath, path62.join(dir, entry.name));
14245
- found.push(rel.split(path62.sep).join("/"));
14845
+ const rel = path65.relative(scanPath, path65.join(dir, entry.name));
14846
+ found.push(rel.split(path65.sep).join("/"));
14246
14847
  }
14247
14848
  }
14248
14849
  }
@@ -14253,7 +14854,7 @@ async function findHookFiles(scanPath) {
14253
14854
  async function pickPrimaryHookFile(scanPath, hookFiles, snippet2) {
14254
14855
  let fallback = null;
14255
14856
  for (const file of hookFiles) {
14256
- const content = await fs29.readFile(path62.join(scanPath, file), "utf8");
14857
+ const content = await fs32.readFile(path65.join(scanPath, file), "utf8");
14257
14858
  const patched = splicedContent(content, snippet2);
14258
14859
  if (patched !== null) return { file, content, patched };
14259
14860
  if (fallback === null) fallback = { file, content };
@@ -14261,12 +14862,12 @@ async function pickPrimaryHookFile(scanPath, hookFiles, snippet2) {
14261
14862
  return { file: fallback.file, content: fallback.content, patched: null };
14262
14863
  }
14263
14864
  function extendLogPath() {
14264
- return process.env.NEAT_EXTEND_LOG ?? path62.join(os3.homedir(), ".neat", "extend-log.ndjson");
14865
+ return process.env.NEAT_EXTEND_LOG ?? path65.join(os3.homedir(), ".neat", "extend-log.ndjson");
14265
14866
  }
14266
14867
  async function appendExtendLog(entry) {
14267
14868
  const logPath = extendLogPath();
14268
- await fs29.mkdir(path62.dirname(logPath), { recursive: true });
14269
- await fs29.appendFile(logPath, JSON.stringify(entry) + "\n", "utf8");
14869
+ await fs32.mkdir(path65.dirname(logPath), { recursive: true });
14870
+ await fs32.appendFile(logPath, JSON.stringify(entry) + "\n", "utf8");
14270
14871
  }
14271
14872
  function splicedContent(fileContent, snippet2) {
14272
14873
  if (fileContent.includes("__INSTRUMENTATION_BLOCK__")) {
@@ -14324,7 +14925,7 @@ function lookupInstrumentation(library, installedVersion) {
14324
14925
  }
14325
14926
  async function describeProjectInstrumentation(ctx) {
14326
14927
  const hookFiles = await findHookFiles(ctx.scanPath);
14327
- const envNeat = await fileExists2(path62.join(ctx.scanPath, ".env.neat"));
14928
+ const envNeat = await fileExists2(path65.join(ctx.scanPath, ".env.neat"));
14328
14929
  const registryInstrPackages = new Set(
14329
14930
  registryList().map((e) => e.instrumentation_package).filter((p) => !!p)
14330
14931
  );
@@ -14346,7 +14947,7 @@ async function applyExtension(ctx, args, options) {
14346
14947
  );
14347
14948
  }
14348
14949
  for (const file of hookFiles) {
14349
- const content = await fs29.readFile(path62.join(ctx.scanPath, file), "utf8");
14950
+ const content = await fs32.readFile(path65.join(ctx.scanPath, file), "utf8");
14350
14951
  if (content.includes(args.registration_snippet)) {
14351
14952
  return { library: args.library, filesTouched: [], depsAdded: [], installOutput: "", alreadyApplied: true };
14352
14953
  }
@@ -14358,18 +14959,18 @@ async function applyExtension(ctx, args, options) {
14358
14959
  );
14359
14960
  }
14360
14961
  const primaryFile = primary.file;
14361
- const primaryPath = path62.join(ctx.scanPath, primaryFile);
14962
+ const primaryPath = path65.join(ctx.scanPath, primaryFile);
14362
14963
  const filesTouched = [];
14363
14964
  const depsAdded = [];
14364
- const pkgPath = path62.join(ctx.scanPath, "package.json");
14965
+ const pkgPath = path65.join(ctx.scanPath, "package.json");
14365
14966
  const pkg = await readPackageJson(ctx.scanPath);
14366
14967
  if (!(pkg.dependencies ?? {})[args.instrumentation_package]) {
14367
14968
  pkg.dependencies = { ...pkg.dependencies ?? {}, [args.instrumentation_package]: args.version };
14368
- await fs29.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
14969
+ await fs32.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
14369
14970
  filesTouched.push("package.json");
14370
14971
  depsAdded.push(`${args.instrumentation_package}@${args.version}`);
14371
14972
  }
14372
- await fs29.writeFile(primaryPath, primary.patched, "utf8");
14973
+ await fs32.writeFile(primaryPath, primary.patched, "utf8");
14373
14974
  filesTouched.push(primaryFile);
14374
14975
  const cmd = await detectPackageManager(ctx.scanPath);
14375
14976
  const installer = options?.runInstall ?? runPackageManagerInstall;
@@ -14400,7 +15001,7 @@ async function dryRunExtension(ctx, args) {
14400
15001
  };
14401
15002
  }
14402
15003
  for (const file of hookFiles) {
14403
- const content = await fs29.readFile(path62.join(ctx.scanPath, file), "utf8");
15004
+ const content = await fs32.readFile(path65.join(ctx.scanPath, file), "utf8");
14404
15005
  if (content.includes(args.registration_snippet)) {
14405
15006
  return {
14406
15007
  library: args.library,
@@ -14435,28 +15036,28 @@ async function rollbackExtension(ctx, args) {
14435
15036
  if (!await fileExists2(logPath)) {
14436
15037
  return { undone: false, message: "no apply found for library" };
14437
15038
  }
14438
- const raw = await fs29.readFile(logPath, "utf8");
15039
+ const raw = await fs32.readFile(logPath, "utf8");
14439
15040
  const entries = raw.trim().split("\n").filter(Boolean).map((line) => JSON.parse(line));
14440
15041
  const match = [...entries].reverse().find((e) => e.project === ctx.project && e.library === args.library);
14441
15042
  if (!match) {
14442
15043
  return { undone: false, message: "no apply found for library" };
14443
15044
  }
14444
- const pkgPath = path62.join(ctx.scanPath, "package.json");
15045
+ const pkgPath = path65.join(ctx.scanPath, "package.json");
14445
15046
  if (await fileExists2(pkgPath)) {
14446
15047
  const pkg = await readPackageJson(ctx.scanPath);
14447
15048
  if (pkg.dependencies?.[match.instrumentation_package]) {
14448
15049
  const { [match.instrumentation_package]: _removed, ...rest } = pkg.dependencies;
14449
15050
  pkg.dependencies = rest;
14450
- await fs29.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
15051
+ await fs32.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
14451
15052
  }
14452
15053
  }
14453
15054
  const hookFiles = await findHookFiles(ctx.scanPath);
14454
15055
  for (const file of hookFiles) {
14455
- const filePath = path62.join(ctx.scanPath, file);
14456
- const content = await fs29.readFile(filePath, "utf8");
15056
+ const filePath = path65.join(ctx.scanPath, file);
15057
+ const content = await fs32.readFile(filePath, "utf8");
14457
15058
  if (content.includes(match.registration_snippet)) {
14458
15059
  const filtered = content.split("\n").filter((line) => !line.includes(match.registration_snippet)).join("\n");
14459
- await fs29.writeFile(filePath, filtered, "utf8");
15060
+ await fs32.writeFile(filePath, filtered, "utf8");
14460
15061
  break;
14461
15062
  }
14462
15063
  }
@@ -14567,8 +15168,8 @@ data: ${JSON.stringify(envelope.payload)}
14567
15168
 
14568
15169
  // src/connectors-config.ts
14569
15170
  import os4 from "os";
14570
- import path63 from "path";
14571
- import { promises as fs30 } from "fs";
15171
+ import path66 from "path";
15172
+ import { promises as fs33 } from "fs";
14572
15173
  var CONNECTORS_CONFIG_VERSION = 1;
14573
15174
  var EnvRefUnsetError = class extends Error {
14574
15175
  ref;
@@ -14582,17 +15183,17 @@ var EnvRefUnsetError = class extends Error {
14582
15183
  };
14583
15184
  function neatHome2() {
14584
15185
  const override = process.env.NEAT_HOME;
14585
- if (override && override.length > 0) return path63.resolve(override);
14586
- return path63.join(os4.homedir(), ".neat");
15186
+ if (override && override.length > 0) return path66.resolve(override);
15187
+ return path66.join(os4.homedir(), ".neat");
14587
15188
  }
14588
15189
  function connectorsConfigPath(home = neatHome2()) {
14589
- return path63.join(home, "connectors.json");
15190
+ return path66.join(home, "connectors.json");
14590
15191
  }
14591
15192
  var MODE_MASK_LOOSER_THAN_0600 = 63;
14592
15193
  async function warnIfModeLooserThan0600(file) {
14593
15194
  if (process.platform === "win32") return;
14594
15195
  try {
14595
- const stat = await fs30.stat(file);
15196
+ const stat = await fs33.stat(file);
14596
15197
  if ((stat.mode & MODE_MASK_LOOSER_THAN_0600) !== 0) {
14597
15198
  const mode = (stat.mode & 511).toString(8).padStart(3, "0");
14598
15199
  console.warn(
@@ -14606,7 +15207,7 @@ async function readConnectorsConfig(home = neatHome2()) {
14606
15207
  const file = connectorsConfigPath(home);
14607
15208
  let raw;
14608
15209
  try {
14609
- raw = await fs30.readFile(file, "utf8");
15210
+ raw = await fs33.readFile(file, "utf8");
14610
15211
  } catch (err) {
14611
15212
  if (err.code === "ENOENT") {
14612
15213
  return { version: CONNECTORS_CONFIG_VERSION, connectors: [] };
@@ -14717,7 +15318,7 @@ function connectorMatchesProject(entry, project) {
14717
15318
  var CONNECTORS_LOCK_TIMEOUT_MS = 5e3;
14718
15319
  var CONNECTORS_LOCK_RETRY_MS = 50;
14719
15320
  function connectorsConfigLockPath(home = neatHome2()) {
14720
- return path63.join(home, "connectors.json.lock");
15321
+ return path66.join(home, "connectors.json.lock");
14721
15322
  }
14722
15323
  function isEnvRef(value) {
14723
15324
  return value.length > 1 && value.startsWith("$");
@@ -14730,9 +15331,9 @@ function redactCredentialRef(ref) {
14730
15331
  return out;
14731
15332
  }
14732
15333
  async function writeConfigAtomically0600(file, contents) {
14733
- await fs30.mkdir(path63.dirname(file), { recursive: true });
15334
+ await fs33.mkdir(path66.dirname(file), { recursive: true });
14734
15335
  const tmp = `${file}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
14735
- const fd = await fs30.open(tmp, "w", 384);
15336
+ const fd = await fs33.open(tmp, "w", 384);
14736
15337
  try {
14737
15338
  await fd.writeFile(contents, "utf8");
14738
15339
  await fd.chmod(384);
@@ -14740,14 +15341,14 @@ async function writeConfigAtomically0600(file, contents) {
14740
15341
  } finally {
14741
15342
  await fd.close();
14742
15343
  }
14743
- await fs30.rename(tmp, file);
15344
+ await fs33.rename(tmp, file);
14744
15345
  }
14745
15346
  async function acquireConnectorsLock(lockPath, timeoutMs = CONNECTORS_LOCK_TIMEOUT_MS) {
14746
15347
  const deadline = Date.now() + timeoutMs;
14747
- await fs30.mkdir(path63.dirname(lockPath), { recursive: true });
15348
+ await fs33.mkdir(path66.dirname(lockPath), { recursive: true });
14748
15349
  for (; ; ) {
14749
15350
  try {
14750
- const fd = await fs30.open(lockPath, "wx");
15351
+ const fd = await fs33.open(lockPath, "wx");
14751
15352
  try {
14752
15353
  await fd.writeFile(`${process.pid}
14753
15354
  `, "utf8");
@@ -14767,7 +15368,7 @@ async function acquireConnectorsLock(lockPath, timeoutMs = CONNECTORS_LOCK_TIMEO
14767
15368
  }
14768
15369
  }
14769
15370
  async function releaseConnectorsLock(lockPath) {
14770
- await fs30.unlink(lockPath).catch(() => {
15371
+ await fs33.unlink(lockPath).catch(() => {
14771
15372
  });
14772
15373
  }
14773
15374
  async function withConnectorsLock(home, fn) {
@@ -14892,7 +15493,7 @@ function getConnectorStatus(id, now = Date.now(), thresholdMs = CONNECTOR_STALE_
14892
15493
  }
14893
15494
 
14894
15495
  // src/connectors/index.ts
14895
- import { NodeType as NodeType26, parseFileId as parseFileId2, Provenance as Provenance26 } from "@neat.is/types";
15496
+ import { NodeType as NodeType29, parseFileId as parseFileId2, Provenance as Provenance26 } from "@neat.is/types";
14896
15497
  var NO_ENV = "unknown";
14897
15498
  function staticCallSiteFor(graph, serviceName, targetNodeId) {
14898
15499
  if (!graph.hasNode(targetNodeId)) return void 0;
@@ -14911,7 +15512,7 @@ function staticCallSiteFor(graph, serviceName, targetNodeId) {
14911
15512
  function routeCallSiteFor(graph, targetNodeId) {
14912
15513
  if (!graph.hasNode(targetNodeId)) return void 0;
14913
15514
  const attrs = graph.getNodeAttributes(targetNodeId);
14914
- if (attrs.type !== NodeType26.RouteNode || !attrs.path) return void 0;
15515
+ if (attrs.type !== NodeType29.RouteNode || !attrs.path) return void 0;
14915
15516
  const site = { relPath: attrs.path };
14916
15517
  if (attrs.line !== void 0) site.line = attrs.line;
14917
15518
  return site;
@@ -15396,10 +15997,10 @@ var SUPABASE_RPC_TARGET_KIND = "supabase-rpc";
15396
15997
  // src/connectors/supabase/map.ts
15397
15998
  var REST_RPC_PATH_RE = /^\/rest\/v1\/rpc\/([^/?]+)/;
15398
15999
  var REST_TABLE_PATH_RE = /^\/rest\/v1\/([^/?]+)/;
15399
- function targetFromRestPath(path64) {
15400
- const rpcMatch = REST_RPC_PATH_RE.exec(path64);
16000
+ function targetFromRestPath(path67) {
16001
+ const rpcMatch = REST_RPC_PATH_RE.exec(path67);
15401
16002
  if (rpcMatch) return { targetKind: SUPABASE_RPC_TARGET_KIND, name: rpcMatch[1] };
15402
- const tableMatch = REST_TABLE_PATH_RE.exec(path64);
16003
+ const tableMatch = REST_TABLE_PATH_RE.exec(path67);
15403
16004
  if (tableMatch) return { targetKind: SUPABASE_TABLE_TARGET_KIND, name: tableMatch[1] };
15404
16005
  return null;
15405
16006
  }
@@ -15616,7 +16217,7 @@ function createSupabaseConnector(graph, config, deps = {}) {
15616
16217
  }
15617
16218
 
15618
16219
  // src/connectors/railway/index.ts
15619
- import { EdgeType as EdgeType27, NodeType as NodeType27, serviceId as serviceId8 } from "@neat.is/types";
16220
+ import { EdgeType as EdgeType27, NodeType as NodeType30, serviceId as serviceId11 } from "@neat.is/types";
15620
16221
 
15621
16222
  // src/connectors/railway/client.ts
15622
16223
  var DEFAULT_RAILWAY_API_URL = "https://backboard.railway.com/graphql/v2";
@@ -15766,7 +16367,7 @@ function buildRailwayRouteIndex(graph, serviceName) {
15766
16367
  const out = [];
15767
16368
  graph.forEachNode((_id, attrs) => {
15768
16369
  const node = attrs;
15769
- if (node.type !== NodeType27.RouteNode) return;
16370
+ if (node.type !== NodeType30.RouteNode) return;
15770
16371
  const route = attrs;
15771
16372
  if (route.service !== serviceName) return;
15772
16373
  out.push({
@@ -15875,7 +16476,7 @@ function createRailwayResolveTarget(config) {
15875
16476
  if (signal.targetKind === PEER_SERVICE_TARGET_KIND) {
15876
16477
  const peerName = config.serviceNameById[signal.targetName];
15877
16478
  if (!peerName) return null;
15878
- return { targetNodeId: serviceId8(peerName), serviceName, edgeType: EdgeType27.CONNECTS_TO };
16479
+ return { targetNodeId: serviceId11(peerName), serviceName, edgeType: EdgeType27.CONNECTS_TO };
15879
16480
  }
15880
16481
  return null;
15881
16482
  };
@@ -15994,9 +16595,9 @@ function parseFirebaseTargetName(targetName) {
15994
16595
  const secondSep = rest.indexOf(FIELD_SEP);
15995
16596
  if (secondSep === -1) return null;
15996
16597
  const method = rest.slice(0, secondSep);
15997
- const path64 = rest.slice(secondSep + 1);
15998
- if (!resourceName || !method || !path64) return null;
15999
- return { resourceName, method, path: path64 };
16598
+ const path67 = rest.slice(secondSep + 1);
16599
+ if (!resourceName || !method || !path67) return null;
16600
+ return { resourceName, method, path: path67 };
16000
16601
  }
16001
16602
  function resourceNameFor(type, labels) {
16002
16603
  if (!labels) return null;
@@ -16034,14 +16635,14 @@ function mapLogEntryToSignal(entry) {
16034
16635
  if (!req) return null;
16035
16636
  if (typeof req.requestMethod !== "string" || req.requestMethod.length === 0) return null;
16036
16637
  const method = req.requestMethod.toUpperCase();
16037
- const path64 = pathFromRequestUrl(req.requestUrl);
16038
- if (path64 === null) return null;
16638
+ const path67 = pathFromRequestUrl(req.requestUrl);
16639
+ if (path67 === null) return null;
16039
16640
  const timestamp = entry.timestamp;
16040
16641
  if (typeof timestamp !== "string" || timestamp.length === 0) return null;
16041
16642
  const isError = typeof req.status === "number" && req.status >= ERROR_STATUS_THRESHOLD2;
16042
16643
  return {
16043
16644
  targetKind: resourceType,
16044
- targetName: packFirebaseTargetName({ resourceName, method, path: path64 }),
16645
+ targetName: packFirebaseTargetName({ resourceName, method, path: path67 }),
16045
16646
  callCount: 1,
16046
16647
  errorCount: isError ? 1 : 0,
16047
16648
  lastObservedIso: timestamp
@@ -16057,7 +16658,7 @@ function mapLogEntriesToSignals(entries) {
16057
16658
  }
16058
16659
 
16059
16660
  // src/connectors/firebase/resolve.ts
16060
- import { NodeType as NodeType28, EdgeType as EdgeType28 } from "@neat.is/types";
16661
+ import { NodeType as NodeType31, EdgeType as EdgeType28 } from "@neat.is/types";
16061
16662
  function neatServiceNameFor(resourceType, resourceName, serviceMap) {
16062
16663
  switch (resourceType) {
16063
16664
  case "cloud_function":
@@ -16072,7 +16673,7 @@ function routeEntriesFor(graph, serviceName) {
16072
16673
  const entries = [];
16073
16674
  graph.forEachNode((_id, attrs) => {
16074
16675
  const node = attrs;
16075
- if (node.type !== NodeType28.RouteNode) return;
16676
+ if (node.type !== NodeType31.RouteNode) return;
16076
16677
  const route = attrs;
16077
16678
  if (route.service !== serviceName) return;
16078
16679
  entries.push({
@@ -16127,7 +16728,7 @@ function createFirebaseConnector(graph, serviceMap) {
16127
16728
  }
16128
16729
 
16129
16730
  // src/connectors/cloudflare/connector.ts
16130
- import { EdgeType as EdgeType29, NodeType as NodeType29, fileId as fileId5, infraId as infraId21 } from "@neat.is/types";
16731
+ import { EdgeType as EdgeType29, NodeType as NodeType32, fileId as fileId5, infraId as infraId21 } from "@neat.is/types";
16131
16732
 
16132
16733
  // src/connectors/cloudflare/client.ts
16133
16734
  import { randomUUID } from "crypto";
@@ -16238,7 +16839,7 @@ function mapEventToSignal(event) {
16238
16839
  if (Number.isNaN(observedAt.getTime())) return null;
16239
16840
  const statusCode = metadata?.statusCode;
16240
16841
  const isError = typeof statusCode === "number" && statusCode >= ERROR_STATUS_THRESHOLD3;
16241
- const path64 = metadata?.trigger ? parsePathFromTrigger(metadata.trigger) : void 0;
16842
+ const path67 = metadata?.trigger ? parsePathFromTrigger(metadata.trigger) : void 0;
16242
16843
  return {
16243
16844
  targetKind: CLOUDFLARE_TARGET_KIND,
16244
16845
  targetName: scriptName,
@@ -16246,7 +16847,7 @@ function mapEventToSignal(event) {
16246
16847
  errorCount: isError ? 1 : 0,
16247
16848
  lastObservedIso: observedAt.toISOString(),
16248
16849
  method,
16249
- ...path64 ? { path: path64 } : {},
16850
+ ...path67 ? { path: path67 } : {},
16250
16851
  ...typeof statusCode === "number" ? { statusCode } : {},
16251
16852
  ...typeof metadata?.duration === "number" ? { duration: metadata.duration } : {}
16252
16853
  };
@@ -16286,19 +16887,19 @@ function findTaggedWorkerFileNode(graph, workerName) {
16286
16887
  graph.forEachNode((id, attrs) => {
16287
16888
  if (found) return;
16288
16889
  const a = attrs;
16289
- if (a.type === NodeType29.FileNode && a.platform === "cloudflare" && a.platformName === workerName) {
16890
+ if (a.type === NodeType32.FileNode && a.platform === "cloudflare" && a.platformName === workerName) {
16290
16891
  found = id;
16291
16892
  }
16292
16893
  });
16293
16894
  return found;
16294
16895
  }
16295
- function findMatchingRouteNode(graph, serviceName, method, path64) {
16296
- const normalizedPath = normalizePathTemplate(path64);
16896
+ function findMatchingRouteNode(graph, serviceName, method, path67) {
16897
+ const normalizedPath = normalizePathTemplate(path67);
16297
16898
  let found = null;
16298
16899
  graph.forEachNode((id, attrs) => {
16299
16900
  if (found) return;
16300
16901
  const a = attrs;
16301
- if (a.type !== NodeType29.RouteNode || a.service !== serviceName) return;
16902
+ if (a.type !== NodeType32.RouteNode || a.service !== serviceName) return;
16302
16903
  if (!a.pathTemplate || normalizePathTemplate(a.pathTemplate) !== normalizedPath) return;
16303
16904
  const routeMethod = (a.method ?? "").toUpperCase();
16304
16905
  if (routeMethod !== "ALL" && routeMethod !== method) return;
@@ -16310,10 +16911,10 @@ function createCloudflareResolveTarget(config, graph) {
16310
16911
  return (signal) => {
16311
16912
  if (signal.targetKind !== CLOUDFLARE_TARGET_KIND) return null;
16312
16913
  const scriptName = signal.targetName;
16313
- const { method, path: path64 } = signal;
16914
+ const { method, path: path67 } = signal;
16314
16915
  const resolveRouteGrain = (serviceName, wholeFileId) => {
16315
- if (!method || !path64) return wholeFileId;
16316
- return findMatchingRouteNode(graph, serviceName, method, path64) ?? wholeFileId;
16916
+ if (!method || !path67) return wholeFileId;
16917
+ return findMatchingRouteNode(graph, serviceName, method, path67) ?? wholeFileId;
16317
16918
  };
16318
16919
  const mapping = config.workers?.[scriptName];
16319
16920
  if (mapping) {
@@ -16643,9 +17244,9 @@ function parseCloudRunTargetName(targetName) {
16643
17244
  const secondSep = rest.indexOf(FIELD_SEP2);
16644
17245
  if (secondSep === -1) return null;
16645
17246
  const method = rest.slice(0, secondSep);
16646
- const path64 = rest.slice(secondSep + 1);
16647
- if (!serviceName || !method || !path64) return null;
16648
- return { serviceName, method, path: path64 };
17247
+ const path67 = rest.slice(secondSep + 1);
17248
+ if (!serviceName || !method || !path67) return null;
17249
+ return { serviceName, method, path: path67 };
16649
17250
  }
16650
17251
 
16651
17252
  // src/connectors/cloud-run/map.ts
@@ -16674,14 +17275,14 @@ function mapLogEntryToSignal2(entry) {
16674
17275
  if (!req) return null;
16675
17276
  if (typeof req.requestMethod !== "string" || req.requestMethod.length === 0) return null;
16676
17277
  const method = req.requestMethod.toUpperCase();
16677
- const path64 = pathFromRequestUrl2(req.requestUrl);
16678
- if (path64 === null) return null;
17278
+ const path67 = pathFromRequestUrl2(req.requestUrl);
17279
+ if (path67 === null) return null;
16679
17280
  const timestamp = entry.timestamp;
16680
17281
  if (typeof timestamp !== "string" || timestamp.length === 0) return null;
16681
17282
  const isError = typeof req.status === "number" && req.status >= ERROR_STATUS_THRESHOLD4;
16682
17283
  return {
16683
17284
  targetKind: CLOUD_RUN_TARGET_KIND,
16684
- targetName: packCloudRunTargetName({ serviceName, method, path: path64 }),
17285
+ targetName: packCloudRunTargetName({ serviceName, method, path: path67 }),
16685
17286
  callCount: 1,
16686
17287
  errorCount: isError ? 1 : 0,
16687
17288
  lastObservedIso: timestamp
@@ -16697,14 +17298,14 @@ function mapLogEntriesToSignals2(entries) {
16697
17298
  }
16698
17299
 
16699
17300
  // src/connectors/cloud-run/resolve.ts
16700
- import { EdgeType as EdgeType31, NodeType as NodeType30, infraId as infraId23 } from "@neat.is/types";
17301
+ import { EdgeType as EdgeType31, NodeType as NodeType33, infraId as infraId23 } from "@neat.is/types";
16701
17302
  var CLOUD_RUN_SERVICE_INFRA_KIND = "cloud-run-service";
16702
17303
  function findMatchingRouteNode2(graph, serviceName, method, normalizedPath) {
16703
17304
  let found = null;
16704
17305
  graph.forEachNode((_id, attrs) => {
16705
17306
  if (found) return;
16706
17307
  const node = attrs;
16707
- if (node.type !== NodeType30.RouteNode) return;
17308
+ if (node.type !== NodeType33.RouteNode) return;
16708
17309
  const route = attrs;
16709
17310
  if (route.service !== serviceName || !route.pathTemplate) return;
16710
17311
  if (normalizePathTemplate(route.pathTemplate) !== normalizedPath) return;
@@ -16719,14 +17320,14 @@ function createCloudRunResolveTarget(graph, config) {
16719
17320
  if (signal.targetKind !== CLOUD_RUN_TARGET_KIND) return null;
16720
17321
  const identity = parseCloudRunTargetName(signal.targetName);
16721
17322
  if (!identity) return null;
16722
- const { serviceName: gcpServiceName, method, path: path64 } = identity;
17323
+ const { serviceName: gcpServiceName, method, path: path67 } = identity;
16723
17324
  const mappedService = config.serviceMap?.[gcpServiceName];
16724
17325
  if (mappedService) {
16725
17326
  const routeNodeId = findMatchingRouteNode2(
16726
17327
  graph,
16727
17328
  mappedService,
16728
17329
  method,
16729
- normalizePathTemplate(path64)
17330
+ normalizePathTemplate(path67)
16730
17331
  );
16731
17332
  if (routeNodeId) {
16732
17333
  return { targetNodeId: routeNodeId, serviceName: mappedService, edgeType: EdgeType31.CALLS };
@@ -16775,7 +17376,7 @@ function createCloudRunConnector(graph, config = {}) {
16775
17376
  }
16776
17377
 
16777
17378
  // src/connectors/render/index.ts
16778
- import { EdgeType as EdgeType32, NodeType as NodeType31 } from "@neat.is/types";
17379
+ import { EdgeType as EdgeType32, NodeType as NodeType34 } from "@neat.is/types";
16779
17380
 
16780
17381
  // src/connectors/render/types.ts
16781
17382
  function readRenderToken(credentials) {
@@ -16851,7 +17452,7 @@ function buildRenderRouteIndex(graph, serviceName) {
16851
17452
  const out = [];
16852
17453
  graph.forEachNode((_id, attrs) => {
16853
17454
  const node = attrs;
16854
- if (node.type !== NodeType31.RouteNode) return;
17455
+ if (node.type !== NodeType34.RouteNode) return;
16855
17456
  const route = attrs;
16856
17457
  if (route.service !== serviceName) return;
16857
17458
  out.push({
@@ -17335,7 +17936,7 @@ function mapBuildsToSignals(builds, serviceName) {
17335
17936
  }
17336
17937
 
17337
17938
  // src/connectors/eas/resolve.ts
17338
- import { EdgeType as EdgeType34, NodeType as NodeType32, parseFileId as parseFileId3 } from "@neat.is/types";
17939
+ import { EdgeType as EdgeType34, NodeType as NodeType35, parseFileId as parseFileId3 } from "@neat.is/types";
17339
17940
  var NO_ENV2 = "unknown";
17340
17941
  var EAS_JSON_PHASES = /* @__PURE__ */ new Set(["READ_EAS_JSON"]);
17341
17942
  var APP_CONFIG_PHASES = /* @__PURE__ */ new Set([
@@ -17363,7 +17964,7 @@ function findConfigNode(graph, basenames, serviceName) {
17363
17964
  graph.forEachNode((id, attrs) => {
17364
17965
  if (scoped) return;
17365
17966
  const node = attrs;
17366
- if (node.type !== NodeType32.ConfigNode) return;
17967
+ if (node.type !== NodeType35.ConfigNode) return;
17367
17968
  if (typeof node.name !== "string" || !basenames.includes(node.name)) return;
17368
17969
  if (anyMatch === null) anyMatch = id;
17369
17970
  if (configNodeService(graph, id) === serviceName) scoped = id;
@@ -18975,4 +19576,4 @@ export {
18975
19576
  deprovisionConnector,
18976
19577
  buildApi
18977
19578
  };
18978
- //# sourceMappingURL=chunk-ZGZYZ33B.js.map
19579
+ //# sourceMappingURL=chunk-T4UGEMWN.js.map