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

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", ".cs", ".java"]);
964
+ var SERVICE_FILE_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".mjs", ".cjs", ".ts", ".tsx", ".py", ".go", ".rb", ".php", ".cs", ".java", ".kt", ".rs", ".cpp", ".cc", ".cxx", ".c++", ".hpp", ".hh", ".hxx", ".h++"]);
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([
@@ -1247,7 +1247,8 @@ function buildServiceHostIndex(services) {
1247
1247
  }
1248
1248
  return { knownHosts, hostToNodeId };
1249
1249
  }
1250
- async function walkSourceFiles(dir) {
1250
+ async function walkSourceFiles(dir, excludeDirs = []) {
1251
+ const excluded = new Set(excludeDirs.map((d) => path5.resolve(d)));
1251
1252
  const out = [];
1252
1253
  async function walk9(current) {
1253
1254
  const entries = await fs5.readdir(current, { withFileTypes: true }).catch(() => []);
@@ -1255,6 +1256,7 @@ async function walkSourceFiles(dir) {
1255
1256
  const full = path5.join(current, entry.name);
1256
1257
  if (entry.isDirectory()) {
1257
1258
  if (IGNORED_DIRS.has(entry.name)) continue;
1259
+ if (excluded.has(path5.resolve(full))) continue;
1258
1260
  if (await isPythonVenvDir(full)) continue;
1259
1261
  await walk9(full);
1260
1262
  } else if (entry.isFile() && SERVICE_FILE_EXTENSIONS.has(path5.extname(entry.name)) && // Skip NEAT's own generated `otel-init.*` bootstrap — extracting it
@@ -1267,8 +1269,8 @@ async function walkSourceFiles(dir) {
1267
1269
  await walk9(dir);
1268
1270
  return out;
1269
1271
  }
1270
- async function loadSourceFiles(dir) {
1271
- const paths = await walkSourceFiles(dir);
1272
+ async function loadSourceFiles(dir, excludeDirs = []) {
1273
+ const paths = await walkSourceFiles(dir, excludeDirs);
1272
1274
  const out = [];
1273
1275
  for (const p of paths) {
1274
1276
  try {
@@ -1305,6 +1307,19 @@ function languageForPath(relPath) {
1305
1307
  return "csharp";
1306
1308
  case ".java":
1307
1309
  return "java";
1310
+ case ".kt":
1311
+ return "kotlin";
1312
+ case ".rs":
1313
+ return "rust";
1314
+ case ".cpp":
1315
+ case ".cc":
1316
+ case ".cxx":
1317
+ case ".c++":
1318
+ case ".hpp":
1319
+ case ".hh":
1320
+ case ".hxx":
1321
+ case ".h++":
1322
+ return "cpp";
1308
1323
  case ".ts":
1309
1324
  case ".tsx":
1310
1325
  return "typescript";
@@ -1643,6 +1658,7 @@ async function resolveGoImport(specifier, modulePath, serviceDir) {
1643
1658
  function emitImportEdge(graph, serviceName, importerFileId, importerRelPath, importeeRelPath, line, snippet2) {
1644
1659
  const importeeFileId = fileId2(serviceName, importeeRelPath);
1645
1660
  if (!graph.hasNode(importeeFileId)) return 0;
1661
+ if (importerFileId === importeeFileId) return 0;
1646
1662
  const edgeId = extractedEdgeId3(importerFileId, importeeFileId, EdgeType3.IMPORTS);
1647
1663
  if (graph.hasEdge(edgeId)) return 0;
1648
1664
  const edge = {
@@ -1664,7 +1680,7 @@ async function addImports(graph, services) {
1664
1680
  let edgesAdded = 0;
1665
1681
  for (const service of services) {
1666
1682
  const tsPaths = await loadTsPathConfig(service.dir);
1667
- const files = await loadSourceFiles(service.dir);
1683
+ const files = await loadSourceFiles(service.dir, service.excludeDirs);
1668
1684
  for (const file of files) {
1669
1685
  if (isTestPath(file.path)) continue;
1670
1686
  const relFile = toPosix(path6.relative(service.dir, file.path));
@@ -1846,8 +1862,8 @@ function chiRoutesFromSource(source, parser) {
1846
1862
  chiWalk(tree.rootNode, "", out);
1847
1863
  return out;
1848
1864
  }
1849
- function stripChiRegex(path67) {
1850
- return path67.replace(/\{([^{}:]+):[^{}]*\}/g, "{$1}");
1865
+ function stripChiRegex(path70) {
1866
+ return path70.replace(/\{([^{}:]+):[^{}]*\}/g, "{$1}");
1851
1867
  }
1852
1868
  function chiWalk(node, prefix, out) {
1853
1869
  for (let i = 0; i < node.namedChildCount; i++) {
@@ -2519,9 +2535,9 @@ function rubyRocketRoute(args) {
2519
2535
  if (!pair || pair.type !== "pair") continue;
2520
2536
  const k = pair.childForFieldName("key");
2521
2537
  if (k?.type !== "string") continue;
2522
- const path67 = rubyLiteral(k);
2523
- if (path67 === null) continue;
2524
- return { path: path67, target: rubyLiteral(pair.childForFieldName("value")) };
2538
+ const path70 = rubyLiteral(k);
2539
+ if (path70 === null) continue;
2540
+ return { path: path70, target: rubyLiteral(pair.childForFieldName("value")) };
2525
2541
  }
2526
2542
  return null;
2527
2543
  }
@@ -3206,7 +3222,7 @@ async function addRoutes(graph, services) {
3206
3222
  const hasLaravel = deps["laravel/framework"] !== void 0;
3207
3223
  if (!hasExpress && !hasFastify && !hasHono && !hasNext && !hasNestjs && !hasFastapi && !hasFlask && !hasDjango && !hasGin && !hasEcho && !hasFiber && !hasChi && !isGoService && !hasRails && !hasLaravel)
3208
3224
  continue;
3209
- const files = await loadSourceFiles(service.dir);
3225
+ const files = await loadSourceFiles(service.dir, service.excludeDirs);
3210
3226
  const mountPrefixes = hasExpress ? await expressMountPrefixes(files, service.dir, await loadTsPathConfig(service.dir)) : /* @__PURE__ */ new Map();
3211
3227
  for (const file of files) {
3212
3228
  if (isTestPath(file.path)) continue;
@@ -4638,29 +4654,29 @@ function promoteFrontierNodes(graph, opts = {}) {
4638
4654
  toPromote.push({ frontierId: id, serviceId: target });
4639
4655
  });
4640
4656
  let promoted = 0;
4641
- for (const { frontierId: frontierId2, serviceId: serviceId12 } of toPromote) {
4657
+ for (const { frontierId: frontierId2, serviceId: serviceId15 } of toPromote) {
4642
4658
  if (opts.policies && opts.policies.length > 0 && opts.policyCtx) {
4643
4659
  const gate = canPromoteFrontier(graph, frontierId2, opts.policies, opts.policyCtx);
4644
4660
  if (!gate.allowed) {
4645
4661
  continue;
4646
4662
  }
4647
4663
  }
4648
- rewireFrontierEdges(graph, frontierId2, serviceId12);
4664
+ rewireFrontierEdges(graph, frontierId2, serviceId15);
4649
4665
  graph.dropNode(frontierId2);
4650
4666
  promoted++;
4651
4667
  }
4652
4668
  return promoted;
4653
4669
  }
4654
- function rewireFrontierEdges(graph, frontierId2, serviceId12) {
4670
+ function rewireFrontierEdges(graph, frontierId2, serviceId15) {
4655
4671
  const inbound = [...graph.inboundEdges(frontierId2)];
4656
4672
  const outbound = [...graph.outboundEdges(frontierId2)];
4657
4673
  for (const edgeId of inbound) {
4658
4674
  const edge = graph.getEdgeAttributes(edgeId);
4659
- rebuildEdge(graph, edge, edge.source, serviceId12, edgeId);
4675
+ rebuildEdge(graph, edge, edge.source, serviceId15, edgeId);
4660
4676
  }
4661
4677
  for (const edgeId of outbound) {
4662
4678
  const edge = graph.getEdgeAttributes(edgeId);
4663
- rebuildEdge(graph, edge, serviceId12, edge.target, edgeId);
4679
+ rebuildEdge(graph, edge, serviceId15, edge.target, edgeId);
4664
4680
  }
4665
4681
  }
4666
4682
  function rebuildEdge(graph, edge, newSource, newTarget, oldEdgeId) {
@@ -4992,19 +5008,19 @@ function confidenceFromMix(edges, now = Date.now()) {
4992
5008
  function longestIncomingWalk(graph, start, maxDepth) {
4993
5009
  let best = { path: [start], edges: [] };
4994
5010
  const visited = /* @__PURE__ */ new Set([start]);
4995
- function step(node, path67, edges) {
4996
- if (path67.length > best.path.length) {
4997
- best = { path: [...path67], edges: [...edges] };
5011
+ function step(node, path70, edges) {
5012
+ if (path70.length > best.path.length) {
5013
+ best = { path: [...path70], edges: [...edges] };
4998
5014
  }
4999
- if (path67.length - 1 >= maxDepth) return;
5015
+ if (path70.length - 1 >= maxDepth) return;
5000
5016
  const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
5001
5017
  for (const [srcId, edge] of incoming) {
5002
5018
  if (visited.has(srcId)) continue;
5003
5019
  visited.add(srcId);
5004
- path67.push(srcId);
5020
+ path70.push(srcId);
5005
5021
  edges.push(edge);
5006
- step(srcId, path67, edges);
5007
- path67.pop();
5022
+ step(srcId, path70, edges);
5023
+ path70.pop();
5008
5024
  edges.pop();
5009
5025
  visited.delete(srcId);
5010
5026
  }
@@ -5019,7 +5035,7 @@ function databaseRootCauseShape(graph, origin, walk9) {
5019
5035
  for (const id of walk9.path) {
5020
5036
  const owner = resolveOwningService(graph, id);
5021
5037
  if (!owner) continue;
5022
- const { id: serviceId12, svc } = owner;
5038
+ const { id: serviceId15, svc } = owner;
5023
5039
  const deps = svc.dependencies ?? {};
5024
5040
  for (const pair of candidatePairs) {
5025
5041
  const declared = deps[pair.driver];
@@ -5032,7 +5048,7 @@ function databaseRootCauseShape(graph, origin, walk9) {
5032
5048
  );
5033
5049
  if (!result.compatible) {
5034
5050
  return {
5035
- rootCauseNode: serviceId12,
5051
+ rootCauseNode: serviceId15,
5036
5052
  rootCauseReason: result.reason ?? "incompatible driver",
5037
5053
  ...result.minDriverVersion ? {
5038
5054
  fixRecommendation: `Upgrade ${svc.name} ${pair.driver} driver to >= ${result.minDriverVersion}`
@@ -5047,7 +5063,7 @@ function serviceRootCauseShape(graph, _origin, walk9) {
5047
5063
  for (const id of walk9.path) {
5048
5064
  const owner = resolveOwningService(graph, id);
5049
5065
  if (!owner) continue;
5050
- const { id: serviceId12, svc } = owner;
5066
+ const { id: serviceId15, svc } = owner;
5051
5067
  const deps = svc.dependencies ?? {};
5052
5068
  const serviceNodeEngine = svc.nodeEngine;
5053
5069
  for (const constraint of nodeEngineConstraints()) {
@@ -5056,7 +5072,7 @@ function serviceRootCauseShape(graph, _origin, walk9) {
5056
5072
  const result = checkNodeEngineConstraint(constraint, declared, serviceNodeEngine);
5057
5073
  if (!result.compatible && result.reason) {
5058
5074
  return {
5059
- rootCauseNode: serviceId12,
5075
+ rootCauseNode: serviceId15,
5060
5076
  rootCauseReason: result.reason,
5061
5077
  ...result.requiredNodeVersion ? {
5062
5078
  fixRecommendation: `Bump ${svc.name}'s engines.node to >= ${result.requiredNodeVersion}`
@@ -5071,7 +5087,7 @@ function serviceRootCauseShape(graph, _origin, walk9) {
5071
5087
  const result = checkPackageConflict(conflict, declared, requiredDeclared);
5072
5088
  if (!result.compatible && result.reason) {
5073
5089
  return {
5074
- rootCauseNode: serviceId12,
5090
+ rootCauseNode: serviceId15,
5075
5091
  rootCauseReason: result.reason,
5076
5092
  fixRecommendation: `Upgrade ${svc.name}'s ${conflict.requires.name} to >= ${conflict.requires.minVersion}`
5077
5093
  };
@@ -5172,9 +5188,9 @@ function rootCauseFromIncidents(nodeId, incidents, errorEvent) {
5172
5188
  function isFailingCallEdge(e) {
5173
5189
  return e.type === EdgeType6.CALLS && (e.signal?.errorCount ?? 0) > 0;
5174
5190
  }
5175
- function callSourcesForService(graph, serviceId12) {
5176
- const ids = [serviceId12];
5177
- for (const edgeId of graph.outboundEdges(serviceId12)) {
5191
+ function callSourcesForService(graph, serviceId15) {
5192
+ const ids = [serviceId15];
5193
+ for (const edgeId of graph.outboundEdges(serviceId15)) {
5178
5194
  const e = graph.getEdgeAttributes(edgeId);
5179
5195
  if (e.type !== EdgeType6.CONTAINS) continue;
5180
5196
  const tgt = graph.getNodeAttributes(e.target);
@@ -5198,9 +5214,9 @@ function failingCallDominates(e, id, curEdge, curId) {
5198
5214
  }
5199
5215
  return id < curId;
5200
5216
  }
5201
- function dominantFailingCall(graph, serviceId12, visited) {
5217
+ function dominantFailingCall(graph, serviceId15, visited) {
5202
5218
  let best = null;
5203
- for (const src of callSourcesForService(graph, serviceId12)) {
5219
+ for (const src of callSourcesForService(graph, serviceId15)) {
5204
5220
  for (const edgeId of graph.outboundEdges(src)) {
5205
5221
  const e = graph.getEdgeAttributes(edgeId);
5206
5222
  if (!isFailingCallEdge(e)) continue;
@@ -5215,26 +5231,26 @@ function dominantFailingCall(graph, serviceId12, visited) {
5215
5231
  return best;
5216
5232
  }
5217
5233
  function followFailingCallChain(graph, originServiceId, maxDepth) {
5218
- const path67 = [originServiceId];
5234
+ const path70 = [originServiceId];
5219
5235
  const edges = [];
5220
5236
  const visited = /* @__PURE__ */ new Set([originServiceId]);
5221
5237
  let current = originServiceId;
5222
5238
  for (let depth = 0; depth < maxDepth; depth++) {
5223
5239
  const hop = dominantFailingCall(graph, current, visited);
5224
5240
  if (!hop) break;
5225
- path67.push(hop.nextService);
5241
+ path70.push(hop.nextService);
5226
5242
  edges.push(hop.edge);
5227
5243
  visited.add(hop.nextService);
5228
5244
  current = hop.nextService;
5229
5245
  }
5230
5246
  if (edges.length === 0) return null;
5231
- return { path: path67, edges, culprit: current };
5247
+ return { path: path70, edges, culprit: current };
5232
5248
  }
5233
5249
  function crossServiceRootCause(graph, originId, incidents, errorEvent) {
5234
5250
  const chain = followFailingCallChain(graph, originId, ROOT_CAUSE_MAX_DEPTH);
5235
5251
  if (!chain) return null;
5236
5252
  const culprit = chain.culprit;
5237
- const path67 = [...chain.path];
5253
+ const path70 = [...chain.path];
5238
5254
  const edgeProvenances = chain.edges.map((e) => e.provenance);
5239
5255
  const baseConfidence = confidenceFromMix(chain.edges);
5240
5256
  const confidence = Math.max(0, Math.min(1, baseConfidence * INCIDENT_ROOT_CAUSE_CONFIDENCE));
@@ -5242,14 +5258,14 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
5242
5258
  if (loc) {
5243
5259
  let rootCauseNode = culprit;
5244
5260
  if (loc.fileNode) {
5245
- path67.push(loc.fileNode);
5261
+ path70.push(loc.fileNode);
5246
5262
  edgeProvenances.push(Provenance6.OBSERVED);
5247
5263
  rootCauseNode = loc.fileNode;
5248
5264
  }
5249
5265
  return RootCauseResultSchema.parse({
5250
5266
  rootCauseNode,
5251
5267
  rootCauseReason: loc.rootCauseReason,
5252
- traversalPath: path67,
5268
+ traversalPath: path70,
5253
5269
  edgeProvenances,
5254
5270
  confidence,
5255
5271
  ...loc.fixRecommendation ? { fixRecommendation: loc.fixRecommendation } : {}
@@ -5261,7 +5277,7 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
5261
5277
  return RootCauseResultSchema.parse({
5262
5278
  rootCauseNode: culprit,
5263
5279
  rootCauseReason: `${culpritName} is failing downstream calls (${errs} observed error${errs === 1 ? "" : "s"})`,
5264
- traversalPath: path67,
5280
+ traversalPath: path70,
5265
5281
  edgeProvenances,
5266
5282
  confidence,
5267
5283
  fixRecommendation: `Inspect ${culpritName}'s failing handler`
@@ -5697,10 +5713,10 @@ function enrichWithNavigation(graph, errorNodeId, legacy, incidents, now) {
5697
5713
  let traversalPath = legacy.traversalPath;
5698
5714
  let edgeProvenances = legacy.edgeProvenances;
5699
5715
  if (top.node !== seedNode) {
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);
5716
+ const path70 = findPath(graph, errorNodeId, top.node, "up", ROOT_CAUSE_MAX_DEPTH);
5717
+ if (path70) {
5718
+ traversalPath = path70.nodes;
5719
+ edgeProvenances = path70.edges.map((e) => e.provenance);
5704
5720
  } else {
5705
5721
  traversalPath = [errorNodeId, top.node];
5706
5722
  edgeProvenances = [top.provenance ?? Provenance6.OBSERVED];
@@ -5729,11 +5745,11 @@ function fixRecommendationForTop(top, seedNode, legacy) {
5729
5745
  }
5730
5746
 
5731
5747
  // src/extract/services.ts
5732
- import { promises as fs15 } from "fs";
5733
- import path17 from "path";
5748
+ import { promises as fs18 } from "fs";
5749
+ import path20 from "path";
5734
5750
  import ignore from "ignore";
5735
5751
  import { minimatch as minimatch2 } from "minimatch";
5736
- import { NodeType as NodeType12, serviceId as serviceId8 } from "@neat.is/types";
5752
+ import { NodeType as NodeType15, serviceId as serviceId11 } from "@neat.is/types";
5737
5753
 
5738
5754
  // src/extract/python.ts
5739
5755
  import { promises as fs8 } from "fs";
@@ -6052,10 +6068,154 @@ async function discoverJavaService(scanPath, dir) {
6052
6068
  return { pkg, dir, node };
6053
6069
  }
6054
6070
 
6055
- // src/extract/dockerfile-service.ts
6071
+ // src/extract/kotlin.ts
6056
6072
  import { promises as fs13 } from "fs";
6057
6073
  import path15 from "path";
6058
6074
  import { NodeType as NodeType11, serviceId as serviceId7 } from "@neat.is/types";
6075
+ var KOTLIN_SOURCE_EXT = ".kt";
6076
+ var JAVA_SOURCE_EXT = ".java";
6077
+ var SOURCE_PROBE_MAX_DEPTH = 8;
6078
+ async function dominantJvmSourceLanguage(dir) {
6079
+ let kt = 0;
6080
+ let java = 0;
6081
+ async function recurse(current, depth) {
6082
+ if (depth > SOURCE_PROBE_MAX_DEPTH) return;
6083
+ const entries = await fs13.readdir(current, { withFileTypes: true }).catch(() => []);
6084
+ for (const entry of entries) {
6085
+ if (entry.isDirectory()) {
6086
+ if (IGNORED_DIRS.has(entry.name)) continue;
6087
+ await recurse(path15.join(current, entry.name), depth + 1);
6088
+ continue;
6089
+ }
6090
+ if (!entry.isFile()) continue;
6091
+ const ext = path15.extname(entry.name).toLowerCase();
6092
+ if (ext === KOTLIN_SOURCE_EXT) kt++;
6093
+ else if (ext === JAVA_SOURCE_EXT) java++;
6094
+ }
6095
+ }
6096
+ await recurse(dir, 0);
6097
+ if (kt > 0 && kt >= java) return "kotlin";
6098
+ if (java > 0) return "java";
6099
+ return void 0;
6100
+ }
6101
+ async function readJvmManifest(dir) {
6102
+ const pomPath = path15.join(dir, JAVA_MANIFESTS[0]);
6103
+ const gradlePath = path15.join(dir, JAVA_MANIFESTS[1]);
6104
+ const gradleKtsPath = path15.join(dir, JAVA_MANIFESTS[2]);
6105
+ if (await exists(pomPath)) {
6106
+ return parsePomXml(await fs13.readFile(pomPath, "utf8").catch(() => ""));
6107
+ }
6108
+ if (await exists(gradlePath)) {
6109
+ return parseGradle(await fs13.readFile(gradlePath, "utf8").catch(() => ""));
6110
+ }
6111
+ if (await exists(gradleKtsPath)) {
6112
+ return parseGradle(await fs13.readFile(gradleKtsPath, "utf8").catch(() => ""));
6113
+ }
6114
+ return null;
6115
+ }
6116
+ async function discoverKotlinService(scanPath, dir) {
6117
+ if (!await hasJavaManifest(dir)) return null;
6118
+ if (await dominantJvmSourceLanguage(dir) !== "kotlin") return null;
6119
+ const manifest = await readJvmManifest(dir);
6120
+ if (!manifest) return null;
6121
+ const name = manifest.name?.trim() || path15.basename(dir);
6122
+ const dependencies = manifest.dependencies;
6123
+ const pkg = { name, dependencies };
6124
+ const node = {
6125
+ id: serviceId7(name),
6126
+ type: NodeType11.ServiceNode,
6127
+ name,
6128
+ language: "kotlin",
6129
+ dependencies,
6130
+ repoPath: path15.relative(scanPath, dir)
6131
+ };
6132
+ return { pkg, dir, node };
6133
+ }
6134
+
6135
+ // src/extract/rust.ts
6136
+ import { promises as fs14 } from "fs";
6137
+ import path16 from "path";
6138
+ import { parse as parseToml2 } from "smol-toml";
6139
+ import { NodeType as NodeType12, serviceId as serviceId8 } from "@neat.is/types";
6140
+ var CARGO_MANIFEST = "Cargo.toml";
6141
+ async function hasRustManifest(dir) {
6142
+ return exists(path16.join(dir, CARGO_MANIFEST));
6143
+ }
6144
+ function parseCargoToml(source) {
6145
+ const dependencies = {};
6146
+ let name;
6147
+ let parsed;
6148
+ try {
6149
+ parsed = parseToml2(source);
6150
+ } catch {
6151
+ return { dependencies };
6152
+ }
6153
+ if (typeof parsed.package?.name === "string") name = parsed.package.name;
6154
+ for (const [crate, spec] of Object.entries(parsed.dependencies ?? {})) {
6155
+ if (typeof spec === "string") {
6156
+ dependencies[crate] = spec;
6157
+ } else if (spec && typeof spec === "object") {
6158
+ const version = spec.version;
6159
+ dependencies[crate] = typeof version === "string" ? version : "";
6160
+ } else {
6161
+ dependencies[crate] = "";
6162
+ }
6163
+ }
6164
+ return { name, dependencies };
6165
+ }
6166
+ async function discoverRustService(scanPath, dir) {
6167
+ const cargoPath = path16.join(dir, CARGO_MANIFEST);
6168
+ if (!await exists(cargoPath)) return null;
6169
+ const manifest = parseCargoToml(await fs14.readFile(cargoPath, "utf8").catch(() => ""));
6170
+ const name = manifest.name?.trim() || path16.basename(dir);
6171
+ const dependencies = manifest.dependencies;
6172
+ const pkg = { name, dependencies };
6173
+ const node = {
6174
+ id: serviceId8(name),
6175
+ type: NodeType12.ServiceNode,
6176
+ name,
6177
+ language: "rust",
6178
+ dependencies,
6179
+ repoPath: path16.relative(scanPath, dir)
6180
+ };
6181
+ return { pkg, dir, node };
6182
+ }
6183
+
6184
+ // src/extract/cpp.ts
6185
+ import { promises as fs15 } from "fs";
6186
+ import path17 from "path";
6187
+ import { NodeType as NodeType13, serviceId as serviceId9 } from "@neat.is/types";
6188
+ var CMAKE_MANIFEST = "CMakeLists.txt";
6189
+ async function hasCppManifest(dir) {
6190
+ return exists(path17.join(dir, CMAKE_MANIFEST));
6191
+ }
6192
+ function parseCMakeProjectName(source) {
6193
+ const withoutComments = source.replace(/#.*$/gm, "");
6194
+ const m = withoutComments.match(/\bproject\s*\(\s*"?([A-Za-z0-9_.+-]+)"?/i);
6195
+ return m?.[1];
6196
+ }
6197
+ async function discoverCppService(scanPath, dir) {
6198
+ const cmakePath = path17.join(dir, CMAKE_MANIFEST);
6199
+ if (!await exists(cmakePath)) return null;
6200
+ const source = await fs15.readFile(cmakePath, "utf8").catch(() => "");
6201
+ const name = parseCMakeProjectName(source)?.trim() || path17.basename(dir);
6202
+ const dependencies = {};
6203
+ const pkg = { name, dependencies };
6204
+ const node = {
6205
+ id: serviceId9(name),
6206
+ type: NodeType13.ServiceNode,
6207
+ name,
6208
+ language: "cpp",
6209
+ dependencies,
6210
+ repoPath: path17.relative(scanPath, dir)
6211
+ };
6212
+ return { pkg, dir, node };
6213
+ }
6214
+
6215
+ // src/extract/dockerfile-service.ts
6216
+ import { promises as fs16 } from "fs";
6217
+ import path18 from "path";
6218
+ import { NodeType as NodeType14, serviceId as serviceId10 } from "@neat.is/types";
6059
6219
  function languageForSourceExt(ext) {
6060
6220
  switch (ext) {
6061
6221
  case ".py":
@@ -6070,6 +6230,19 @@ function languageForSourceExt(ext) {
6070
6230
  return "csharp";
6071
6231
  case ".java":
6072
6232
  return "java";
6233
+ case ".kt":
6234
+ return "kotlin";
6235
+ case ".rs":
6236
+ return "rust";
6237
+ case ".cpp":
6238
+ case ".cc":
6239
+ case ".cxx":
6240
+ case ".c++":
6241
+ case ".hpp":
6242
+ case ".hh":
6243
+ case ".hxx":
6244
+ case ".h++":
6245
+ return "cpp";
6073
6246
  case ".ts":
6074
6247
  case ".tsx":
6075
6248
  return "typescript";
@@ -6081,14 +6254,14 @@ function languageForSourceExt(ext) {
6081
6254
  return null;
6082
6255
  }
6083
6256
  }
6084
- var LANGUAGE_PRECEDENCE = ["typescript", "javascript", "python", "go", "ruby", "php", "csharp", "java"];
6257
+ var LANGUAGE_PRECEDENCE = ["typescript", "javascript", "python", "go", "ruby", "php", "csharp", "java", "kotlin", "rust", "cpp"];
6085
6258
  async function inferServiceLanguage(dir) {
6086
- const entries = await fs13.readdir(dir, { withFileTypes: true }).catch(() => []);
6259
+ const entries = await fs16.readdir(dir, { withFileTypes: true }).catch(() => []);
6087
6260
  const counts = /* @__PURE__ */ new Map();
6088
6261
  for (const entry of entries) {
6089
6262
  if (!entry.isFile()) continue;
6090
6263
  if (isNeatAuthoredSourceFile(entry.name)) continue;
6091
- const ext = path15.extname(entry.name).toLowerCase();
6264
+ const ext = path18.extname(entry.name).toLowerCase();
6092
6265
  if (!SERVICE_FILE_EXTENSIONS.has(ext)) continue;
6093
6266
  const lang = languageForSourceExt(ext);
6094
6267
  if (!lang) continue;
@@ -6107,45 +6280,49 @@ async function inferServiceLanguage(dir) {
6107
6280
  return best;
6108
6281
  }
6109
6282
  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
6283
+ return await exists(path18.join(dir, "package.json")) || await exists(path18.join(dir, "pyproject.toml")) || await exists(path18.join(dir, "requirements.txt")) || await exists(path18.join(dir, "setup.py")) || await exists(path18.join(dir, "go.mod")) || await exists(path18.join(dir, "Gemfile")) || await exists(path18.join(dir, "composer.json")) || // C#'s marker is a glob (`*.csproj` / `*.sln`), so it reads the directory
6111
6284
  // 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);
6285
+ await hasCsharpProject(dir) || // Java (and its JVM sibling Kotlin, ADR-199) key on the same Maven / Gradle
6286
+ // build manifest (ADR-197), so this one check stands the Dockerfile fallback
6287
+ // down for both.
6288
+ await hasJavaManifest(dir) || // Rust's marker is a fixed `Cargo.toml` (ADR-201).
6289
+ await hasRustManifest(dir) || // C++'s marker is a fixed `CMakeLists.txt` (ADR-202).
6290
+ await hasCppManifest(dir);
6114
6291
  }
6115
6292
  async function hasDockerfileDeclaredService(dir) {
6116
- if (!await exists(path15.join(dir, "Dockerfile"))) return false;
6293
+ if (!await exists(path18.join(dir, "Dockerfile"))) return false;
6117
6294
  return await inferServiceLanguage(dir) !== null;
6118
6295
  }
6119
6296
  async function discoverDockerfileService(scanPath, dir) {
6120
- if (!await exists(path15.join(dir, "Dockerfile"))) return null;
6297
+ if (!await exists(path18.join(dir, "Dockerfile"))) return null;
6121
6298
  if (await hasLanguageManifest(dir)) return null;
6122
6299
  const language = await inferServiceLanguage(dir);
6123
6300
  if (!language) return null;
6124
- const name = path15.basename(dir);
6301
+ const name = path18.basename(dir);
6125
6302
  const pkg = { name, dependencies: {} };
6126
6303
  const node = {
6127
- id: serviceId7(name),
6128
- type: NodeType11.ServiceNode,
6304
+ id: serviceId10(name),
6305
+ type: NodeType14.ServiceNode,
6129
6306
  name,
6130
6307
  language,
6131
6308
  dependencies: {},
6132
- repoPath: path15.relative(scanPath, dir)
6309
+ repoPath: path18.relative(scanPath, dir)
6133
6310
  };
6134
6311
  return { pkg, dir, node };
6135
6312
  }
6136
6313
 
6137
6314
  // src/extract/owners.ts
6138
- import { promises as fs14 } from "fs";
6139
- import path16 from "path";
6315
+ import { promises as fs17 } from "fs";
6316
+ import path19 from "path";
6140
6317
  import { minimatch } from "minimatch";
6141
6318
  async function loadCodeowners(scanPath) {
6142
6319
  const candidates = [
6143
- path16.join(scanPath, "CODEOWNERS"),
6144
- path16.join(scanPath, ".github", "CODEOWNERS")
6320
+ path19.join(scanPath, "CODEOWNERS"),
6321
+ path19.join(scanPath, ".github", "CODEOWNERS")
6145
6322
  ];
6146
6323
  for (const file of candidates) {
6147
6324
  if (await exists(file)) {
6148
- const raw = await fs14.readFile(file, "utf8");
6325
+ const raw = await fs17.readFile(file, "utf8");
6149
6326
  return parseCodeowners(raw);
6150
6327
  }
6151
6328
  }
@@ -6163,7 +6340,7 @@ function parseCodeowners(raw) {
6163
6340
  return { rules };
6164
6341
  }
6165
6342
  function matchOwner(file, repoPath) {
6166
- const normalized = repoPath.split(path16.sep).join("/");
6343
+ const normalized = repoPath.split(path19.sep).join("/");
6167
6344
  for (const rule of file.rules) {
6168
6345
  if (matchesPattern(rule.pattern, normalized)) return rule.owners;
6169
6346
  }
@@ -6179,7 +6356,7 @@ function matchesPattern(rawPattern, repoPath) {
6179
6356
  return false;
6180
6357
  }
6181
6358
  async function readPackageJsonAuthor(serviceDir) {
6182
- const pkgPath = path16.join(serviceDir, "package.json");
6359
+ const pkgPath = path19.join(serviceDir, "package.json");
6183
6360
  if (!await exists(pkgPath)) return null;
6184
6361
  try {
6185
6362
  const pkg = await readJson(pkgPath);
@@ -6216,36 +6393,36 @@ function workspaceGlobs(pkg) {
6216
6393
  return null;
6217
6394
  }
6218
6395
  async function hasPythonManifest(dir) {
6219
- return await exists(path17.join(dir, "pyproject.toml")) || await exists(path17.join(dir, "requirements.txt")) || await exists(path17.join(dir, "setup.py"));
6396
+ return await exists(path20.join(dir, "pyproject.toml")) || await exists(path20.join(dir, "requirements.txt")) || await exists(path20.join(dir, "setup.py"));
6220
6397
  }
6221
6398
  async function hasGoManifest(dir) {
6222
- return exists(path17.join(dir, "go.mod"));
6399
+ return exists(path20.join(dir, "go.mod"));
6223
6400
  }
6224
6401
  async function hasRubyManifest(dir) {
6225
- return exists(path17.join(dir, "Gemfile"));
6402
+ return exists(path20.join(dir, "Gemfile"));
6226
6403
  }
6227
6404
  async function hasPhpManifest(dir) {
6228
- return exists(path17.join(dir, "composer.json"));
6405
+ return exists(path20.join(dir, "composer.json"));
6229
6406
  }
6230
6407
  async function hasCsharpManifest(dir) {
6231
6408
  return hasCsharpProject(dir);
6232
6409
  }
6233
6410
  async function loadGitignore(scanPath) {
6234
- const gitignorePath = path17.join(scanPath, ".gitignore");
6411
+ const gitignorePath = path20.join(scanPath, ".gitignore");
6235
6412
  if (!await exists(gitignorePath)) return null;
6236
- const raw = await fs15.readFile(gitignorePath, "utf8");
6413
+ const raw = await fs18.readFile(gitignorePath, "utf8");
6237
6414
  return ignore().add(raw);
6238
6415
  }
6239
6416
  async function walkDirs(start, scanPath, options, visit) {
6240
6417
  async function recurse(current, depth) {
6241
6418
  if (depth > options.maxDepth) return;
6242
- const entries = await fs15.readdir(current, { withFileTypes: true }).catch(() => []);
6419
+ const entries = await fs18.readdir(current, { withFileTypes: true }).catch(() => []);
6243
6420
  for (const entry of entries) {
6244
6421
  if (!entry.isDirectory()) continue;
6245
6422
  if (IGNORED_DIRS.has(entry.name)) continue;
6246
- const child = path17.join(current, entry.name);
6423
+ const child = path20.join(current, entry.name);
6247
6424
  if (options.ig) {
6248
- const rel = path17.relative(scanPath, child).split(path17.sep).join("/");
6425
+ const rel = path20.relative(scanPath, child).split(path20.sep).join("/");
6249
6426
  if (rel && options.ig.ignores(rel + "/")) continue;
6250
6427
  }
6251
6428
  if (await isPythonVenvDir(child)) continue;
@@ -6261,8 +6438,8 @@ async function expandWorkspaceGlobs(scanPath, globs) {
6261
6438
  for (const raw of globs) {
6262
6439
  const pattern = raw.replace(/^\.\//, "");
6263
6440
  if (!pattern.includes("*")) {
6264
- const candidate = path17.join(scanPath, pattern);
6265
- if (await exists(path17.join(candidate, "package.json"))) found.add(candidate);
6441
+ const candidate = path20.join(scanPath, pattern);
6442
+ if (await exists(path20.join(candidate, "package.json"))) found.add(candidate);
6266
6443
  continue;
6267
6444
  }
6268
6445
  const segments = pattern.split("/");
@@ -6271,13 +6448,13 @@ async function expandWorkspaceGlobs(scanPath, globs) {
6271
6448
  if (seg.includes("*")) break;
6272
6449
  staticSegments.push(seg);
6273
6450
  }
6274
- const start = path17.join(scanPath, ...staticSegments);
6451
+ const start = path20.join(scanPath, ...staticSegments);
6275
6452
  if (!await exists(start)) continue;
6276
6453
  const hasDoubleStar = pattern.includes("**");
6277
6454
  const walkDepth = hasDoubleStar ? scanDepth : Math.max(0, segments.length - staticSegments.length - 1);
6278
6455
  await walkDirs(start, scanPath, { maxDepth: walkDepth, ig: null }, async (dir) => {
6279
- const rel = path17.relative(scanPath, dir).split(path17.sep).join("/");
6280
- if (minimatch2(rel, pattern) && await exists(path17.join(dir, "package.json"))) {
6456
+ const rel = path20.relative(scanPath, dir).split(path20.sep).join("/");
6457
+ if (minimatch2(rel, pattern) && await exists(path20.join(dir, "package.json"))) {
6281
6458
  found.add(dir);
6282
6459
  }
6283
6460
  });
@@ -6300,31 +6477,31 @@ function detectJsFramework(pkg) {
6300
6477
  async function detectJsServiceLanguage(dir, pkg) {
6301
6478
  const deps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
6302
6479
  if (deps["typescript"] !== void 0) return "typescript";
6303
- const entries = await fs15.readdir(dir).catch(() => []);
6480
+ const entries = await fs18.readdir(dir).catch(() => []);
6304
6481
  if (entries.some((name) => /^tsconfig(\..+)?\.json$/.test(name))) return "typescript";
6305
6482
  return "javascript";
6306
6483
  }
6307
6484
  async function discoverNodeService(scanPath, dir) {
6308
- const pkgPath = path17.join(dir, "package.json");
6485
+ const pkgPath = path20.join(dir, "package.json");
6309
6486
  if (!await exists(pkgPath)) return null;
6310
6487
  let pkg;
6311
6488
  try {
6312
6489
  pkg = await readJson(pkgPath);
6313
6490
  } catch (err) {
6314
- recordExtractionError("services", path17.relative(scanPath, pkgPath), err);
6491
+ recordExtractionError("services", path20.relative(scanPath, pkgPath), err);
6315
6492
  return null;
6316
6493
  }
6317
6494
  if (!pkg.name) return null;
6318
6495
  const framework = detectJsFramework(pkg);
6319
6496
  const language = await detectJsServiceLanguage(dir, pkg);
6320
6497
  const node = {
6321
- id: serviceId8(pkg.name),
6322
- type: NodeType12.ServiceNode,
6498
+ id: serviceId11(pkg.name),
6499
+ type: NodeType15.ServiceNode,
6323
6500
  name: pkg.name,
6324
6501
  language,
6325
6502
  version: pkg.version,
6326
6503
  dependencies: pkg.dependencies ?? {},
6327
- repoPath: path17.relative(scanPath, dir),
6504
+ repoPath: path20.relative(scanPath, dir),
6328
6505
  ...pkg.engines?.node ? { nodeEngine: pkg.engines.node } : {},
6329
6506
  ...framework ? { framework } : {}
6330
6507
  };
@@ -6335,18 +6512,18 @@ async function discoverPyService(scanPath, dir) {
6335
6512
  if (!py) return null;
6336
6513
  const pkg = pythonToPackage(py);
6337
6514
  const node = {
6338
- id: serviceId8(py.name),
6339
- type: NodeType12.ServiceNode,
6515
+ id: serviceId11(py.name),
6516
+ type: NodeType15.ServiceNode,
6340
6517
  name: py.name,
6341
6518
  language: "python",
6342
6519
  version: py.version,
6343
6520
  dependencies: py.dependencies,
6344
- repoPath: path17.relative(scanPath, dir)
6521
+ repoPath: path20.relative(scanPath, dir)
6345
6522
  };
6346
6523
  return { pkg, dir, node };
6347
6524
  }
6348
6525
  async function discoverServices(scanPath) {
6349
- const rootPkgPath = path17.join(scanPath, "package.json");
6526
+ const rootPkgPath = path20.join(scanPath, "package.json");
6350
6527
  let rootPkg = null;
6351
6528
  if (await exists(rootPkgPath)) {
6352
6529
  try {
@@ -6354,7 +6531,7 @@ async function discoverServices(scanPath) {
6354
6531
  } catch (err) {
6355
6532
  recordExtractionError(
6356
6533
  "services workspaces",
6357
- path17.relative(scanPath, rootPkgPath),
6534
+ path20.relative(scanPath, rootPkgPath),
6358
6535
  err
6359
6536
  );
6360
6537
  }
@@ -6366,7 +6543,7 @@ async function discoverServices(scanPath) {
6366
6543
  } else {
6367
6544
  if (rootPkg && rootPkg.name) {
6368
6545
  candidateDirs.push(scanPath);
6369
- } else if (await hasPythonManifest(scanPath) || await hasGoManifest(scanPath) || await hasRubyManifest(scanPath) || await hasPhpManifest(scanPath) || await hasCsharpManifest(scanPath) || await hasJavaManifest(scanPath)) {
6546
+ } else if (await hasPythonManifest(scanPath) || await hasGoManifest(scanPath) || await hasRubyManifest(scanPath) || await hasPhpManifest(scanPath) || await hasCsharpManifest(scanPath) || await hasJavaManifest(scanPath) || await hasRustManifest(scanPath) || await hasCppManifest(scanPath)) {
6370
6547
  candidateDirs.push(scanPath);
6371
6548
  }
6372
6549
  const ig = await loadGitignore(scanPath);
@@ -6375,9 +6552,9 @@ async function discoverServices(scanPath) {
6375
6552
  scanPath,
6376
6553
  { maxDepth: parseScanDepth(), ig },
6377
6554
  async (dir) => {
6378
- if (await exists(path17.join(dir, "package.json"))) {
6555
+ if (await exists(path20.join(dir, "package.json"))) {
6379
6556
  candidateDirs.push(dir);
6380
- } else if (await hasPythonManifest(dir) || await hasGoManifest(dir) || await hasRubyManifest(dir) || await hasPhpManifest(dir) || await hasCsharpManifest(dir) || await hasJavaManifest(dir)) {
6557
+ } else if (await hasPythonManifest(dir) || await hasGoManifest(dir) || await hasRubyManifest(dir) || await hasPhpManifest(dir) || await hasCsharpManifest(dir) || await hasJavaManifest(dir) || await hasRustManifest(dir) || await hasCppManifest(dir)) {
6381
6558
  candidateDirs.push(dir);
6382
6559
  } else if (await hasDockerfileDeclaredService(dir)) {
6383
6560
  candidateDirs.push(dir);
@@ -6390,14 +6567,21 @@ async function discoverServices(scanPath) {
6390
6567
  const seenDirs = /* @__PURE__ */ new Set();
6391
6568
  const out = [];
6392
6569
  for (const dir of candidateDirs) {
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);
6570
+ 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) ?? // Kotlin runs ahead of Java: both mark a service with the same Maven/Gradle
6571
+ // manifest, so Kotlin claims a JVM-manifest dir whose source is `.kt` (ADR-199)
6572
+ // and a `.java`-dominant one falls through to the Java reader unchanged.
6573
+ await discoverKotlinService(scanPath, dir) ?? await discoverJavaService(scanPath, dir) ?? // Rust keys on a `Cargo.toml`, a manifest no other language shares (ADR-201),
6574
+ // so its order among the distinct-manifest discoverers doesn't matter.
6575
+ await discoverRustService(scanPath, dir) ?? // C++ keys on a `CMakeLists.txt`, likewise a manifest no other language shares
6576
+ // (ADR-202), so it too sits anywhere among the distinct-manifest discoverers.
6577
+ await discoverCppService(scanPath, dir) ?? await discoverDockerfileService(scanPath, dir);
6394
6578
  if (!service) continue;
6395
6579
  if (seenDirs.has(service.dir)) continue;
6396
6580
  seenDirs.add(service.dir);
6397
6581
  const existingDir = seen.get(service.node.name);
6398
6582
  if (existingDir !== void 0) {
6399
- const a = path17.relative(scanPath, existingDir) || ".";
6400
- const b = path17.relative(scanPath, dir) || ".";
6583
+ const a = path20.relative(scanPath, existingDir) || ".";
6584
+ const b = path20.relative(scanPath, dir) || ".";
6401
6585
  console.warn(
6402
6586
  `[neat] duplicate package name "${service.node.name}" \u2014 keeping ${a}, ignoring ${b}`
6403
6587
  );
@@ -6411,6 +6595,17 @@ async function discoverServices(scanPath) {
6411
6595
  const owner = await computeServiceOwner(codeowners, service.node.repoPath, service.dir);
6412
6596
  if (owner !== void 0) service.node.owner = owner;
6413
6597
  }
6598
+ const resolvedDirs = out.map((s) => path20.resolve(s.dir));
6599
+ for (let i = 0; i < out.length; i++) {
6600
+ const self = resolvedDirs[i];
6601
+ const nested = [];
6602
+ for (let j = 0; j < out.length; j++) {
6603
+ if (i === j) continue;
6604
+ const other = resolvedDirs[j];
6605
+ if (other.startsWith(self + path20.sep)) nested.push(other);
6606
+ }
6607
+ out[i].excludeDirs = nested;
6608
+ }
6414
6609
  return out;
6415
6610
  }
6416
6611
  function addServiceNodes(graph, services) {
@@ -6433,20 +6628,20 @@ function addServiceNodes(graph, services) {
6433
6628
  }
6434
6629
 
6435
6630
  // src/extract/aliases.ts
6436
- import path18 from "path";
6437
- import { promises as fs16 } from "fs";
6631
+ import path21 from "path";
6632
+ import { promises as fs19 } from "fs";
6438
6633
  import { parseAllDocuments } from "yaml";
6439
- import { NodeType as NodeType13 } from "@neat.is/types";
6634
+ import { NodeType as NodeType16 } from "@neat.is/types";
6440
6635
  var K8S_KINDS_WITH_HOSTNAMES = /* @__PURE__ */ new Set([
6441
6636
  "Service",
6442
6637
  "Deployment",
6443
6638
  "StatefulSet",
6444
6639
  "DaemonSet"
6445
6640
  ]);
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;
6641
+ function addAliases(graph, serviceId15, candidates) {
6642
+ if (!graph.hasNode(serviceId15)) return;
6643
+ const node = graph.getNodeAttributes(serviceId15);
6644
+ if (node.type !== NodeType16.ServiceNode) return;
6450
6645
  const set = new Set(node.aliases ?? []);
6451
6646
  for (const c of candidates) {
6452
6647
  if (!c) continue;
@@ -6455,20 +6650,20 @@ function addAliases(graph, serviceId12, candidates) {
6455
6650
  }
6456
6651
  if (set.size === 0) return;
6457
6652
  const updated = { ...node, aliases: [...set].sort() };
6458
- graph.replaceNodeAttributes(serviceId12, updated);
6653
+ graph.replaceNodeAttributes(serviceId15, updated);
6459
6654
  }
6460
6655
  function indexServicesByName(services) {
6461
6656
  const map = /* @__PURE__ */ new Map();
6462
6657
  for (const s of services) {
6463
6658
  map.set(s.node.name, s.node.id);
6464
- map.set(path18.basename(s.dir), s.node.id);
6659
+ map.set(path21.basename(s.dir), s.node.id);
6465
6660
  }
6466
6661
  return map;
6467
6662
  }
6468
6663
  async function collectComposeAliases(graph, scanPath, serviceIndex) {
6469
6664
  let composePath = null;
6470
6665
  for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
6471
- const abs = path18.join(scanPath, name);
6666
+ const abs = path21.join(scanPath, name);
6472
6667
  if (await exists(abs)) {
6473
6668
  composePath = abs;
6474
6669
  break;
@@ -6481,19 +6676,19 @@ async function collectComposeAliases(graph, scanPath, serviceIndex) {
6481
6676
  } catch (err) {
6482
6677
  recordExtractionError(
6483
6678
  "aliases compose",
6484
- path18.relative(scanPath, composePath),
6679
+ path21.relative(scanPath, composePath),
6485
6680
  err
6486
6681
  );
6487
6682
  return;
6488
6683
  }
6489
6684
  if (!compose?.services) return;
6490
6685
  for (const [composeName, svc] of Object.entries(compose.services)) {
6491
- const serviceId12 = serviceIndex.get(composeName);
6492
- if (!serviceId12) continue;
6686
+ const serviceId15 = serviceIndex.get(composeName);
6687
+ if (!serviceId15) continue;
6493
6688
  const aliases = /* @__PURE__ */ new Set([composeName]);
6494
6689
  if (svc.container_name) aliases.add(svc.container_name);
6495
6690
  if (svc.hostname) aliases.add(svc.hostname);
6496
- addAliases(graph, serviceId12, aliases);
6691
+ addAliases(graph, serviceId15, aliases);
6497
6692
  }
6498
6693
  }
6499
6694
  var LABEL_KEYS = /* @__PURE__ */ new Set([
@@ -6524,11 +6719,11 @@ function parseDockerfileLabels(content) {
6524
6719
  }
6525
6720
  async function collectDockerfileAliases(graph, services) {
6526
6721
  for (const service of services) {
6527
- const dockerfilePath = path18.join(service.dir, "Dockerfile");
6722
+ const dockerfilePath = path21.join(service.dir, "Dockerfile");
6528
6723
  if (!await exists(dockerfilePath)) continue;
6529
6724
  let content;
6530
6725
  try {
6531
- content = await fs16.readFile(dockerfilePath, "utf8");
6726
+ content = await fs19.readFile(dockerfilePath, "utf8");
6532
6727
  } catch (err) {
6533
6728
  recordExtractionError("aliases dockerfile", dockerfilePath, err);
6534
6729
  continue;
@@ -6540,15 +6735,15 @@ async function collectDockerfileAliases(graph, services) {
6540
6735
  async function walkYamlFiles(start, depth = 0, max = 5) {
6541
6736
  if (depth > max) return [];
6542
6737
  const out = [];
6543
- const entries = await fs16.readdir(start, { withFileTypes: true }).catch(() => []);
6738
+ const entries = await fs19.readdir(start, { withFileTypes: true }).catch(() => []);
6544
6739
  for (const entry of entries) {
6545
6740
  if (entry.isDirectory()) {
6546
6741
  if (IGNORED_DIRS.has(entry.name)) continue;
6547
- const child = path18.join(start, entry.name);
6742
+ const child = path21.join(start, entry.name);
6548
6743
  if (await isPythonVenvDir(child)) continue;
6549
6744
  out.push(...await walkYamlFiles(child, depth + 1, max));
6550
- } else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(path18.extname(entry.name))) {
6551
- out.push(path18.join(start, entry.name));
6745
+ } else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(path21.extname(entry.name))) {
6746
+ out.push(path21.join(start, entry.name));
6552
6747
  }
6553
6748
  }
6554
6749
  return out;
@@ -6575,7 +6770,7 @@ function k8sServiceTarget(doc, byName) {
6575
6770
  async function collectK8sAliases(graph, scanPath, serviceIndex) {
6576
6771
  const files = await walkYamlFiles(scanPath);
6577
6772
  for (const file of files) {
6578
- const content = await fs16.readFile(file, "utf8");
6773
+ const content = await fs19.readFile(file, "utf8");
6579
6774
  let docs;
6580
6775
  try {
6581
6776
  docs = parseAllDocuments(content).map((d) => d.toJSON());
@@ -6599,14 +6794,14 @@ async function addServiceAliases(graph, scanPath, services) {
6599
6794
  }
6600
6795
 
6601
6796
  // src/extract/files.ts
6602
- import path19 from "path";
6797
+ import path22 from "path";
6603
6798
  async function addFiles(graph, services) {
6604
6799
  let nodesAdded = 0;
6605
6800
  let edgesAdded = 0;
6606
6801
  for (const service of services) {
6607
- const filePaths = await walkSourceFiles(service.dir);
6802
+ const filePaths = await walkSourceFiles(service.dir, service.excludeDirs);
6608
6803
  for (const filePath of filePaths) {
6609
- const relPath = toPosix(path19.relative(service.dir, filePath));
6804
+ const relPath = toPosix(path22.relative(service.dir, filePath));
6610
6805
  const { nodesAdded: n, edgesAdded: e } = ensureFileNode(
6611
6806
  graph,
6612
6807
  service.pkg.name,
@@ -6621,7 +6816,7 @@ async function addFiles(graph, services) {
6621
6816
  }
6622
6817
 
6623
6818
  // src/extract/symbols.ts
6624
- import path20 from "path";
6819
+ import path23 from "path";
6625
6820
  import Parser3 from "tree-sitter";
6626
6821
  import JavaScript3 from "tree-sitter-javascript";
6627
6822
  import TypeScript from "tree-sitter-typescript";
@@ -6631,9 +6826,12 @@ import Ruby2 from "tree-sitter-ruby";
6631
6826
  import Php2 from "tree-sitter-php";
6632
6827
  import CSharp from "tree-sitter-c-sharp";
6633
6828
  import Java from "tree-sitter-java";
6829
+ import Kotlin from "tree-sitter-kotlin";
6830
+ import Rust from "tree-sitter-rust";
6831
+ import Cpp from "tree-sitter-cpp";
6634
6832
  import {
6635
6833
  EdgeType as EdgeType7,
6636
- NodeType as NodeType14,
6834
+ NodeType as NodeType17,
6637
6835
  Provenance as Provenance7,
6638
6836
  confidenceForExtracted as confidenceForExtracted4,
6639
6837
  extractedEdgeId as extractedEdgeId6,
@@ -6655,7 +6853,25 @@ var SYMBOL_GRAMMAR_BY_EXT = {
6655
6853
  ".rb": Ruby2,
6656
6854
  ".php": Php2.php_only,
6657
6855
  ".cs": CSharp,
6658
- ".java": Java
6856
+ ".java": Java,
6857
+ ".kt": Kotlin,
6858
+ ".rs": Rust,
6859
+ // C++ (ADR-202) — only the UNAMBIGUOUS extensions. `.cpp` / `.cc` / `.cxx` /
6860
+ // `.c++` are implementation files; `.hpp` / `.hh` / `.hxx` / `.h++` are C++-only
6861
+ // headers. `.h` and `.c` are deliberately absent: they are shared with C (a
6862
+ // language NEAT does not add), and this map is global — the symbol grain
6863
+ // dispatches on extension, not on the owning service's language — so claiming
6864
+ // `.h` would parse every pure-C header in any repo with the C++ grammar and
6865
+ // mislabel it. The otel-demo `currency` target's executable code lives in a
6866
+ // `.cpp`, so nothing is lost.
6867
+ ".cpp": Cpp,
6868
+ ".cc": Cpp,
6869
+ ".cxx": Cpp,
6870
+ ".c++": Cpp,
6871
+ ".hpp": Cpp,
6872
+ ".hh": Cpp,
6873
+ ".hxx": Cpp,
6874
+ ".h++": Cpp
6659
6875
  };
6660
6876
  function parseSource3(parser, source) {
6661
6877
  return parser.parse(
@@ -7068,6 +7284,237 @@ function collectJavaSymbolDefs(root) {
7068
7284
  walkChildren(root, void 0);
7069
7285
  return out;
7070
7286
  }
7287
+ function collectKotlinSymbolDefs(root) {
7288
+ const out = [];
7289
+ const push = (kind, qualname, node) => {
7290
+ out.push({
7291
+ kind,
7292
+ qualname,
7293
+ startLine: node.startPosition.row + 1,
7294
+ endLine: node.endPosition.row + 1
7295
+ });
7296
+ };
7297
+ const join = (prefix, name) => prefix ? `${prefix}.${name}` : name;
7298
+ const firstChildOfType = (node, types) => {
7299
+ for (let i = 0; i < node.namedChildCount; i++) {
7300
+ const child = node.namedChild(i);
7301
+ if (child && types.includes(child.type)) return child;
7302
+ }
7303
+ return void 0;
7304
+ };
7305
+ const nameOf = (node, ...types) => firstChildOfType(node, types)?.text;
7306
+ const bodyOf = (node) => firstChildOfType(node, ["class_body", "enum_class_body"]);
7307
+ let pkg;
7308
+ for (let i = 0; i < root.namedChildCount; i++) {
7309
+ const child = root.namedChild(i);
7310
+ if (child?.type === "package_header") {
7311
+ pkg = child.namedChild(0)?.text;
7312
+ break;
7313
+ }
7314
+ }
7315
+ const walkChildren = (container, classCtx) => {
7316
+ for (let i = 0; i < container.namedChildCount; i++) {
7317
+ const child = container.namedChild(i);
7318
+ if (child) visit(child, classCtx);
7319
+ }
7320
+ };
7321
+ const visit = (node, classCtx) => {
7322
+ switch (node.type) {
7323
+ case "class_declaration":
7324
+ case "object_declaration": {
7325
+ const name = nameOf(node, "type_identifier");
7326
+ const full = name ? join(classCtx ?? pkg, name) : classCtx;
7327
+ if (name) push("class", full, node);
7328
+ const body = bodyOf(node);
7329
+ if (body) walkChildren(body, full);
7330
+ return;
7331
+ }
7332
+ case "companion_object": {
7333
+ const name = nameOf(node, "type_identifier");
7334
+ const full = name ? join(classCtx ?? pkg, name) : classCtx ?? pkg;
7335
+ const body = bodyOf(node);
7336
+ if (body) walkChildren(body, full);
7337
+ return;
7338
+ }
7339
+ case "function_declaration": {
7340
+ const name = nameOf(node, "simple_identifier");
7341
+ if (name) push(classCtx ? "method" : "function", join(classCtx ?? pkg, name), node);
7342
+ return;
7343
+ }
7344
+ case "secondary_constructor": {
7345
+ if (classCtx) {
7346
+ const typeName = classCtx.slice(classCtx.lastIndexOf(".") + 1);
7347
+ push("constructor", join(classCtx, typeName), node);
7348
+ }
7349
+ return;
7350
+ }
7351
+ }
7352
+ walkChildren(node, classCtx);
7353
+ };
7354
+ walkChildren(root, void 0);
7355
+ return out;
7356
+ }
7357
+ function collectRustSymbolDefs(root) {
7358
+ const out = [];
7359
+ const push = (kind, qualname, node) => {
7360
+ out.push({
7361
+ kind,
7362
+ qualname,
7363
+ startLine: node.startPosition.row + 1,
7364
+ endLine: node.endPosition.row + 1
7365
+ });
7366
+ };
7367
+ const join = (prefix, name) => prefix ? `${prefix}::${name}` : name;
7368
+ const walkTypeBody = (body, typeCtx) => {
7369
+ for (let i = 0; i < body.namedChildCount; i++) {
7370
+ const child = body.namedChild(i);
7371
+ if (!child) continue;
7372
+ if (child.type === "function_item" || child.type === "function_signature_item") {
7373
+ const name = child.childForFieldName("name")?.text;
7374
+ if (name) push("method", join(typeCtx, name), child);
7375
+ }
7376
+ }
7377
+ };
7378
+ const walkChildren = (container, modCtx) => {
7379
+ for (let i = 0; i < container.namedChildCount; i++) {
7380
+ const child = container.namedChild(i);
7381
+ if (child) visit(child, modCtx);
7382
+ }
7383
+ };
7384
+ const visit = (node, modCtx) => {
7385
+ switch (node.type) {
7386
+ case "mod_item": {
7387
+ const name = node.childForFieldName("name")?.text;
7388
+ const full = name ? join(modCtx, name) : modCtx;
7389
+ const body = node.childForFieldName("body");
7390
+ if (body) walkChildren(body, full);
7391
+ return;
7392
+ }
7393
+ case "struct_item":
7394
+ case "enum_item": {
7395
+ const name = node.childForFieldName("name")?.text;
7396
+ if (name) push("class", join(modCtx, name), node);
7397
+ return;
7398
+ }
7399
+ case "trait_item": {
7400
+ const name = node.childForFieldName("name")?.text;
7401
+ const full = name ? join(modCtx, name) : modCtx;
7402
+ if (name) push("class", full, node);
7403
+ const body = node.childForFieldName("body");
7404
+ if (body && full) walkTypeBody(body, full);
7405
+ return;
7406
+ }
7407
+ case "impl_item": {
7408
+ const typeName = node.childForFieldName("type")?.text;
7409
+ const full = typeName ? join(modCtx, typeName) : modCtx;
7410
+ const body = node.childForFieldName("body");
7411
+ if (body && full) walkTypeBody(body, full);
7412
+ return;
7413
+ }
7414
+ case "function_item": {
7415
+ const name = node.childForFieldName("name")?.text;
7416
+ if (name) push("function", join(modCtx, name), node);
7417
+ return;
7418
+ }
7419
+ }
7420
+ walkChildren(node, modCtx);
7421
+ };
7422
+ walkChildren(root, void 0);
7423
+ return out;
7424
+ }
7425
+ function collectCppSymbolDefs(root) {
7426
+ const out = [];
7427
+ const push = (kind, qualname, node) => {
7428
+ out.push({
7429
+ kind,
7430
+ qualname,
7431
+ startLine: node.startPosition.row + 1,
7432
+ endLine: node.endPosition.row + 1
7433
+ });
7434
+ };
7435
+ const join = (prefix, name) => prefix ? `${prefix}::${name}` : name;
7436
+ const DECLARATOR_WRAPPERS = /* @__PURE__ */ new Set([
7437
+ "function_declarator",
7438
+ "pointer_declarator",
7439
+ "reference_declarator",
7440
+ "parenthesized_declarator"
7441
+ ]);
7442
+ const functionNameNode = (node) => {
7443
+ let decl = node.childForFieldName("declarator");
7444
+ for (let guard = 0; decl && guard < 8; guard++) {
7445
+ if (decl.type === "function_declarator") {
7446
+ return decl.childForFieldName("declarator") ?? void 0;
7447
+ }
7448
+ let next;
7449
+ for (let i = 0; i < decl.namedChildCount; i++) {
7450
+ const child = decl.namedChild(i);
7451
+ if (child && DECLARATOR_WRAPPERS.has(child.type)) {
7452
+ next = child;
7453
+ break;
7454
+ }
7455
+ }
7456
+ decl = next;
7457
+ }
7458
+ return void 0;
7459
+ };
7460
+ const classify = (nameText, prefix, enclosingClass) => {
7461
+ const qualname = join(prefix, nameText);
7462
+ let className;
7463
+ let memberName;
7464
+ const idx = nameText.lastIndexOf("::");
7465
+ if (idx >= 0) {
7466
+ memberName = nameText.slice(idx + 2);
7467
+ const before = nameText.slice(0, idx);
7468
+ const j = before.lastIndexOf("::");
7469
+ className = j >= 0 ? before.slice(j + 2) : before;
7470
+ } else {
7471
+ memberName = nameText;
7472
+ className = enclosingClass;
7473
+ }
7474
+ let kind;
7475
+ if (className && memberName === className) kind = "constructor";
7476
+ else if (className) kind = "method";
7477
+ else kind = "function";
7478
+ return { kind, qualname };
7479
+ };
7480
+ const walkChildren = (container, prefix, enclosingClass) => {
7481
+ for (let i = 0; i < container.namedChildCount; i++) {
7482
+ const child = container.namedChild(i);
7483
+ if (child) visit(child, prefix, enclosingClass);
7484
+ }
7485
+ };
7486
+ const visit = (node, prefix, enclosingClass) => {
7487
+ switch (node.type) {
7488
+ case "namespace_definition": {
7489
+ const name = node.childForFieldName("name")?.text;
7490
+ const newPrefix = name ? join(prefix, name) : prefix;
7491
+ const body = node.childForFieldName("body");
7492
+ if (body) walkChildren(body, newPrefix, void 0);
7493
+ return;
7494
+ }
7495
+ case "class_specifier":
7496
+ case "struct_specifier": {
7497
+ const name = node.childForFieldName("name")?.text;
7498
+ const full = name ? join(prefix, name) : prefix;
7499
+ if (name) push("class", full, node);
7500
+ const body = node.childForFieldName("body");
7501
+ if (body && name) walkChildren(body, full, name);
7502
+ return;
7503
+ }
7504
+ case "function_definition": {
7505
+ const nameNode = functionNameNode(node);
7506
+ if (nameNode) {
7507
+ const { kind, qualname } = classify(nameNode.text, prefix, enclosingClass);
7508
+ push(kind, qualname, node);
7509
+ }
7510
+ return;
7511
+ }
7512
+ }
7513
+ walkChildren(node, prefix, enclosingClass);
7514
+ };
7515
+ walkChildren(root, void 0, void 0);
7516
+ return out;
7517
+ }
7071
7518
  function collectSymbolDefsForExt(ext, root) {
7072
7519
  switch (ext) {
7073
7520
  case ".py":
@@ -7082,6 +7529,19 @@ function collectSymbolDefsForExt(ext, root) {
7082
7529
  return collectCsharpSymbolDefs(root);
7083
7530
  case ".java":
7084
7531
  return collectJavaSymbolDefs(root);
7532
+ case ".kt":
7533
+ return collectKotlinSymbolDefs(root);
7534
+ case ".rs":
7535
+ return collectRustSymbolDefs(root);
7536
+ case ".cpp":
7537
+ case ".cc":
7538
+ case ".cxx":
7539
+ case ".c++":
7540
+ case ".hpp":
7541
+ case ".hh":
7542
+ case ".hxx":
7543
+ case ".h++":
7544
+ return collectCppSymbolDefs(root);
7085
7545
  default:
7086
7546
  return collectSymbolDefs(root);
7087
7547
  }
@@ -7113,12 +7573,12 @@ async function addSymbols(graph, services) {
7113
7573
  let nodesAdded = 0;
7114
7574
  let edgesAdded = 0;
7115
7575
  for (const service of services) {
7116
- const files = await loadSourceFiles(service.dir);
7576
+ const files = await loadSourceFiles(service.dir, service.excludeDirs);
7117
7577
  for (const file of files) {
7118
- const ext = path20.extname(file.path);
7578
+ const ext = path23.extname(file.path);
7119
7579
  const parser = parserForExt5(ext);
7120
7580
  if (!parser) continue;
7121
- const relPath = toPosix(path20.relative(service.dir, file.path));
7581
+ const relPath = toPosix(path23.relative(service.dir, file.path));
7122
7582
  let defs;
7123
7583
  try {
7124
7584
  const tree = parseSource3(parser, file.content);
@@ -7141,7 +7601,7 @@ async function addSymbols(graph, services) {
7141
7601
  if (!graph.hasNode(sid)) {
7142
7602
  const node = {
7143
7603
  id: sid,
7144
- type: NodeType14.SymbolNode,
7604
+ type: NodeType17.SymbolNode,
7145
7605
  kind: def.kind,
7146
7606
  qualname: def.qualname,
7147
7607
  span: { startLine: def.startLine, endLine: def.endLine },
@@ -7177,11 +7637,11 @@ async function addSymbols(graph, services) {
7177
7637
  }
7178
7638
 
7179
7639
  // src/extract/symbol-edges.ts
7180
- import path21 from "path";
7640
+ import path24 from "path";
7181
7641
  import Parser4 from "tree-sitter";
7182
7642
  import {
7183
7643
  EdgeType as EdgeType8,
7184
- NodeType as NodeType15,
7644
+ NodeType as NodeType18,
7185
7645
  Provenance as Provenance8,
7186
7646
  confidenceForExtracted as confidenceForExtracted5,
7187
7647
  extractedEdgeId as extractedEdgeId7,
@@ -7289,12 +7749,12 @@ async function addSymbolEdges(graph, services) {
7289
7749
  for (const service of services) {
7290
7750
  const serviceName = service.pkg.name;
7291
7751
  const tsPaths = await loadTsPathConfig(service.dir);
7292
- const files = await loadSourceFiles(service.dir);
7752
+ const files = await loadSourceFiles(service.dir, service.excludeDirs);
7293
7753
  for (const file of files) {
7294
- const parser = parserForExt5(path21.extname(file.path));
7754
+ const parser = parserForExt5(path24.extname(file.path));
7295
7755
  if (!parser) continue;
7296
- const relPath = toPosix(path21.relative(service.dir, file.path));
7297
- const fileDir = path21.dirname(file.path);
7756
+ const relPath = toPosix(path24.relative(service.dir, file.path));
7757
+ const fileDir = path24.dirname(file.path);
7298
7758
  let root;
7299
7759
  try {
7300
7760
  root = parseSource3(parser, file.content).rootNode;
@@ -7336,7 +7796,7 @@ async function addSymbolEdges(graph, services) {
7336
7796
  const candidate = symbolId3(serviceName, imp.targetRelPath, imp.importedName);
7337
7797
  if (graph.hasNode(candidate)) {
7338
7798
  const node = graph.getNodeAttributes(candidate);
7339
- if (node.type === NodeType15.SymbolNode && node.kind === wantKind) return candidate;
7799
+ if (node.type === NodeType18.SymbolNode && node.kind === wantKind) return candidate;
7340
7800
  }
7341
7801
  }
7342
7802
  return null;
@@ -7428,11 +7888,11 @@ async function addSymbolEdges(graph, services) {
7428
7888
  }
7429
7889
 
7430
7890
  // src/extract/actions.ts
7431
- import path22 from "path";
7891
+ import path25 from "path";
7432
7892
  import Parser5 from "tree-sitter";
7433
7893
  import {
7434
7894
  EdgeType as EdgeType9,
7435
- NodeType as NodeType16,
7895
+ NodeType as NodeType19,
7436
7896
  Provenance as Provenance9,
7437
7897
  confidenceForExtracted as confidenceForExtracted6,
7438
7898
  extractedEdgeId as extractedEdgeId8,
@@ -7579,12 +8039,12 @@ async function addServerActions(graph, services) {
7579
8039
  };
7580
8040
  if (deps["next"] === void 0) continue;
7581
8041
  const tsPaths = await loadTsPathConfig(service.dir);
7582
- const files = await loadSourceFiles(service.dir);
8042
+ const files = await loadSourceFiles(service.dir, service.excludeDirs);
7583
8043
  for (const file of files) {
7584
8044
  if (isTestPath(file.path)) continue;
7585
- const parser = parserForExt5(path22.extname(file.path));
8045
+ const parser = parserForExt5(path25.extname(file.path));
7586
8046
  if (!parser) continue;
7587
- const relPath = toPosix(path22.relative(service.dir, file.path));
8047
+ const relPath = toPosix(path25.relative(service.dir, file.path));
7588
8048
  let root;
7589
8049
  try {
7590
8050
  root = parseSource3(parser, file.content).rootNode;
@@ -7611,7 +8071,7 @@ async function addServerActions(graph, services) {
7611
8071
  if (!graph.hasNode(aid)) {
7612
8072
  const node = {
7613
8073
  id: aid,
7614
- type: NodeType16.ServerActionNode,
8074
+ type: NodeType19.ServerActionNode,
7615
8075
  name: action.exportName,
7616
8076
  service: service.pkg.name,
7617
8077
  module: relPath,
@@ -7645,10 +8105,10 @@ async function addServerActions(graph, services) {
7645
8105
  }
7646
8106
  for (const file of files) {
7647
8107
  if (isTestPath(file.path)) continue;
7648
- const parser = parserForExt5(path22.extname(file.path));
8108
+ const parser = parserForExt5(path25.extname(file.path));
7649
8109
  if (!parser) continue;
7650
- const relPath = toPosix(path22.relative(service.dir, file.path));
7651
- const fileDir = path22.dirname(file.path);
8110
+ const relPath = toPosix(path25.relative(service.dir, file.path));
8111
+ const fileDir = path25.dirname(file.path);
7652
8112
  let root;
7653
8113
  try {
7654
8114
  root = parseSource3(parser, file.content).rootNode;
@@ -7700,10 +8160,10 @@ async function addServerActions(graph, services) {
7700
8160
  }
7701
8161
 
7702
8162
  // src/extract/databases/index.ts
7703
- import path30 from "path";
8163
+ import path33 from "path";
7704
8164
  import {
7705
8165
  EdgeType as EdgeType10,
7706
- NodeType as NodeType17,
8166
+ NodeType as NodeType20,
7707
8167
  Provenance as Provenance10,
7708
8168
  configId,
7709
8169
  databaseId as databaseId2,
@@ -7711,9 +8171,9 @@ import {
7711
8171
  } from "@neat.is/types";
7712
8172
 
7713
8173
  // src/extract/databases/db-config-yaml.ts
7714
- import path23 from "path";
8174
+ import path26 from "path";
7715
8175
  async function parse(serviceDir) {
7716
- const yamlPath = path23.join(serviceDir, "db-config.yaml");
8176
+ const yamlPath = path26.join(serviceDir, "db-config.yaml");
7717
8177
  if (!await exists(yamlPath)) return [];
7718
8178
  const raw = await readYaml(yamlPath);
7719
8179
  return [
@@ -7730,12 +8190,12 @@ async function parse(serviceDir) {
7730
8190
  var dbConfigYamlParser = { name: "db-config.yaml", parse };
7731
8191
 
7732
8192
  // src/extract/databases/dotenv.ts
7733
- import { promises as fs18 } from "fs";
7734
- import path25 from "path";
8193
+ import { promises as fs21 } from "fs";
8194
+ import path28 from "path";
7735
8195
 
7736
8196
  // src/extract/databases/shared.ts
7737
- import { promises as fs17 } from "fs";
7738
- import path24 from "path";
8197
+ import { promises as fs20 } from "fs";
8198
+ import path27 from "path";
7739
8199
  function schemeToEngine(scheme) {
7740
8200
  const s = scheme.toLowerCase().split("+")[0];
7741
8201
  switch (s) {
@@ -7774,18 +8234,18 @@ function parseConnectionString(url) {
7774
8234
  }
7775
8235
  async function readIfExists(filePath) {
7776
8236
  try {
7777
- return await fs17.readFile(filePath, "utf8");
8237
+ return await fs20.readFile(filePath, "utf8");
7778
8238
  } catch {
7779
8239
  return null;
7780
8240
  }
7781
8241
  }
7782
8242
  async function resolveEnvVar(serviceDir, name) {
7783
- const entries = await fs17.readdir(serviceDir, { withFileTypes: true }).catch(() => []);
8243
+ const entries = await fs20.readdir(serviceDir, { withFileTypes: true }).catch(() => []);
7784
8244
  const envNames = entries.filter((e) => e.isFile() && (e.name === ".env" || e.name.startsWith(".env."))).map((e) => e.name);
7785
8245
  const rank = (n) => n === ".env.local" ? 0 : n === ".env" ? 1 : 2;
7786
8246
  envNames.sort((a, b) => rank(a) - rank(b) || a.localeCompare(b));
7787
8247
  for (const fileName of envNames) {
7788
- const content = await readIfExists(path24.join(serviceDir, fileName));
8248
+ const content = await readIfExists(path27.join(serviceDir, fileName));
7789
8249
  if (!content) continue;
7790
8250
  for (const line of content.split("\n")) {
7791
8251
  const trimmed = line.trim();
@@ -7804,7 +8264,7 @@ async function resolveEnvVar(serviceDir, name) {
7804
8264
  }
7805
8265
  async function findFirst(serviceDir, candidates) {
7806
8266
  for (const rel of candidates) {
7807
- const abs = path24.join(serviceDir, rel);
8267
+ const abs = path27.join(serviceDir, rel);
7808
8268
  const content = await readIfExists(abs);
7809
8269
  if (content !== null) return abs;
7810
8270
  }
@@ -7862,15 +8322,15 @@ function parseDotenvLine(line) {
7862
8322
  return { key, value };
7863
8323
  }
7864
8324
  async function parse2(serviceDir) {
7865
- const entries = await fs18.readdir(serviceDir, { withFileTypes: true }).catch(() => []);
8325
+ const entries = await fs21.readdir(serviceDir, { withFileTypes: true }).catch(() => []);
7866
8326
  const configs = [];
7867
8327
  const seen = /* @__PURE__ */ new Set();
7868
8328
  for (const entry of entries) {
7869
8329
  if (!entry.isFile()) continue;
7870
8330
  const match = isConfigFile(entry.name);
7871
8331
  if (!match.match || match.fileType !== "env") continue;
7872
- const filePath = path25.join(serviceDir, entry.name);
7873
- const content = await fs18.readFile(filePath, "utf8");
8332
+ const filePath = path28.join(serviceDir, entry.name);
8333
+ const content = await fs21.readFile(filePath, "utf8");
7874
8334
  for (const line of content.split("\n")) {
7875
8335
  const parsed = parseDotenvLine(line);
7876
8336
  if (!parsed) continue;
@@ -7888,9 +8348,9 @@ async function parse2(serviceDir) {
7888
8348
  var dotenvParser = { name: ".env", parse: parse2 };
7889
8349
 
7890
8350
  // src/extract/databases/prisma.ts
7891
- import path26 from "path";
8351
+ import path29 from "path";
7892
8352
  async function parse3(serviceDir) {
7893
- const schemaPath = path26.join(serviceDir, "prisma", "schema.prisma");
8353
+ const schemaPath = path29.join(serviceDir, "prisma", "schema.prisma");
7894
8354
  const content = await readIfExists(schemaPath);
7895
8355
  if (!content) return [];
7896
8356
  const block = content.match(/datasource\s+\w+\s*\{([^}]*)\}/s);
@@ -8045,10 +8505,10 @@ async function parse5(serviceDir) {
8045
8505
  var knexParser = { name: "knex", parse: parse5 };
8046
8506
 
8047
8507
  // src/extract/databases/ormconfig.ts
8048
- import path27 from "path";
8508
+ import path30 from "path";
8049
8509
  async function parse6(serviceDir) {
8050
8510
  for (const candidate of ["ormconfig.json", "ormconfig.yaml", "ormconfig.yml"]) {
8051
- const abs = path27.join(serviceDir, candidate);
8511
+ const abs = path30.join(serviceDir, candidate);
8052
8512
  if (!await exists(abs)) continue;
8053
8513
  const raw = candidate.endsWith(".json") ? await readJson(abs) : await readYaml(abs);
8054
8514
  const entries = Array.isArray(raw) ? raw : [raw];
@@ -8106,9 +8566,9 @@ async function parse7(serviceDir) {
8106
8566
  var typeormParser = { name: "typeorm", parse: parse7 };
8107
8567
 
8108
8568
  // src/extract/databases/sequelize.ts
8109
- import path28 from "path";
8569
+ import path31 from "path";
8110
8570
  async function parse8(serviceDir) {
8111
- const configPath = path28.join(serviceDir, "config", "config.json");
8571
+ const configPath = path31.join(serviceDir, "config", "config.json");
8112
8572
  if (!await exists(configPath)) return [];
8113
8573
  const raw = await readJson(configPath);
8114
8574
  const out = [];
@@ -8134,7 +8594,7 @@ async function parse8(serviceDir) {
8134
8594
  var sequelizeParser = { name: "sequelize", parse: parse8 };
8135
8595
 
8136
8596
  // src/extract/databases/docker-compose.ts
8137
- import path29 from "path";
8597
+ import path32 from "path";
8138
8598
  function portFromService(svc) {
8139
8599
  for (const raw of svc.ports ?? []) {
8140
8600
  const str = String(raw);
@@ -8161,7 +8621,7 @@ function databaseFromEnv(svc) {
8161
8621
  }
8162
8622
  async function parse9(serviceDir) {
8163
8623
  for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
8164
- const abs = path29.join(serviceDir, name);
8624
+ const abs = path32.join(serviceDir, name);
8165
8625
  if (!await exists(abs)) continue;
8166
8626
  const raw = await readYaml(abs);
8167
8627
  if (!raw?.services) return [];
@@ -8203,7 +8663,7 @@ function compatibleDriversFor(engine) {
8203
8663
  function toDatabaseNode(config) {
8204
8664
  return {
8205
8665
  id: databaseId2(config.host),
8206
- type: NodeType17.DatabaseNode,
8666
+ type: NodeType20.DatabaseNode,
8207
8667
  name: config.database || config.host,
8208
8668
  engine: config.engine,
8209
8669
  engineVersion: config.engineVersion,
@@ -8333,7 +8793,7 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
8333
8793
  discoveredVia: mergedDiscoveredVia
8334
8794
  });
8335
8795
  }
8336
- const relConfigFile = toPosix(path30.relative(service.dir, config.sourceFile));
8796
+ const relConfigFile = toPosix(path33.relative(service.dir, config.sourceFile));
8337
8797
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
8338
8798
  graph,
8339
8799
  service.pkg.name,
@@ -8342,7 +8802,7 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
8342
8802
  );
8343
8803
  nodesAdded += fn;
8344
8804
  edgesAdded += fe;
8345
- const evidenceFile = toPosix(path30.relative(scanPath, config.sourceFile));
8805
+ const evidenceFile = toPosix(path33.relative(scanPath, config.sourceFile));
8346
8806
  const edge = {
8347
8807
  id: extractedEdgeId(fileNodeId, dbNode.id, EdgeType10.CONNECTS_TO),
8348
8808
  source: fileNodeId,
@@ -8360,15 +8820,15 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
8360
8820
  if (allConfigs.length === 1) {
8361
8821
  const primary = allConfigs[0];
8362
8822
  service.node.dbConnectionTarget = primary.port ? `${primary.host}:${primary.port}` : primary.host;
8363
- const relPath = path30.relative(scanPath, primary.sourceFile);
8823
+ const relPath = path33.relative(scanPath, primary.sourceFile);
8364
8824
  const cfgId = configId(relPath);
8365
8825
  if (!graph.hasNode(cfgId)) {
8366
8826
  const cfgNode = {
8367
8827
  id: cfgId,
8368
- type: NodeType17.ConfigNode,
8369
- name: path30.basename(primary.sourceFile),
8828
+ type: NodeType20.ConfigNode,
8829
+ name: path33.basename(primary.sourceFile),
8370
8830
  path: relPath,
8371
- fileType: isConfigFile(path30.basename(primary.sourceFile)).fileType || "config"
8831
+ fileType: isConfigFile(path33.basename(primary.sourceFile)).fileType || "config"
8372
8832
  };
8373
8833
  graph.addNode(cfgId, cfgNode);
8374
8834
  nodesAdded++;
@@ -8408,23 +8868,25 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
8408
8868
  }
8409
8869
 
8410
8870
  // src/extract/configs.ts
8411
- import { promises as fs19 } from "fs";
8412
- import path31 from "path";
8871
+ import { promises as fs22 } from "fs";
8872
+ import path34 from "path";
8413
8873
  import {
8414
8874
  EdgeType as EdgeType11,
8415
- NodeType as NodeType18,
8875
+ NodeType as NodeType21,
8416
8876
  Provenance as Provenance11,
8417
8877
  configId as configId2,
8418
8878
  confidenceForExtracted as confidenceForExtracted8
8419
8879
  } from "@neat.is/types";
8420
- async function walkConfigFiles(dir) {
8880
+ async function walkConfigFiles(dir, excludeDirs = []) {
8881
+ const excluded = new Set(excludeDirs.map((d) => path34.resolve(d)));
8421
8882
  const out = [];
8422
8883
  async function walk9(current) {
8423
- const entries = await fs19.readdir(current, { withFileTypes: true });
8884
+ const entries = await fs22.readdir(current, { withFileTypes: true });
8424
8885
  for (const entry of entries) {
8425
- const full = path31.join(current, entry.name);
8886
+ const full = path34.join(current, entry.name);
8426
8887
  if (entry.isDirectory()) {
8427
8888
  if (IGNORED_DIRS.has(entry.name)) continue;
8889
+ if (excluded.has(path34.resolve(full))) continue;
8428
8890
  if (await isPythonVenvDir(full)) continue;
8429
8891
  await walk9(full);
8430
8892
  } else if (entry.isFile() && isConfigFile(entry.name).match) {
@@ -8439,21 +8901,21 @@ async function addConfigNodes(graph, services, scanPath) {
8439
8901
  let nodesAdded = 0;
8440
8902
  let edgesAdded = 0;
8441
8903
  for (const service of services) {
8442
- const configFiles = await walkConfigFiles(service.dir);
8904
+ const configFiles = await walkConfigFiles(service.dir, service.excludeDirs);
8443
8905
  for (const file of configFiles) {
8444
- const relPath = path31.relative(scanPath, file);
8906
+ const relPath = path34.relative(scanPath, file);
8445
8907
  const node = {
8446
8908
  id: configId2(relPath),
8447
- type: NodeType18.ConfigNode,
8448
- name: path31.basename(file),
8909
+ type: NodeType21.ConfigNode,
8910
+ name: path34.basename(file),
8449
8911
  path: relPath,
8450
- fileType: isConfigFile(path31.basename(file)).fileType
8912
+ fileType: isConfigFile(path34.basename(file)).fileType
8451
8913
  };
8452
8914
  if (!graph.hasNode(node.id)) {
8453
8915
  graph.addNode(node.id, node);
8454
8916
  nodesAdded++;
8455
8917
  }
8456
- const relToService = toPosix(path31.relative(service.dir, file));
8918
+ const relToService = toPosix(path34.relative(service.dir, file));
8457
8919
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
8458
8920
  graph,
8459
8921
  service.pkg.name,
@@ -8469,7 +8931,7 @@ async function addConfigNodes(graph, services, scanPath) {
8469
8931
  type: EdgeType11.CONFIGURED_BY,
8470
8932
  provenance: Provenance11.EXTRACTED,
8471
8933
  confidence: confidenceForExtracted8("structural"),
8472
- evidence: { file: relPath.split(path31.sep).join("/") }
8934
+ evidence: { file: relPath.split(path34.sep).join("/") }
8473
8935
  };
8474
8936
  if (!graph.hasEdge(edge.id)) {
8475
8937
  graph.addEdgeWithKey(edge.id, edge.source, edge.target, edge);
@@ -8481,11 +8943,11 @@ async function addConfigNodes(graph, services, scanPath) {
8481
8943
  }
8482
8944
 
8483
8945
  // src/extract/proto.ts
8484
- import { promises as fs20 } from "fs";
8485
- import path32 from "path";
8946
+ import { promises as fs23 } from "fs";
8947
+ import path35 from "path";
8486
8948
  import {
8487
8949
  EdgeType as EdgeType12,
8488
- NodeType as NodeType19,
8950
+ NodeType as NodeType22,
8489
8951
  Provenance as Provenance12,
8490
8952
  confidenceForExtracted as confidenceForExtracted9,
8491
8953
  extractedEdgeId as extractedEdgeId9,
@@ -8526,17 +8988,19 @@ function grpcMethodsFromProto(content, fqPackage) {
8526
8988
  }
8527
8989
  return out;
8528
8990
  }
8529
- async function walkProtoFiles(dir) {
8991
+ async function walkProtoFiles(dir, excludeDirs = []) {
8992
+ const excluded = new Set(excludeDirs.map((d) => path35.resolve(d)));
8530
8993
  const out = [];
8531
8994
  async function walk9(current) {
8532
- const entries = await fs20.readdir(current, { withFileTypes: true }).catch(() => []);
8995
+ const entries = await fs23.readdir(current, { withFileTypes: true }).catch(() => []);
8533
8996
  for (const entry of entries) {
8534
- const full = path32.join(current, entry.name);
8997
+ const full = path35.join(current, entry.name);
8535
8998
  if (entry.isDirectory()) {
8536
8999
  if (IGNORED_DIRS.has(entry.name)) continue;
9000
+ if (excluded.has(path35.resolve(full))) continue;
8537
9001
  if (await isPythonVenvDir(full)) continue;
8538
9002
  await walk9(full);
8539
- } else if (entry.isFile() && path32.extname(entry.name) === PROTO_EXTENSION) {
9003
+ } else if (entry.isFile() && path35.extname(entry.name) === PROTO_EXTENSION) {
8540
9004
  out.push(full);
8541
9005
  }
8542
9006
  }
@@ -8548,13 +9012,13 @@ async function addGrpcMethods(graph, services) {
8548
9012
  let nodesAdded = 0;
8549
9013
  let edgesAdded = 0;
8550
9014
  for (const service of services) {
8551
- const protoPaths = await walkProtoFiles(service.dir);
9015
+ const protoPaths = await walkProtoFiles(service.dir, service.excludeDirs);
8552
9016
  for (const protoPath of protoPaths) {
8553
9017
  if (isTestPath(protoPath)) continue;
8554
- const relFile = toPosix(path32.relative(service.dir, protoPath));
9018
+ const relFile = toPosix(path35.relative(service.dir, protoPath));
8555
9019
  let content;
8556
9020
  try {
8557
- content = await fs20.readFile(protoPath, "utf8");
9021
+ content = await fs23.readFile(protoPath, "utf8");
8558
9022
  } catch (err) {
8559
9023
  recordExtractionError("proto extraction", protoPath, err);
8560
9024
  continue;
@@ -8572,7 +9036,7 @@ async function addGrpcMethods(graph, services) {
8572
9036
  if (!graph.hasNode(mid)) {
8573
9037
  const node = {
8574
9038
  id: mid,
8575
- type: NodeType19.GrpcMethodNode,
9039
+ type: NodeType22.GrpcMethodNode,
8576
9040
  name: `${method.rpcService}/${method.rpcMethod}`,
8577
9041
  rpcService: method.rpcService,
8578
9042
  rpcMethod: method.rpcMethod,
@@ -8610,14 +9074,14 @@ async function addGrpcMethods(graph, services) {
8610
9074
  // src/extract/calls/index.ts
8611
9075
  import {
8612
9076
  EdgeType as EdgeType15,
8613
- NodeType as NodeType21,
9077
+ NodeType as NodeType24,
8614
9078
  Provenance as Provenance15,
8615
9079
  confidenceForExtracted as confidenceForExtracted12,
8616
9080
  passesExtractedFloor as passesExtractedFloor3
8617
9081
  } from "@neat.is/types";
8618
9082
 
8619
9083
  // src/extract/calls/http.ts
8620
- import path33 from "path";
9084
+ import path36 from "path";
8621
9085
  import Parser6 from "tree-sitter";
8622
9086
  import JavaScript4 from "tree-sitter-javascript";
8623
9087
  import TypeScript2 from "tree-sitter-typescript";
@@ -8701,11 +9165,11 @@ async function addHttpCallEdges(graph, services) {
8701
9165
  let nodesAdded = 0;
8702
9166
  let edgesAdded = 0;
8703
9167
  for (const service of services) {
8704
- const files = await loadSourceFiles(service.dir);
9168
+ const files = await loadSourceFiles(service.dir, service.excludeDirs);
8705
9169
  const seen = /* @__PURE__ */ new Set();
8706
9170
  for (const file of files) {
8707
9171
  if (isTestPath(file.path)) continue;
8708
- const parser = parserForExt(path33.extname(file.path), parserCache);
9172
+ const parser = parserForExt(path36.extname(file.path), parserCache);
8709
9173
  let sites;
8710
9174
  try {
8711
9175
  sites = callsFromSource(file.content, parser, knownHosts);
@@ -8714,7 +9178,7 @@ async function addHttpCallEdges(graph, services) {
8714
9178
  continue;
8715
9179
  }
8716
9180
  if (sites.length === 0) continue;
8717
- const relFile = toPosix(path33.relative(service.dir, file.path));
9181
+ const relFile = toPosix(path36.relative(service.dir, file.path));
8718
9182
  for (const site of sites) {
8719
9183
  const targetId = hostToNodeId.get(site.host);
8720
9184
  if (!targetId || targetId === service.node.id) continue;
@@ -8767,16 +9231,16 @@ async function addHttpCallEdges(graph, services) {
8767
9231
  }
8768
9232
 
8769
9233
  // src/extract/calls/route-match.ts
8770
- import path34 from "path";
9234
+ import path37 from "path";
8771
9235
  import Parser7 from "tree-sitter";
8772
9236
  import JavaScript5 from "tree-sitter-javascript";
8773
9237
  import {
8774
9238
  EdgeType as EdgeType14,
8775
- NodeType as NodeType20,
9239
+ NodeType as NodeType23,
8776
9240
  Provenance as Provenance14,
8777
9241
  confidenceForExtracted as confidenceForExtracted11,
8778
9242
  passesExtractedFloor as passesExtractedFloor2,
8779
- serviceId as serviceId9
9243
+ serviceId as serviceId12
8780
9244
  } from "@neat.is/types";
8781
9245
  var PARSE_CHUNK5 = 16384;
8782
9246
  function parseSource5(parser, source) {
@@ -8942,9 +9406,9 @@ function buildRouteIndex(graph) {
8942
9406
  const index = /* @__PURE__ */ new Map();
8943
9407
  graph.forEachNode((_id, attrs) => {
8944
9408
  const node = attrs;
8945
- if (node.type !== NodeType20.RouteNode) return;
9409
+ if (node.type !== NodeType23.RouteNode) return;
8946
9410
  const route = attrs;
8947
- const owner = serviceId9(route.service);
9411
+ const owner = serviceId12(route.service);
8948
9412
  const entry = {
8949
9413
  method: route.method.toUpperCase(),
8950
9414
  normalizedPath: normalizePathTemplate(route.pathTemplate),
@@ -8969,11 +9433,11 @@ async function addRouteCallEdges(graph, services) {
8969
9433
  let nodesAdded = 0;
8970
9434
  let edgesAdded = 0;
8971
9435
  for (const service of services) {
8972
- const files = await loadSourceFiles(service.dir);
9436
+ const files = await loadSourceFiles(service.dir, service.excludeDirs);
8973
9437
  const seen = /* @__PURE__ */ new Set();
8974
9438
  for (const file of files) {
8975
9439
  if (isTestPath(file.path)) continue;
8976
- if (!JS_CLIENT_EXTENSIONS.has(path34.extname(file.path))) continue;
9440
+ if (!JS_CLIENT_EXTENSIONS.has(path37.extname(file.path))) continue;
8977
9441
  let sites;
8978
9442
  try {
8979
9443
  sites = clientCallSitesFromSource(file.content, jsParser, knownHosts);
@@ -8982,7 +9446,7 @@ async function addRouteCallEdges(graph, services) {
8982
9446
  continue;
8983
9447
  }
8984
9448
  if (sites.length === 0) continue;
8985
- const relFile = toPosix(path34.relative(service.dir, file.path));
9449
+ const relFile = toPosix(path37.relative(service.dir, file.path));
8986
9450
  for (const site of sites) {
8987
9451
  const serverServiceId = hostToNodeId.get(site.host);
8988
9452
  if (!serverServiceId || serverServiceId === service.node.id) continue;
@@ -9042,7 +9506,7 @@ async function addRouteCallEdges(graph, services) {
9042
9506
  }
9043
9507
 
9044
9508
  // src/extract/calls/kafka.ts
9045
- import path35 from "path";
9509
+ import path38 from "path";
9046
9510
  import { infraId as infraId2 } from "@neat.is/types";
9047
9511
  var PRODUCER_TOPIC_RE = /(?:producer|kafkaProducer)[\s\S]{0,40}?\.send\s*\(\s*\{[\s\S]{0,200}?topic\s*:\s*['"`]([^'"`]+)['"`]/g;
9048
9512
  var CONSUMER_TOPIC_RE = /(?:consumer|kafkaConsumer)[\s\S]{0,40}?\.(?:subscribe|run)\s*\(\s*\{[\s\S]{0,200}?topic[s]?\s*:\s*(?:\[\s*)?['"`]([^'"`]+)['"`]/g;
@@ -9073,7 +9537,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
9073
9537
  // tier (ADR-066).
9074
9538
  confidenceKind: "verified-call-site",
9075
9539
  evidence: {
9076
- file: path35.relative(serviceDir, file.path),
9540
+ file: path38.relative(serviceDir, file.path),
9077
9541
  line,
9078
9542
  snippet: snippet(file.content, line)
9079
9543
  }
@@ -9085,7 +9549,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
9085
9549
  }
9086
9550
 
9087
9551
  // src/extract/calls/redis.ts
9088
- import path36 from "path";
9552
+ import path39 from "path";
9089
9553
  import { infraId as infraId3 } from "@neat.is/types";
9090
9554
  var REDIS_URL_RE = /redis(?:s)?:\/\/(?:[^@'"`\s]+@)?([^:/'"`\s]+)(?::(\d+))?/g;
9091
9555
  function redisEndpointsFromFile(file, serviceDir) {
@@ -9108,7 +9572,7 @@ function redisEndpointsFromFile(file, serviceDir) {
9108
9572
  // support tier (ADR-066).
9109
9573
  confidenceKind: "url-with-structural-support",
9110
9574
  evidence: {
9111
- file: path36.relative(serviceDir, file.path),
9575
+ file: path39.relative(serviceDir, file.path),
9112
9576
  line,
9113
9577
  snippet: snippet(file.content, line)
9114
9578
  }
@@ -9118,7 +9582,7 @@ function redisEndpointsFromFile(file, serviceDir) {
9118
9582
  }
9119
9583
 
9120
9584
  // src/extract/calls/aws.ts
9121
- import path37 from "path";
9585
+ import path40 from "path";
9122
9586
  import { infraId as infraId4 } from "@neat.is/types";
9123
9587
  var S3_BUCKET_RE = /Bucket\s*:\s*['"`]([^'"`]+)['"`]/g;
9124
9588
  var DYNAMO_TABLE_RE = /TableName\s*:\s*['"`]([^'"`]+)['"`]/g;
@@ -9152,7 +9616,7 @@ function awsEndpointsFromFile(file, serviceDir) {
9152
9616
  // (ADR-066).
9153
9617
  confidenceKind: "verified-call-site",
9154
9618
  evidence: {
9155
- file: path37.relative(serviceDir, file.path),
9619
+ file: path40.relative(serviceDir, file.path),
9156
9620
  line,
9157
9621
  snippet: snippet(file.content, line)
9158
9622
  }
@@ -9176,7 +9640,7 @@ function awsEndpointsFromFile(file, serviceDir) {
9176
9640
  }
9177
9641
 
9178
9642
  // src/extract/calls/grpc.ts
9179
- import path38 from "path";
9643
+ import path41 from "path";
9180
9644
  import { infraId as infraId5 } from "@neat.is/types";
9181
9645
  var GRPC_CLIENT_RE = /new\s+([A-Z][A-Za-z0-9_]*)Client\s*\(\s*['"`]?([^,'"`)]+)?/g;
9182
9646
  var AWS_SDK_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@aws-sdk\/client-([a-z0-9-]+)['"`]/g;
@@ -9235,7 +9699,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
9235
9699
  // tier (ADR-066).
9236
9700
  confidenceKind: "verified-call-site",
9237
9701
  evidence: {
9238
- file: path38.relative(serviceDir, file.path),
9702
+ file: path41.relative(serviceDir, file.path),
9239
9703
  line,
9240
9704
  snippet: snippet(file.content, line)
9241
9705
  }
@@ -9245,7 +9709,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
9245
9709
  }
9246
9710
 
9247
9711
  // src/extract/calls/supabase.ts
9248
- import path39 from "path";
9712
+ import path42 from "path";
9249
9713
  import { infraId as infraId6 } from "@neat.is/types";
9250
9714
  var SUPABASE_JS_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@supabase\/supabase-js['"`]/;
9251
9715
  var SUPABASE_SSR_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@supabase\/ssr['"`]/;
@@ -9304,7 +9768,7 @@ function supabaseEndpointsFromFile(file, serviceDir) {
9304
9768
  // tier (ADR-066), the same grade aws.ts / grpc.ts emit at.
9305
9769
  confidenceKind: "verified-call-site",
9306
9770
  evidence: {
9307
- file: path39.relative(serviceDir, file.path),
9771
+ file: path42.relative(serviceDir, file.path),
9308
9772
  line,
9309
9773
  snippet: snippet(file.content, line)
9310
9774
  }
@@ -9331,7 +9795,7 @@ function supabaseEndpointsFromFile(file, serviceDir) {
9331
9795
  edgeType: "CALLS",
9332
9796
  confidenceKind: "verified-call-site",
9333
9797
  evidence: {
9334
- file: path39.relative(serviceDir, file.path),
9798
+ file: path42.relative(serviceDir, file.path),
9335
9799
  line,
9336
9800
  snippet: snippet(file.content, line)
9337
9801
  }
@@ -9342,7 +9806,7 @@ function supabaseEndpointsFromFile(file, serviceDir) {
9342
9806
  }
9343
9807
 
9344
9808
  // src/extract/calls/firestore.ts
9345
- import path40 from "path";
9809
+ import path43 from "path";
9346
9810
  import Parser8 from "tree-sitter";
9347
9811
  import JavaScript6 from "tree-sitter-javascript";
9348
9812
  import { infraId as infraId7 } from "@neat.is/types";
@@ -9513,7 +9977,7 @@ function firestoreEndpointsFromFile(file, serviceDir) {
9513
9977
  const hasAdmin = FIRESTORE_ADMIN_IMPORT_RE.test(file.content);
9514
9978
  if (!hasClient && !hasAdmin) return [];
9515
9979
  const fileSdk = hasClient && !hasAdmin ? "client" : hasAdmin && !hasClient ? "admin" : null;
9516
- const tree = parseSource3(parserForExt2(path40.extname(file.path)), file.content);
9980
+ const tree = parseSource3(parserForExt2(path43.extname(file.path)), file.content);
9517
9981
  const clientVars = firestoreClientVars(tree.rootNode);
9518
9982
  const collLine = /* @__PURE__ */ new Map();
9519
9983
  const writes = /* @__PURE__ */ new Map();
@@ -9617,7 +10081,7 @@ function firestoreEndpointsFromFile(file, serviceDir) {
9617
10081
  ...columnSet.size > 0 ? { columns: [...columnSet] } : {},
9618
10082
  ...sdkWrites ? { sdkWrites } : {},
9619
10083
  evidence: {
9620
- file: path40.relative(serviceDir, file.path),
10084
+ file: path43.relative(serviceDir, file.path),
9621
10085
  line,
9622
10086
  snippet: snippet(file.content, line)
9623
10087
  }
@@ -9627,7 +10091,7 @@ function firestoreEndpointsFromFile(file, serviceDir) {
9627
10091
  }
9628
10092
 
9629
10093
  // src/extract/calls/mongoose.ts
9630
- import path41 from "path";
10094
+ import path44 from "path";
9631
10095
  import { infraId as infraId8 } from "@neat.is/types";
9632
10096
  var MONGOOSE_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])mongoose['"`]/;
9633
10097
  var MONGODB_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])mongodb['"`]/;
@@ -9778,7 +10242,7 @@ function endpoint(r, file, serviceDir, matchText) {
9778
10242
  kind: r.kind,
9779
10243
  edgeType: "CALLS",
9780
10244
  confidenceKind: "verified-call-site",
9781
- evidence: { file: path41.relative(serviceDir, file.path), line, snippet: snippet(file.content, line) }
10245
+ evidence: { file: path44.relative(serviceDir, file.path), line, snippet: snippet(file.content, line) }
9782
10246
  };
9783
10247
  }
9784
10248
  function mongooseEndpointsFromFile(file, serviceDir) {
@@ -9868,7 +10332,7 @@ async function mongooseCrossFileEndpoints(files, serviceDir) {
9868
10332
  const registry = /* @__PURE__ */ new Map();
9869
10333
  for (const f of mongooseFiles) {
9870
10334
  const fx = fileExportsOf(f.content, pluralizeOn);
9871
- if (fx) registry.set(toPosix(path41.relative(serviceDir, f.path)), fx);
10335
+ if (fx) registry.set(toPosix(path44.relative(serviceDir, f.path)), fx);
9872
10336
  }
9873
10337
  if (registry.size === 0) return [];
9874
10338
  const out = [];
@@ -9879,7 +10343,7 @@ async function mongooseCrossFileEndpoints(files, serviceDir) {
9879
10343
  const directColl = /* @__PURE__ */ new Map();
9880
10344
  const nsExports = /* @__PURE__ */ new Map();
9881
10345
  for (const b of bindings) {
9882
- const resolvedRel = await resolveJsImport(b.specifier, path41.dirname(f.path), serviceDir, null);
10346
+ const resolvedRel = await resolveJsImport(b.specifier, path44.dirname(f.path), serviceDir, null);
9883
10347
  if (!resolvedRel) continue;
9884
10348
  const fx = registry.get(resolvedRel);
9885
10349
  if (!fx) continue;
@@ -9922,7 +10386,7 @@ async function mongooseCrossFileEndpoints(files, serviceDir) {
9922
10386
  }
9923
10387
 
9924
10388
  // src/extract/calls/sqlalchemy.ts
9925
- import path42 from "path";
10389
+ import path45 from "path";
9926
10390
  import Parser9 from "tree-sitter";
9927
10391
  import Python5 from "tree-sitter-python";
9928
10392
  import { infraId as infraId9 } from "@neat.is/types";
@@ -10061,7 +10525,7 @@ function sqlalchemyForeignKeys(file, serviceDir) {
10061
10525
  childTable,
10062
10526
  parentTable,
10063
10527
  evidence: {
10064
- file: path42.relative(serviceDir, file.path),
10528
+ file: path45.relative(serviceDir, file.path),
10065
10529
  line,
10066
10530
  snippet: snippet(file.content, line)
10067
10531
  }
@@ -10086,7 +10550,7 @@ function sqlalchemyEndpointsFromFile(file, serviceDir) {
10086
10550
  confidenceKind: "verified-call-site",
10087
10551
  ...columns && columns.length > 0 ? { columns } : {},
10088
10552
  evidence: {
10089
- file: path42.relative(serviceDir, file.path),
10553
+ file: path45.relative(serviceDir, file.path),
10090
10554
  line,
10091
10555
  snippet: snippet(file.content, line)
10092
10556
  }
@@ -10193,7 +10657,7 @@ function pythonOrmCrossFileEndpoints(files, serviceDir) {
10193
10657
  edgeType: "CALLS",
10194
10658
  confidenceKind: "verified-call-site",
10195
10659
  evidence: {
10196
- file: path42.relative(serviceDir, file.path),
10660
+ file: path45.relative(serviceDir, file.path),
10197
10661
  line,
10198
10662
  snippet: snippet(file.content, line)
10199
10663
  }
@@ -10204,7 +10668,7 @@ function pythonOrmCrossFileEndpoints(files, serviceDir) {
10204
10668
  }
10205
10669
 
10206
10670
  // src/extract/calls/django-orm.ts
10207
- import path43 from "path";
10671
+ import path46 from "path";
10208
10672
  import Parser10 from "tree-sitter";
10209
10673
  import Python6 from "tree-sitter-python";
10210
10674
  import { infraId as infraId10 } from "@neat.is/types";
@@ -10274,7 +10738,7 @@ function djangoOrmEndpointsFromFile(file, serviceDir) {
10274
10738
  const tree = parseSource7(makePyParser4(), file.content);
10275
10739
  const out = [];
10276
10740
  const seen = /* @__PURE__ */ new Set();
10277
- const defaultAppLabel = path43.basename(path43.dirname(file.path));
10741
+ const defaultAppLabel = path46.basename(path46.dirname(file.path));
10278
10742
  walk4(tree.rootNode, (node) => {
10279
10743
  if (node.type !== "class_definition") return;
10280
10744
  if (!extendsDjangoModel(node)) return;
@@ -10292,14 +10756,14 @@ function djangoOrmEndpointsFromFile(file, serviceDir) {
10292
10756
  kind: "sql-table",
10293
10757
  edgeType: "CALLS",
10294
10758
  confidenceKind: "verified-call-site",
10295
- evidence: { file: path43.relative(serviceDir, file.path), line, snippet: snippet(file.content, line) }
10759
+ evidence: { file: path46.relative(serviceDir, file.path), line, snippet: snippet(file.content, line) }
10296
10760
  });
10297
10761
  });
10298
10762
  return out;
10299
10763
  }
10300
10764
 
10301
10765
  // src/extract/calls/drizzle.ts
10302
- import path44 from "path";
10766
+ import path47 from "path";
10303
10767
  import Parser11 from "tree-sitter";
10304
10768
  import JavaScript7 from "tree-sitter-javascript";
10305
10769
  import { infraId as infraId11 } from "@neat.is/types";
@@ -10377,7 +10841,7 @@ function columnsFromObject(obj) {
10377
10841
  }
10378
10842
  function drizzleEndpointsFromFile(file, serviceDir) {
10379
10843
  if (!DRIZZLE_IMPORT_RE.test(file.content)) return [];
10380
- const tree = parseSource3(parserForExt3(path44.extname(file.path)), file.content);
10844
+ const tree = parseSource3(parserForExt3(path47.extname(file.path)), file.content);
10381
10845
  const out = [];
10382
10846
  const seen = /* @__PURE__ */ new Set();
10383
10847
  const walk9 = (node) => {
@@ -10400,7 +10864,7 @@ function drizzleEndpointsFromFile(file, serviceDir) {
10400
10864
  confidenceKind: "structural",
10401
10865
  columns,
10402
10866
  evidence: {
10403
- file: path44.relative(serviceDir, file.path),
10867
+ file: path47.relative(serviceDir, file.path),
10404
10868
  line,
10405
10869
  snippet: snippet(file.content, line)
10406
10870
  }
@@ -10466,7 +10930,7 @@ function referencesTargetVar(call) {
10466
10930
  }
10467
10931
  function drizzleForeignKeys(file, serviceDir) {
10468
10932
  if (!DRIZZLE_IMPORT_RE.test(file.content)) return [];
10469
- const tree = parseSource3(parserForExt3(path44.extname(file.path)), file.content);
10933
+ const tree = parseSource3(parserForExt3(path47.extname(file.path)), file.content);
10470
10934
  const { tables, varToTable } = collectDrizzleTables(tree.rootNode);
10471
10935
  const out = [];
10472
10936
  const seen = /* @__PURE__ */ new Set();
@@ -10485,7 +10949,7 @@ function drizzleForeignKeys(file, serviceDir) {
10485
10949
  childTable: table.tableName,
10486
10950
  parentTable,
10487
10951
  evidence: {
10488
- file: path44.relative(serviceDir, file.path),
10952
+ file: path47.relative(serviceDir, file.path),
10489
10953
  line,
10490
10954
  snippet: snippet(file.content, line)
10491
10955
  }
@@ -10501,7 +10965,7 @@ function drizzleForeignKeys(file, serviceDir) {
10501
10965
  }
10502
10966
 
10503
10967
  // src/extract/calls/prisma.ts
10504
- import path45 from "path";
10968
+ import path48 from "path";
10505
10969
  import { infraId as infraId12 } from "@neat.is/types";
10506
10970
  var SCALAR_TYPES = /* @__PURE__ */ new Set([
10507
10971
  "Int",
@@ -10557,7 +11021,7 @@ function prismaColumnsFromSchema(file, serviceDir) {
10557
11021
  confidenceKind: "structural",
10558
11022
  columns: b.columns,
10559
11023
  evidence: {
10560
- file: path45.relative(serviceDir, file.path),
11024
+ file: path48.relative(serviceDir, file.path),
10561
11025
  line: b.startLine,
10562
11026
  snippet: snippet(content, b.startLine)
10563
11027
  }
@@ -10618,7 +11082,7 @@ function prismaColumnsFromSchema(file, serviceDir) {
10618
11082
  }
10619
11083
  async function prismaColumnEndpoints(serviceDir) {
10620
11084
  const schemaPath = await findFirst(serviceDir, [
10621
- path45.join("prisma", "schema.prisma"),
11085
+ path48.join("prisma", "schema.prisma"),
10622
11086
  "schema.prisma"
10623
11087
  ]);
10624
11088
  if (!schemaPath) return [];
@@ -10693,7 +11157,7 @@ function prismaForeignKeysFromSchema(file, serviceDir) {
10693
11157
  childTable: current.table,
10694
11158
  parentTable,
10695
11159
  evidence: {
10696
- file: path45.relative(serviceDir, file.path),
11160
+ file: path48.relative(serviceDir, file.path),
10697
11161
  line: lineNo,
10698
11162
  snippet: snippet(content, lineNo)
10699
11163
  }
@@ -10708,7 +11172,7 @@ function prismaForeignKeysFromSchema(file, serviceDir) {
10708
11172
  }
10709
11173
  async function prismaForeignKeys(serviceDir) {
10710
11174
  const schemaPath = await findFirst(serviceDir, [
10711
- path45.join("prisma", "schema.prisma"),
11175
+ path48.join("prisma", "schema.prisma"),
10712
11176
  "schema.prisma"
10713
11177
  ]);
10714
11178
  if (!schemaPath) return [];
@@ -10718,7 +11182,7 @@ async function prismaForeignKeys(serviceDir) {
10718
11182
  }
10719
11183
 
10720
11184
  // src/extract/calls/activerecord.ts
10721
- import path46 from "path";
11185
+ import path49 from "path";
10722
11186
  import Parser12 from "tree-sitter";
10723
11187
  import Ruby3 from "tree-sitter-ruby";
10724
11188
  import { infraId as infraId13 } from "@neat.is/types";
@@ -10926,7 +11390,7 @@ function railsSchemaEndpointsFromFile(file, serviceDir) {
10926
11390
  confidenceKind: "structural",
10927
11391
  ...table.columns.length > 0 ? { columns: table.columns } : {},
10928
11392
  evidence: {
10929
- file: path46.relative(serviceDir, file.path),
11393
+ file: path49.relative(serviceDir, file.path),
10930
11394
  line: table.line,
10931
11395
  snippet: snippet(file.content, table.line)
10932
11396
  }
@@ -10948,7 +11412,7 @@ function railsSchemaForeignKeys(file, serviceDir) {
10948
11412
  childTable,
10949
11413
  parentTable,
10950
11414
  evidence: {
10951
- file: path46.relative(serviceDir, file.path),
11415
+ file: path49.relative(serviceDir, file.path),
10952
11416
  line,
10953
11417
  snippet: snippet(file.content, line)
10954
11418
  }
@@ -11031,7 +11495,7 @@ function railsModelEndpointsFromFile(file, serviceDir) {
11031
11495
  edgeType: "CALLS",
11032
11496
  confidenceKind: "verified-call-site",
11033
11497
  evidence: {
11034
- file: path46.relative(serviceDir, file.path),
11498
+ file: path49.relative(serviceDir, file.path),
11035
11499
  line,
11036
11500
  snippet: snippet(file.content, line)
11037
11501
  }
@@ -11068,7 +11532,7 @@ function railsModelForeignKeys(file, serviceDir) {
11068
11532
  childTable,
11069
11533
  parentTable,
11070
11534
  evidence: {
11071
- file: path46.relative(serviceDir, file.path),
11535
+ file: path49.relative(serviceDir, file.path),
11072
11536
  line,
11073
11537
  snippet: snippet(file.content, line)
11074
11538
  }
@@ -11079,7 +11543,7 @@ function railsModelForeignKeys(file, serviceDir) {
11079
11543
  }
11080
11544
 
11081
11545
  // src/extract/calls/eloquent.ts
11082
- import path47 from "path";
11546
+ import path50 from "path";
11083
11547
  import Parser13 from "tree-sitter";
11084
11548
  import Php3 from "tree-sitter-php";
11085
11549
  import { infraId as infraId14 } from "@neat.is/types";
@@ -11336,7 +11800,7 @@ function laravelMigrationEndpointsFromFile(file, serviceDir) {
11336
11800
  confidenceKind: "structural",
11337
11801
  ...columns.length > 0 ? { columns } : {},
11338
11802
  evidence: {
11339
- file: path47.relative(serviceDir, file.path),
11803
+ file: path50.relative(serviceDir, file.path),
11340
11804
  line: bp.line,
11341
11805
  snippet: snippet(file.content, bp.line)
11342
11806
  }
@@ -11358,7 +11822,7 @@ function laravelMigrationForeignKeys(file, serviceDir) {
11358
11822
  childTable,
11359
11823
  parentTable,
11360
11824
  evidence: {
11361
- file: path47.relative(serviceDir, file.path),
11825
+ file: path50.relative(serviceDir, file.path),
11362
11826
  line,
11363
11827
  snippet: snippet(file.content, line)
11364
11828
  }
@@ -11478,7 +11942,7 @@ function laravelModelEndpointsFromFile(file, serviceDir) {
11478
11942
  edgeType: "CALLS",
11479
11943
  confidenceKind: "verified-call-site",
11480
11944
  evidence: {
11481
- file: path47.relative(serviceDir, file.path),
11945
+ file: path50.relative(serviceDir, file.path),
11482
11946
  line,
11483
11947
  snippet: snippet(file.content, line)
11484
11948
  }
@@ -11514,7 +11978,7 @@ function laravelModelForeignKeys(file, serviceDir) {
11514
11978
  childTable,
11515
11979
  parentTable,
11516
11980
  evidence: {
11517
- file: path47.relative(serviceDir, file.path),
11981
+ file: path50.relative(serviceDir, file.path),
11518
11982
  line,
11519
11983
  snippet: snippet(file.content, line)
11520
11984
  }
@@ -11525,7 +11989,7 @@ function laravelModelForeignKeys(file, serviceDir) {
11525
11989
  }
11526
11990
 
11527
11991
  // src/extract/calls/go.ts
11528
- import path48 from "path";
11992
+ import path51 from "path";
11529
11993
  import Parser14 from "tree-sitter";
11530
11994
  import Go4 from "tree-sitter-go";
11531
11995
  import { infraId as infraId15 } from "@neat.is/types";
@@ -11599,7 +12063,7 @@ function firstStringLiteralArg(argsNode) {
11599
12063
  return null;
11600
12064
  }
11601
12065
  function goSqlEndpointsFromFile(file, serviceDir) {
11602
- if (path48.extname(file.path) !== ".go") return [];
12066
+ if (path51.extname(file.path) !== ".go") return [];
11603
12067
  if (!file.content.includes(DATABASE_SQL_IMPORT) && !file.content.includes(SQLX_IMPORT)) return [];
11604
12068
  const tree = parseSource10(makeGoParser3(), file.content);
11605
12069
  const importsDatabaseSql = goImportsAny(tree.rootNode, /* @__PURE__ */ new Set([DATABASE_SQL_IMPORT]));
@@ -11628,7 +12092,7 @@ function goSqlEndpointsFromFile(file, serviceDir) {
11628
12092
  confidenceKind: "verified-call-site",
11629
12093
  ...columns.length > 0 ? { columns } : {},
11630
12094
  evidence: {
11631
- file: toPosix(path48.relative(serviceDir, file.path)),
12095
+ file: toPosix(path51.relative(serviceDir, file.path)),
11632
12096
  line,
11633
12097
  snippet: snippet(file.content, line)
11634
12098
  }
@@ -11638,7 +12102,7 @@ function goSqlEndpointsFromFile(file, serviceDir) {
11638
12102
  }
11639
12103
 
11640
12104
  // src/extract/calls/gorm.ts
11641
- import path49 from "path";
12105
+ import path52 from "path";
11642
12106
  import Parser15 from "tree-sitter";
11643
12107
  import Go5 from "tree-sitter-go";
11644
12108
  import { infraId as infraId16 } from "@neat.is/types";
@@ -12071,7 +12535,7 @@ function collectColumns(struct, structs, seen, prefix, out, emitted) {
12071
12535
  seen.delete(struct.name);
12072
12536
  }
12073
12537
  function gormEndpointsFromFile(file, serviceDir) {
12074
- if (path49.extname(file.path) !== ".go") return [];
12538
+ if (path52.extname(file.path) !== ".go") return [];
12075
12539
  if (!GORM_IMPORT_RE.test(file.content)) return [];
12076
12540
  const tree = parseSource11(makeGoParser4(), file.content);
12077
12541
  const { structs, models, tableFor } = analyze(tree);
@@ -12093,7 +12557,7 @@ function gormEndpointsFromFile(file, serviceDir) {
12093
12557
  confidenceKind: "structural",
12094
12558
  ...columns.length > 0 ? { columns } : {},
12095
12559
  evidence: {
12096
- file: toPosix(path49.relative(serviceDir, file.path)),
12560
+ file: toPosix(path52.relative(serviceDir, file.path)),
12097
12561
  line: struct.line,
12098
12562
  snippet: snippet(file.content, struct.line)
12099
12563
  }
@@ -12102,7 +12566,7 @@ function gormEndpointsFromFile(file, serviceDir) {
12102
12566
  return out;
12103
12567
  }
12104
12568
  function gormForeignKeys(file, serviceDir) {
12105
- if (path49.extname(file.path) !== ".go") return [];
12569
+ if (path52.extname(file.path) !== ".go") return [];
12106
12570
  if (!GORM_IMPORT_RE.test(file.content)) return [];
12107
12571
  const tree = parseSource11(makeGoParser4(), file.content);
12108
12572
  const { structs, models, tableFor } = analyze(tree);
@@ -12117,7 +12581,7 @@ function gormForeignKeys(file, serviceDir) {
12117
12581
  childTable,
12118
12582
  parentTable,
12119
12583
  evidence: {
12120
- file: toPosix(path49.relative(serviceDir, file.path)),
12584
+ file: toPosix(path52.relative(serviceDir, file.path)),
12121
12585
  line,
12122
12586
  snippet: snippet(file.content, line)
12123
12587
  }
@@ -12170,7 +12634,7 @@ async function addExternalEndpointEdges(graph, services) {
12170
12634
  let nodesAdded = 0;
12171
12635
  let edgesAdded = 0;
12172
12636
  for (const service of services) {
12173
- const files = await loadSourceFiles(service.dir);
12637
+ const files = await loadSourceFiles(service.dir, service.excludeDirs);
12174
12638
  const endpoints = [];
12175
12639
  const maskedFiles = [];
12176
12640
  for (const file of files) {
@@ -12220,7 +12684,7 @@ async function addExternalEndpointEdges(graph, services) {
12220
12684
  if (!graph.hasNode(ep.infraId)) {
12221
12685
  const node = {
12222
12686
  id: ep.infraId,
12223
- type: NodeType21.InfraNode,
12687
+ type: NodeType24.InfraNode,
12224
12688
  name: ep.name,
12225
12689
  // #238 — `aws-*` covers AWS-SDK client kinds (aws-s3, aws-dynamodb,
12226
12690
  // aws-cognito-identity-provider, …); `s3-` / `dynamodb-` cover the
@@ -12233,7 +12697,7 @@ async function addExternalEndpointEdges(graph, services) {
12233
12697
  }
12234
12698
  if (ep.columns && ep.columns.length > 0) {
12235
12699
  const node = graph.getNodeAttributes(ep.infraId);
12236
- if (node.type === NodeType21.InfraNode) {
12700
+ if (node.type === NodeType24.InfraNode) {
12237
12701
  graph.replaceNodeAttributes(ep.infraId, {
12238
12702
  ...node,
12239
12703
  columns: foldColumns(
@@ -12247,7 +12711,7 @@ async function addExternalEndpointEdges(graph, services) {
12247
12711
  }
12248
12712
  if (ep.sdkWrites && Object.keys(ep.sdkWrites).length > 0) {
12249
12713
  const node = graph.getNodeAttributes(ep.infraId);
12250
- if (node.type === NodeType21.InfraNode) {
12714
+ if (node.type === NodeType24.InfraNode) {
12251
12715
  graph.replaceNodeAttributes(ep.infraId, {
12252
12716
  ...node,
12253
12717
  columns: foldSdkWrites(node.columns, ep.sdkWrites)
@@ -12309,7 +12773,7 @@ async function addCallEdges(graph, services) {
12309
12773
  // src/extract/table-edges.ts
12310
12774
  import {
12311
12775
  EdgeType as EdgeType16,
12312
- NodeType as NodeType22,
12776
+ NodeType as NodeType25,
12313
12777
  Provenance as Provenance16,
12314
12778
  confidenceForExtracted as confidenceForExtracted13,
12315
12779
  extractedEdgeId as extractedEdgeId10,
@@ -12319,7 +12783,7 @@ async function addTableEdges(graph, services) {
12319
12783
  let nodesAdded = 0;
12320
12784
  let edgesAdded = 0;
12321
12785
  for (const service of services) {
12322
- const files = await loadSourceFiles(service.dir);
12786
+ const files = await loadSourceFiles(service.dir, service.excludeDirs);
12323
12787
  const refs = [];
12324
12788
  const modelRefs = [];
12325
12789
  for (const file of files) {
@@ -12368,7 +12832,7 @@ function ensureTableNode(graph, id, name) {
12368
12832
  if (graph.hasNode(id)) return 0;
12369
12833
  const node = {
12370
12834
  id,
12371
- type: NodeType22.InfraNode,
12835
+ type: NodeType25.InfraNode,
12372
12836
  name,
12373
12837
  provider: "self",
12374
12838
  kind: "sql-table"
@@ -12378,15 +12842,15 @@ function ensureTableNode(graph, id, name) {
12378
12842
  }
12379
12843
 
12380
12844
  // src/extract/infra/docker-compose.ts
12381
- import path50 from "path";
12845
+ import path53 from "path";
12382
12846
  import { EdgeType as EdgeType17, Provenance as Provenance18, confidenceForExtracted as confidenceForExtracted15 } from "@neat.is/types";
12383
12847
 
12384
12848
  // src/extract/infra/shared.ts
12385
- import { NodeType as NodeType23, Provenance as Provenance17, confidenceForExtracted as confidenceForExtracted14, infraId as infraId18 } from "@neat.is/types";
12849
+ import { NodeType as NodeType26, Provenance as Provenance17, confidenceForExtracted as confidenceForExtracted14, infraId as infraId18 } from "@neat.is/types";
12386
12850
  function makeInfraNode(kind, name, provider = "self", extras) {
12387
12851
  return {
12388
12852
  id: infraId18(kind, name),
12389
- type: NodeType23.InfraNode,
12853
+ type: NodeType26.InfraNode,
12390
12854
  name,
12391
12855
  provider,
12392
12856
  kind,
@@ -12448,7 +12912,7 @@ function dependsOnList(value) {
12448
12912
  }
12449
12913
  function serviceNameToServiceNode(name, services) {
12450
12914
  for (const s of services) {
12451
- if (s.node.name === name || path50.basename(s.dir) === name) return s.node.id;
12915
+ if (s.node.name === name || path53.basename(s.dir) === name) return s.node.id;
12452
12916
  }
12453
12917
  return null;
12454
12918
  }
@@ -12457,7 +12921,7 @@ async function addComposeInfra(graph, scanPath, services) {
12457
12921
  let edgesAdded = 0;
12458
12922
  let composePath = null;
12459
12923
  for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
12460
- const abs = path50.join(scanPath, name);
12924
+ const abs = path53.join(scanPath, name);
12461
12925
  if (await exists(abs)) {
12462
12926
  composePath = abs;
12463
12927
  break;
@@ -12470,13 +12934,13 @@ async function addComposeInfra(graph, scanPath, services) {
12470
12934
  } catch (err) {
12471
12935
  recordExtractionError(
12472
12936
  "infra docker-compose",
12473
- path50.relative(scanPath, composePath),
12937
+ path53.relative(scanPath, composePath),
12474
12938
  err
12475
12939
  );
12476
12940
  return { nodesAdded, edgesAdded };
12477
12941
  }
12478
12942
  if (!compose?.services) return { nodesAdded, edgesAdded };
12479
- const evidenceFile = path50.relative(scanPath, composePath).split(path50.sep).join("/");
12943
+ const evidenceFile = path53.relative(scanPath, composePath).split(path53.sep).join("/");
12480
12944
  const composeNameToNodeId = /* @__PURE__ */ new Map();
12481
12945
  for (const [composeName, svc] of Object.entries(compose.services)) {
12482
12946
  const matchedServiceId = serviceNameToServiceNode(composeName, services);
@@ -12517,8 +12981,8 @@ async function addComposeInfra(graph, scanPath, services) {
12517
12981
  }
12518
12982
 
12519
12983
  // src/extract/infra/dockerfile.ts
12520
- import path51 from "path";
12521
- import { promises as fs21 } from "fs";
12984
+ import path54 from "path";
12985
+ import { promises as fs24 } from "fs";
12522
12986
  import { EdgeType as EdgeType18, Provenance as Provenance19, confidenceForExtracted as confidenceForExtracted16 } from "@neat.is/types";
12523
12987
  function readDockerfile(content) {
12524
12988
  let image = null;
@@ -12548,15 +13012,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
12548
13012
  let nodesAdded = 0;
12549
13013
  let edgesAdded = 0;
12550
13014
  for (const service of services) {
12551
- const dockerfilePath = path51.join(service.dir, "Dockerfile");
13015
+ const dockerfilePath = path54.join(service.dir, "Dockerfile");
12552
13016
  if (!await exists(dockerfilePath)) continue;
12553
13017
  let content;
12554
13018
  try {
12555
- content = await fs21.readFile(dockerfilePath, "utf8");
13019
+ content = await fs24.readFile(dockerfilePath, "utf8");
12556
13020
  } catch (err) {
12557
13021
  recordExtractionError(
12558
13022
  "infra dockerfile",
12559
- path51.relative(scanPath, dockerfilePath),
13023
+ path54.relative(scanPath, dockerfilePath),
12560
13024
  err
12561
13025
  );
12562
13026
  continue;
@@ -12568,8 +13032,8 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
12568
13032
  graph.addNode(node.id, node);
12569
13033
  nodesAdded++;
12570
13034
  }
12571
- const relDockerfile = toPosix(path51.relative(service.dir, dockerfilePath));
12572
- const evidenceFile = toPosix(path51.relative(scanPath, dockerfilePath));
13035
+ const relDockerfile = toPosix(path54.relative(service.dir, dockerfilePath));
13036
+ const evidenceFile = toPosix(path54.relative(scanPath, dockerfilePath));
12573
13037
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
12574
13038
  graph,
12575
13039
  service.pkg.name,
@@ -12620,23 +13084,23 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
12620
13084
  }
12621
13085
 
12622
13086
  // src/extract/infra/terraform.ts
12623
- import { promises as fs22 } from "fs";
12624
- import path52 from "path";
13087
+ import { promises as fs25 } from "fs";
13088
+ import path55 from "path";
12625
13089
  import { EdgeType as EdgeType19, Provenance as Provenance20, confidenceForExtracted as confidenceForExtracted17 } from "@neat.is/types";
12626
13090
  var RESOURCE_RE = /resource\s+"(aws_[A-Za-z0-9_]+)"\s+"([A-Za-z0-9_-]+)"/g;
12627
13091
  var REFERENCE_RE = /(?<![\w.])(aws_[A-Za-z0-9_]+)\.([A-Za-z0-9_-]+)/g;
12628
13092
  async function walkTfFiles(start, depth = 0, max = 5) {
12629
13093
  if (depth > max) return [];
12630
13094
  const out = [];
12631
- const entries = await fs22.readdir(start, { withFileTypes: true }).catch(() => []);
13095
+ const entries = await fs25.readdir(start, { withFileTypes: true }).catch(() => []);
12632
13096
  for (const entry of entries) {
12633
13097
  if (entry.isDirectory()) {
12634
13098
  if (IGNORED_DIRS.has(entry.name) || entry.name === ".terraform") continue;
12635
- const child = path52.join(start, entry.name);
13099
+ const child = path55.join(start, entry.name);
12636
13100
  if (await isPythonVenvDir(child)) continue;
12637
13101
  out.push(...await walkTfFiles(child, depth + 1, max));
12638
13102
  } else if (entry.isFile() && entry.name.endsWith(".tf")) {
12639
- out.push(path52.join(start, entry.name));
13103
+ out.push(path55.join(start, entry.name));
12640
13104
  }
12641
13105
  }
12642
13106
  return out;
@@ -12667,8 +13131,8 @@ async function addTerraformResources(graph, scanPath) {
12667
13131
  let edgesAdded = 0;
12668
13132
  const files = await walkTfFiles(scanPath);
12669
13133
  for (const file of files) {
12670
- const content = await fs22.readFile(file, "utf8");
12671
- const evidenceFile = toPosix(path52.relative(scanPath, file));
13134
+ const content = await fs25.readFile(file, "utf8");
13135
+ const evidenceFile = toPosix(path55.relative(scanPath, file));
12672
13136
  const resources = [];
12673
13137
  const byKey = /* @__PURE__ */ new Map();
12674
13138
  RESOURCE_RE.lastIndex = 0;
@@ -12724,8 +13188,8 @@ async function addTerraformResources(graph, scanPath) {
12724
13188
  }
12725
13189
 
12726
13190
  // src/extract/infra/k8s.ts
12727
- import { promises as fs23 } from "fs";
12728
- import path53 from "path";
13191
+ import { promises as fs26 } from "fs";
13192
+ import path56 from "path";
12729
13193
  import { parseAllDocuments as parseAllDocuments2 } from "yaml";
12730
13194
  var K8S_KIND_TO_INFRA_KIND = {
12731
13195
  Service: "k8s-service",
@@ -12739,15 +13203,15 @@ var K8S_KIND_TO_INFRA_KIND = {
12739
13203
  async function walkYamlFiles2(start, depth = 0, max = 5) {
12740
13204
  if (depth > max) return [];
12741
13205
  const out = [];
12742
- const entries = await fs23.readdir(start, { withFileTypes: true }).catch(() => []);
13206
+ const entries = await fs26.readdir(start, { withFileTypes: true }).catch(() => []);
12743
13207
  for (const entry of entries) {
12744
13208
  if (entry.isDirectory()) {
12745
13209
  if (IGNORED_DIRS.has(entry.name)) continue;
12746
- const child = path53.join(start, entry.name);
13210
+ const child = path56.join(start, entry.name);
12747
13211
  if (await isPythonVenvDir(child)) continue;
12748
13212
  out.push(...await walkYamlFiles2(child, depth + 1, max));
12749
- } else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(path53.extname(entry.name))) {
12750
- out.push(path53.join(start, entry.name));
13213
+ } else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(path56.extname(entry.name))) {
13214
+ out.push(path56.join(start, entry.name));
12751
13215
  }
12752
13216
  }
12753
13217
  return out;
@@ -12756,7 +13220,7 @@ async function addK8sResources(graph, scanPath) {
12756
13220
  let nodesAdded = 0;
12757
13221
  const files = await walkYamlFiles2(scanPath);
12758
13222
  for (const file of files) {
12759
- const content = await fs23.readFile(file, "utf8");
13223
+ const content = await fs26.readFile(file, "utf8");
12760
13224
  let docs;
12761
13225
  try {
12762
13226
  docs = parseAllDocuments2(content).map((d) => d.toJSON());
@@ -12779,17 +13243,17 @@ async function addK8sResources(graph, scanPath) {
12779
13243
  }
12780
13244
 
12781
13245
  // src/extract/infra/cloudflare.ts
12782
- import { promises as fs24 } from "fs";
12783
- import path54 from "path";
12784
- import { parse as parseToml2 } from "smol-toml";
13246
+ import { promises as fs27 } from "fs";
13247
+ import path57 from "path";
13248
+ import { parse as parseToml3 } from "smol-toml";
12785
13249
  import { EdgeType as EdgeType20, Provenance as Provenance21, confidenceForExtracted as confidenceForExtracted18 } from "@neat.is/types";
12786
13250
  var WRANGLER_FILENAMES = ["wrangler.toml", "wrangler.jsonc", "wrangler.json"];
12787
13251
  async function readWranglerConfig(dir) {
12788
13252
  for (const filename of WRANGLER_FILENAMES) {
12789
- const abs = path54.join(dir, filename);
13253
+ const abs = path57.join(dir, filename);
12790
13254
  if (!await exists(abs)) continue;
12791
- const raw = await fs24.readFile(abs, "utf8");
12792
- const config = filename === "wrangler.toml" ? parseToml2(raw) : JSON.parse(maskCommentsInSource(raw));
13255
+ const raw = await fs27.readFile(abs, "utf8");
13256
+ const config = filename === "wrangler.toml" ? parseToml3(raw) : JSON.parse(maskCommentsInSource(raw));
12793
13257
  return { config, relFile: filename, raw };
12794
13258
  }
12795
13259
  return null;
@@ -12849,11 +13313,11 @@ async function addCloudflareWorkers(graph, services, scanPath) {
12849
13313
  try {
12850
13314
  read = await readWranglerConfig(service.dir);
12851
13315
  } catch (err) {
12852
- recordExtractionError("infra cloudflare", path54.relative(scanPath, service.dir), err);
13316
+ recordExtractionError("infra cloudflare", path57.relative(scanPath, service.dir), err);
12853
13317
  continue;
12854
13318
  }
12855
13319
  if (!read || !read.config.name) continue;
12856
- const evidenceFile = toPosix(path54.relative(scanPath, path54.join(service.dir, read.relFile)));
13320
+ const evidenceFile = toPosix(path57.relative(scanPath, path57.join(service.dir, read.relFile)));
12857
13321
  discovered.push({ service, config: read.config, relFile: read.relFile, raw: read.raw, evidenceFile });
12858
13322
  }
12859
13323
  for (const worker of discovered) {
@@ -12865,7 +13329,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
12865
13329
  }
12866
13330
  let anchorId = service.node.id;
12867
13331
  if (config.main) {
12868
- const entryRelPath = toPosix(path54.normalize(config.main));
13332
+ const entryRelPath = toPosix(path57.normalize(config.main));
12869
13333
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
12870
13334
  graph,
12871
13335
  service.pkg.name,
@@ -13011,24 +13475,24 @@ async function addCloudflareWorkers(graph, services, scanPath) {
13011
13475
  }
13012
13476
 
13013
13477
  // src/extract/infra/vercel.ts
13014
- import { promises as fs25 } from "fs";
13015
- import path55 from "path";
13478
+ import { promises as fs28 } from "fs";
13479
+ import path58 from "path";
13016
13480
  import { EdgeType as EdgeType21 } from "@neat.is/types";
13017
13481
  var VERCEL_CONFIG_FILENAMES = ["vercel.json", "vercel.jsonc"];
13018
13482
  async function readVercelConfig(dir) {
13019
13483
  for (const filename of VERCEL_CONFIG_FILENAMES) {
13020
- const abs = path55.join(dir, filename);
13484
+ const abs = path58.join(dir, filename);
13021
13485
  if (!await exists(abs)) continue;
13022
- const raw = await fs25.readFile(abs, "utf8");
13486
+ const raw = await fs28.readFile(abs, "utf8");
13023
13487
  const config = JSON.parse(maskCommentsInSource(raw));
13024
13488
  return { config, relFile: filename, raw };
13025
13489
  }
13026
13490
  return null;
13027
13491
  }
13028
13492
  async function readLinkedProjectName(dir) {
13029
- const abs = path55.join(dir, ".vercel", "project.json");
13493
+ const abs = path58.join(dir, ".vercel", "project.json");
13030
13494
  if (!await exists(abs)) return void 0;
13031
- const parsed = JSON.parse(await fs25.readFile(abs, "utf8"));
13495
+ const parsed = JSON.parse(await fs28.readFile(abs, "utf8"));
13032
13496
  return typeof parsed.projectName === "string" ? parsed.projectName : void 0;
13033
13497
  }
13034
13498
  function routeSource(route) {
@@ -13044,7 +13508,7 @@ async function addVercelServices(graph, services, scanPath) {
13044
13508
  read = await readVercelConfig(service.dir);
13045
13509
  projectName = await readLinkedProjectName(service.dir);
13046
13510
  } catch (err) {
13047
- recordExtractionError("infra vercel", path55.relative(scanPath, service.dir), err);
13511
+ recordExtractionError("infra vercel", path58.relative(scanPath, service.dir), err);
13048
13512
  continue;
13049
13513
  }
13050
13514
  if (!read && !projectName) continue;
@@ -13060,7 +13524,7 @@ async function addVercelServices(graph, services, scanPath) {
13060
13524
  const anchorId = service.node.id;
13061
13525
  if (!read) continue;
13062
13526
  const { config, relFile, raw } = read;
13063
- const evidenceFile = toPosix(path55.relative(scanPath, path55.join(service.dir, relFile)));
13527
+ const evidenceFile = toPosix(path58.relative(scanPath, path58.join(service.dir, relFile)));
13064
13528
  const add = (edgeType, kind, name) => {
13065
13529
  if (!name) return;
13066
13530
  const result = emitPlatformResourceEdge(
@@ -13088,17 +13552,17 @@ async function addVercelServices(graph, services, scanPath) {
13088
13552
  }
13089
13553
 
13090
13554
  // src/extract/infra/railway.ts
13091
- import { promises as fs26 } from "fs";
13092
- import path56 from "path";
13093
- import { parse as parseToml3 } from "smol-toml";
13555
+ import { promises as fs29 } from "fs";
13556
+ import path59 from "path";
13557
+ import { parse as parseToml4 } from "smol-toml";
13094
13558
  import { EdgeType as EdgeType22 } from "@neat.is/types";
13095
13559
  var RAILWAY_FILENAMES = ["railway.toml", "railway.json", "railway.jsonc"];
13096
13560
  async function readRailwayConfig(dir) {
13097
13561
  for (const filename of RAILWAY_FILENAMES) {
13098
- const abs = path56.join(dir, filename);
13562
+ const abs = path59.join(dir, filename);
13099
13563
  if (!await exists(abs)) continue;
13100
- const raw = await fs26.readFile(abs, "utf8");
13101
- const config = filename === "railway.toml" ? parseToml3(raw) : JSON.parse(maskCommentsInSource(raw));
13564
+ const raw = await fs29.readFile(abs, "utf8");
13565
+ const config = filename === "railway.toml" ? parseToml4(raw) : JSON.parse(maskCommentsInSource(raw));
13102
13566
  return { config, relFile: filename, raw };
13103
13567
  }
13104
13568
  return null;
@@ -13111,7 +13575,7 @@ async function addRailwayServices(graph, services, scanPath) {
13111
13575
  try {
13112
13576
  read = await readRailwayConfig(service.dir);
13113
13577
  } catch (err) {
13114
- recordExtractionError("infra railway", path56.relative(scanPath, service.dir), err);
13578
+ recordExtractionError("infra railway", path59.relative(scanPath, service.dir), err);
13115
13579
  continue;
13116
13580
  }
13117
13581
  if (!read) continue;
@@ -13121,7 +13585,7 @@ async function addRailwayServices(graph, services, scanPath) {
13121
13585
  }
13122
13586
  const anchorId = service.node.id;
13123
13587
  const { config, relFile, raw } = read;
13124
- const evidenceFile = toPosix(path56.relative(scanPath, path56.join(service.dir, relFile)));
13588
+ const evidenceFile = toPosix(path59.relative(scanPath, path59.join(service.dir, relFile)));
13125
13589
  const add = (edgeType, kind, name) => {
13126
13590
  if (!name) return;
13127
13591
  const result = emitPlatformResourceEdge(
@@ -13145,16 +13609,16 @@ async function addRailwayServices(graph, services, scanPath) {
13145
13609
  }
13146
13610
 
13147
13611
  // src/extract/infra/supabase.ts
13148
- import { promises as fs27 } from "fs";
13149
- import path57 from "path";
13150
- import { parse as parseToml4 } from "smol-toml";
13612
+ import { promises as fs30 } from "fs";
13613
+ import path60 from "path";
13614
+ import { parse as parseToml5 } from "smol-toml";
13151
13615
  import { EdgeType as EdgeType23 } from "@neat.is/types";
13152
13616
  async function readSupabaseConfig(dir) {
13153
- const relFile = path57.join("supabase", "config.toml");
13154
- const abs = path57.join(dir, relFile);
13617
+ const relFile = path60.join("supabase", "config.toml");
13618
+ const abs = path60.join(dir, relFile);
13155
13619
  if (!await exists(abs)) return null;
13156
- const raw = await fs27.readFile(abs, "utf8");
13157
- const config = parseToml4(raw);
13620
+ const raw = await fs30.readFile(abs, "utf8");
13621
+ const config = parseToml5(raw);
13158
13622
  return { config, relFile, raw };
13159
13623
  }
13160
13624
  async function addSupabaseProjects(graph, services, scanPath) {
@@ -13165,7 +13629,7 @@ async function addSupabaseProjects(graph, services, scanPath) {
13165
13629
  try {
13166
13630
  read = await readSupabaseConfig(service.dir);
13167
13631
  } catch (err) {
13168
- recordExtractionError("infra supabase", path57.relative(scanPath, service.dir), err);
13632
+ recordExtractionError("infra supabase", path60.relative(scanPath, service.dir), err);
13169
13633
  continue;
13170
13634
  }
13171
13635
  if (!read) continue;
@@ -13180,7 +13644,7 @@ async function addSupabaseProjects(graph, services, scanPath) {
13180
13644
  });
13181
13645
  }
13182
13646
  const anchorId = service.node.id;
13183
- const evidenceFile = toPosix(path57.relative(scanPath, path57.join(service.dir, relFile)));
13647
+ const evidenceFile = toPosix(path60.relative(scanPath, path60.join(service.dir, relFile)));
13184
13648
  const add = (edgeType, kind, name) => {
13185
13649
  if (!name) return;
13186
13650
  const result = emitPlatformResourceEdge(
@@ -13221,12 +13685,12 @@ async function addInfra(graph, scanPath, services) {
13221
13685
  }
13222
13686
 
13223
13687
  // src/extract/zod-shapes.ts
13224
- import path58 from "path";
13688
+ import path61 from "path";
13225
13689
  import Parser16 from "tree-sitter";
13226
13690
  import JavaScript8 from "tree-sitter-javascript";
13227
13691
  import {
13228
13692
  EdgeType as EdgeType24,
13229
- NodeType as NodeType24,
13693
+ NodeType as NodeType27,
13230
13694
  Provenance as Provenance22,
13231
13695
  confidenceForExtracted as confidenceForExtracted19,
13232
13696
  extractedEdgeId as extractedEdgeId11,
@@ -13323,7 +13787,7 @@ function topLevelSchemas(root) {
13323
13787
  }
13324
13788
  function zodShapesFromFile(file, serviceDir) {
13325
13789
  if (!ZOD_IMPORT_RE.test(file.content)) return [];
13326
- const tree = parseSource3(parserForExt4(path58.extname(file.path)), file.content);
13790
+ const tree = parseSource3(parserForExt4(path61.extname(file.path)), file.content);
13327
13791
  const out = [];
13328
13792
  const seen = /* @__PURE__ */ new Set();
13329
13793
  for (const { name, call } of topLevelSchemas(tree.rootNode)) {
@@ -13341,7 +13805,7 @@ function zodShapesFromFile(file, serviceDir) {
13341
13805
  name,
13342
13806
  fields,
13343
13807
  evidence: {
13344
- file: path58.relative(serviceDir, file.path),
13808
+ file: path61.relative(serviceDir, file.path),
13345
13809
  line,
13346
13810
  snippet: snippet(file.content, line)
13347
13811
  }
@@ -13358,7 +13822,7 @@ async function addZodShapes(graph, services) {
13358
13822
  ...service.pkg.devDependencies ?? {}
13359
13823
  };
13360
13824
  if (deps["zod"] === void 0) continue;
13361
- const files = await loadSourceFiles(service.dir);
13825
+ const files = await loadSourceFiles(service.dir, service.excludeDirs);
13362
13826
  for (const file of files) {
13363
13827
  if (isTestPath(file.path)) continue;
13364
13828
  let shapes;
@@ -13372,7 +13836,7 @@ async function addZodShapes(graph, services) {
13372
13836
  if (!graph.hasNode(shape.infraId)) {
13373
13837
  const node = {
13374
13838
  id: shape.infraId,
13375
- type: NodeType24.InfraNode,
13839
+ type: NodeType27.InfraNode,
13376
13840
  name: shape.name,
13377
13841
  provider: "self",
13378
13842
  kind: "zod-schema"
@@ -13382,7 +13846,7 @@ async function addZodShapes(graph, services) {
13382
13846
  }
13383
13847
  if (shape.fields.length > 0) {
13384
13848
  const node = graph.getNodeAttributes(shape.infraId);
13385
- if (node.type === NodeType24.InfraNode) {
13849
+ if (node.type === NodeType27.InfraNode) {
13386
13850
  graph.replaceNodeAttributes(shape.infraId, {
13387
13851
  ...node,
13388
13852
  columns: foldColumns(
@@ -13424,7 +13888,7 @@ async function addZodShapes(graph, services) {
13424
13888
  }
13425
13889
 
13426
13890
  // src/extract/firestore-rules.ts
13427
- import { NodeType as NodeType25 } from "@neat.is/types";
13891
+ import { NodeType as NodeType28 } from "@neat.is/types";
13428
13892
  var FIRESTORE_COLLECTION_KIND = "firestore-collection";
13429
13893
  var WRITE_METHODS = /* @__PURE__ */ new Set(["write", "create", "update"]);
13430
13894
  function stripComments(src) {
@@ -13564,7 +14028,7 @@ async function addFirestoreRules(graph, services) {
13564
14028
  if (guards.size === 0) return { nodesAdded: 0, edgesAdded: 0 };
13565
14029
  graph.forEachNode((id, attrs) => {
13566
14030
  const node = attrs;
13567
- if (node.type !== NodeType25.InfraNode) return;
14031
+ if (node.type !== NodeType28.InfraNode) return;
13568
14032
  if (node.kind !== FIRESTORE_COLLECTION_KIND) return;
13569
14033
  const fields = guards.get(collectionKeyFromName(node.name));
13570
14034
  if (!fields || fields.size === 0) return;
@@ -13577,16 +14041,16 @@ async function addFirestoreRules(graph, services) {
13577
14041
  }
13578
14042
 
13579
14043
  // src/extract/index.ts
13580
- import path60 from "path";
14044
+ import path63 from "path";
13581
14045
 
13582
14046
  // src/extract/retire.ts
13583
14047
  import { existsSync as existsSync2 } from "fs";
13584
- import path59 from "path";
13585
- import { NodeType as NodeType26, Provenance as Provenance23 } from "@neat.is/types";
14048
+ import path62 from "path";
14049
+ import { NodeType as NodeType29, Provenance as Provenance23 } from "@neat.is/types";
13586
14050
  function dropOrphanedFileNodes(graph) {
13587
14051
  const orphans = [];
13588
14052
  graph.forEachNode((id, attrs) => {
13589
- if (attrs.type !== NodeType26.FileNode) return;
14053
+ if (attrs.type !== NodeType29.FileNode) return;
13590
14054
  if (graph.inboundEdges(id).length === 0 && graph.outboundEdges(id).length === 0) {
13591
14055
  orphans.push(id);
13592
14056
  }
@@ -13615,11 +14079,11 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
13615
14079
  if (edge.provenance !== Provenance23.EXTRACTED) return;
13616
14080
  const evidenceFile = edge.evidence?.file;
13617
14081
  if (!evidenceFile) return;
13618
- if (path59.isAbsolute(evidenceFile)) {
14082
+ if (path62.isAbsolute(evidenceFile)) {
13619
14083
  if (!existsSync2(evidenceFile)) toDrop.push(id);
13620
14084
  return;
13621
14085
  }
13622
- const found = bases.some((base) => existsSync2(path59.join(base, evidenceFile)));
14086
+ const found = bases.some((base) => existsSync2(path62.join(base, evidenceFile)));
13623
14087
  if (!found) toDrop.push(id);
13624
14088
  });
13625
14089
  for (const id of toDrop) graph.dropEdge(id);
@@ -13676,7 +14140,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
13676
14140
  }
13677
14141
  const droppedEntries = drainDroppedExtracted();
13678
14142
  if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
13679
- const rejectedPath = path60.join(path60.dirname(opts.errorsPath), "rejected.ndjson");
14143
+ const rejectedPath = path63.join(path63.dirname(opts.errorsPath), "rejected.ndjson");
13680
14144
  try {
13681
14145
  await writeRejectedExtracted(droppedEntries, rejectedPath);
13682
14146
  } catch (err) {
@@ -13713,11 +14177,11 @@ import {
13713
14177
  databaseId as databaseId3,
13714
14178
  DivergenceResultSchema,
13715
14179
  EdgeType as EdgeType25,
13716
- NodeType as NodeType27,
14180
+ NodeType as NodeType30,
13717
14181
  parseEdgeId,
13718
14182
  parseFileId,
13719
14183
  Provenance as Provenance24,
13720
- serviceId as serviceId10
14184
+ serviceId as serviceId13
13721
14185
  } from "@neat.is/types";
13722
14186
  function bucketKey(source, target, type) {
13723
14187
  return `${type}|${source}|${target}`;
@@ -13727,8 +14191,8 @@ function bucketSourceFor(graph, edge) {
13727
14191
  const parsed = parseFileId(edge.source);
13728
14192
  if (!parsed || !graph.hasNode(edge.target)) return edge.source;
13729
14193
  const target = graph.getNodeAttributes(edge.target);
13730
- if (target.type !== NodeType27.DatabaseNode) return edge.source;
13731
- return serviceId10(parsed.service);
14194
+ if (target.type !== NodeType30.DatabaseNode) return edge.source;
14195
+ return serviceId13(parsed.service);
13732
14196
  }
13733
14197
  function bucketEdges(graph) {
13734
14198
  const buckets2 = /* @__PURE__ */ new Map();
@@ -13759,22 +14223,22 @@ function bucketEdges(graph) {
13759
14223
  function nodeIsFrontier(graph, nodeId) {
13760
14224
  if (!graph.hasNode(nodeId)) return false;
13761
14225
  const attrs = graph.getNodeAttributes(nodeId);
13762
- return attrs.type === NodeType27.FrontierNode;
14226
+ return attrs.type === NodeType30.FrontierNode;
13763
14227
  }
13764
14228
  function nodeIsWebsocketChannel(graph, nodeId) {
13765
14229
  if (!graph.hasNode(nodeId)) return false;
13766
14230
  const attrs = graph.getNodeAttributes(nodeId);
13767
- return attrs.type === NodeType27.WebSocketChannelNode;
14231
+ return attrs.type === NodeType30.WebSocketChannelNode;
13768
14232
  }
13769
14233
  function nodeIsServerAction(graph, nodeId) {
13770
14234
  if (!graph.hasNode(nodeId)) return false;
13771
14235
  const attrs = graph.getNodeAttributes(nodeId);
13772
- return attrs.type === NodeType27.ServerActionNode;
14236
+ return attrs.type === NodeType30.ServerActionNode;
13773
14237
  }
13774
14238
  function nodeIsSymbol(graph, nodeId) {
13775
14239
  if (!graph.hasNode(nodeId)) return false;
13776
14240
  const attrs = graph.getNodeAttributes(nodeId);
13777
- return attrs.type === NodeType27.SymbolNode;
14241
+ return attrs.type === NodeType30.SymbolNode;
13778
14242
  }
13779
14243
  function clampConfidence(n) {
13780
14244
  if (!Number.isFinite(n)) return 0;
@@ -13859,7 +14323,7 @@ function detectHostMismatch(graph, svcId, svc) {
13859
14323
  if (edge.type !== EdgeType25.CONNECTS_TO) continue;
13860
14324
  if (edge.provenance !== Provenance24.OBSERVED) continue;
13861
14325
  const target = graph.getNodeAttributes(edge.target);
13862
- if (target.type !== NodeType27.DatabaseNode) continue;
14326
+ if (target.type !== NodeType30.DatabaseNode) continue;
13863
14327
  const observedHost = target.host?.trim();
13864
14328
  if (!observedHost) continue;
13865
14329
  if (observedHost === declaredHost) continue;
@@ -13884,7 +14348,7 @@ function detectCompatDivergences(graph, svcId, svc) {
13884
14348
  if (edge.type !== EdgeType25.CONNECTS_TO) continue;
13885
14349
  if (edge.provenance !== Provenance24.OBSERVED) continue;
13886
14350
  const target = graph.getNodeAttributes(edge.target);
13887
- if (target.type !== NodeType27.DatabaseNode) continue;
14351
+ if (target.type !== NodeType30.DatabaseNode) continue;
13888
14352
  for (const pair of compatPairs()) {
13889
14353
  if (pair.engine !== target.engine) continue;
13890
14354
  const declared = deps[pair.driver];
@@ -14000,13 +14464,13 @@ function computeDivergences(graph, opts = {}) {
14000
14464
  }
14001
14465
  graph.forEachNode((nodeId, attrs) => {
14002
14466
  const n = attrs;
14003
- if (n.type === NodeType27.ServiceNode) {
14467
+ if (n.type === NodeType30.ServiceNode) {
14004
14468
  const svc = n;
14005
14469
  for (const d of detectHostMismatch(graph, nodeId, svc)) all.push(d);
14006
14470
  for (const d of detectCompatDivergences(graph, nodeId, svc)) all.push(d);
14007
14471
  return;
14008
14472
  }
14009
- if (n.type === NodeType27.InfraNode && n.kind === "sql-table") {
14473
+ if (n.type === NodeType30.InfraNode && n.kind === "sql-table") {
14010
14474
  for (const d of detectColumnDrift(n)) all.push(d);
14011
14475
  }
14012
14476
  });
@@ -14050,9 +14514,9 @@ function computeDivergences(graph, opts = {}) {
14050
14514
  }
14051
14515
 
14052
14516
  // src/persist.ts
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";
14517
+ import { promises as fs31 } from "fs";
14518
+ import path64 from "path";
14519
+ import { NodeType as NodeType31, Provenance as Provenance25, observedEdgeId as observedEdgeId2 } from "@neat.is/types";
14056
14520
  var SCHEMA_VERSION = 7;
14057
14521
  function migrateV1ToV2(payload) {
14058
14522
  const nodes = payload.graph.nodes;
@@ -14076,7 +14540,7 @@ function migrateV5ToV6(payload) {
14076
14540
  if (Array.isArray(nodes)) {
14077
14541
  for (const node of nodes) {
14078
14542
  const attrs = node.attributes;
14079
- if (!attrs || attrs.type !== NodeType28.InfraNode) continue;
14543
+ if (!attrs || attrs.type !== NodeType31.InfraNode) continue;
14080
14544
  if (attrs.kind !== "sql-table" && attrs.kind !== "supabase-table") continue;
14081
14545
  if (!Array.isArray(attrs.columns)) attrs.columns = [];
14082
14546
  }
@@ -14106,7 +14570,7 @@ function migrateV2ToV3(payload) {
14106
14570
  return { ...payload, schemaVersion: 3 };
14107
14571
  }
14108
14572
  async function ensureDir(filePath) {
14109
- await fs28.mkdir(path61.dirname(filePath), { recursive: true });
14573
+ await fs31.mkdir(path64.dirname(filePath), { recursive: true });
14110
14574
  }
14111
14575
  async function saveGraphToDisk(graph, outPath) {
14112
14576
  await ensureDir(outPath);
@@ -14116,13 +14580,13 @@ async function saveGraphToDisk(graph, outPath) {
14116
14580
  graph: graph.export()
14117
14581
  };
14118
14582
  const tmp = `${outPath}.tmp`;
14119
- await fs28.writeFile(tmp, JSON.stringify(payload), "utf8");
14120
- await fs28.rename(tmp, outPath);
14583
+ await fs31.writeFile(tmp, JSON.stringify(payload), "utf8");
14584
+ await fs31.rename(tmp, outPath);
14121
14585
  }
14122
14586
  async function loadGraphFromDisk(graph, outPath) {
14123
14587
  let raw;
14124
14588
  try {
14125
- raw = await fs28.readFile(outPath, "utf8");
14589
+ raw = await fs31.readFile(outPath, "utf8");
14126
14590
  } catch (err) {
14127
14591
  if (err.code === "ENOENT") return;
14128
14592
  throw err;
@@ -14195,7 +14659,7 @@ function startPersistLoop(graph, outPath, opts = {}) {
14195
14659
  }
14196
14660
 
14197
14661
  // src/diff.ts
14198
- import { promises as fs29 } from "fs";
14662
+ import { promises as fs32 } from "fs";
14199
14663
  async function loadSnapshotForDiff(target) {
14200
14664
  if (/^https?:\/\//i.test(target)) {
14201
14665
  const res = await fetch(target);
@@ -14204,7 +14668,7 @@ async function loadSnapshotForDiff(target) {
14204
14668
  }
14205
14669
  return await res.json();
14206
14670
  }
14207
- const raw = await fs29.readFile(target, "utf8");
14671
+ const raw = await fs32.readFile(target, "utf8");
14208
14672
  return JSON.parse(raw);
14209
14673
  }
14210
14674
  function indexEntries(entries) {
@@ -14271,23 +14735,23 @@ function canonicalJson(value) {
14271
14735
  }
14272
14736
 
14273
14737
  // src/projects.ts
14274
- import path62 from "path";
14738
+ import path65 from "path";
14275
14739
  function pathsForProject(project, baseDir) {
14276
14740
  if (project === DEFAULT_PROJECT) {
14277
14741
  return {
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")
14742
+ snapshotPath: path65.join(baseDir, "graph.json"),
14743
+ errorsPath: path65.join(baseDir, "errors.ndjson"),
14744
+ staleEventsPath: path65.join(baseDir, "stale-events.ndjson"),
14745
+ embeddingsCachePath: path65.join(baseDir, "embeddings.json"),
14746
+ policyViolationsPath: path65.join(baseDir, "policy-violations.ndjson")
14283
14747
  };
14284
14748
  }
14285
14749
  return {
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`)
14750
+ snapshotPath: path65.join(baseDir, `${project}.json`),
14751
+ errorsPath: path65.join(baseDir, `errors.${project}.ndjson`),
14752
+ staleEventsPath: path65.join(baseDir, `stale-events.${project}.ndjson`),
14753
+ embeddingsCachePath: path65.join(baseDir, `embeddings.${project}.json`),
14754
+ policyViolationsPath: path65.join(baseDir, `policy-violations.${project}.ndjson`)
14291
14755
  };
14292
14756
  }
14293
14757
  var Projects = class {
@@ -14326,9 +14790,9 @@ function parseExtraProjects(raw) {
14326
14790
  }
14327
14791
 
14328
14792
  // src/registry.ts
14329
- import { promises as fs30 } from "fs";
14793
+ import { promises as fs33 } from "fs";
14330
14794
  import os2 from "os";
14331
- import path63 from "path";
14795
+ import path66 from "path";
14332
14796
  import {
14333
14797
  RegistryFileSchema
14334
14798
  } from "@neat.is/types";
@@ -14336,20 +14800,20 @@ var LOCK_TIMEOUT_MS = 5e3;
14336
14800
  var LOCK_RETRY_MS = 50;
14337
14801
  function neatHome() {
14338
14802
  const override = process.env.NEAT_HOME;
14339
- if (override && override.length > 0) return path63.resolve(override);
14340
- return path63.join(os2.homedir(), ".neat");
14803
+ if (override && override.length > 0) return path66.resolve(override);
14804
+ return path66.join(os2.homedir(), ".neat");
14341
14805
  }
14342
14806
  function registryPath() {
14343
- return path63.join(neatHome(), "projects.json");
14807
+ return path66.join(neatHome(), "projects.json");
14344
14808
  }
14345
14809
  function registryLockPath() {
14346
- return path63.join(neatHome(), "projects.json.lock");
14810
+ return path66.join(neatHome(), "projects.json.lock");
14347
14811
  }
14348
14812
  function daemonPidPath() {
14349
- return path63.join(neatHome(), "neatd.pid");
14813
+ return path66.join(neatHome(), "neatd.pid");
14350
14814
  }
14351
14815
  function daemonsDir() {
14352
- return path63.join(neatHome(), "daemons");
14816
+ return path66.join(neatHome(), "daemons");
14353
14817
  }
14354
14818
  function isFiniteInt(v) {
14355
14819
  return typeof v === "number" && Number.isFinite(v);
@@ -14382,7 +14846,7 @@ async function discoverDaemons(probe = defaultDiscoveryProbe) {
14382
14846
  const dir = daemonsDir();
14383
14847
  let names;
14384
14848
  try {
14385
- names = await fs30.readdir(dir);
14849
+ names = await fs33.readdir(dir);
14386
14850
  } catch (err) {
14387
14851
  if (err.code === "ENOENT") return [];
14388
14852
  throw err;
@@ -14390,10 +14854,10 @@ async function discoverDaemons(probe = defaultDiscoveryProbe) {
14390
14854
  const out = [];
14391
14855
  for (const name of names) {
14392
14856
  if (!name.endsWith(".json")) continue;
14393
- const file = path63.join(dir, name);
14857
+ const file = path66.join(dir, name);
14394
14858
  let raw;
14395
14859
  try {
14396
- raw = await fs30.readFile(file, "utf8");
14860
+ raw = await fs33.readFile(file, "utf8");
14397
14861
  } catch {
14398
14862
  continue;
14399
14863
  }
@@ -14406,7 +14870,7 @@ async function discoverDaemons(probe = defaultDiscoveryProbe) {
14406
14870
  return out;
14407
14871
  }
14408
14872
  async function removeDaemonRecord(source) {
14409
- await fs30.unlink(source).catch(() => {
14873
+ await fs33.unlink(source).catch(() => {
14410
14874
  });
14411
14875
  }
14412
14876
  async function listMachineProjects(probe = defaultDiscoveryProbe) {
@@ -14463,7 +14927,7 @@ function isPidAliveDefault(pid) {
14463
14927
  }
14464
14928
  async function readPidFile(file) {
14465
14929
  try {
14466
- const raw = await fs30.readFile(file, "utf8");
14930
+ const raw = await fs33.readFile(file, "utf8");
14467
14931
  const pid = Number.parseInt(raw.trim(), 10);
14468
14932
  return Number.isInteger(pid) && pid > 0 ? pid : void 0;
14469
14933
  } catch {
@@ -14511,32 +14975,32 @@ function lockHolderMessage(holder, lockPath, timeoutMs) {
14511
14975
  }
14512
14976
  }
14513
14977
  async function normalizeProjectPath(input) {
14514
- const resolved = path63.resolve(input);
14978
+ const resolved = path66.resolve(input);
14515
14979
  try {
14516
- return await fs30.realpath(resolved);
14980
+ return await fs33.realpath(resolved);
14517
14981
  } catch {
14518
14982
  return resolved;
14519
14983
  }
14520
14984
  }
14521
14985
  async function writeAtomically(target, contents) {
14522
- await fs30.mkdir(path63.dirname(target), { recursive: true });
14986
+ await fs33.mkdir(path66.dirname(target), { recursive: true });
14523
14987
  const tmp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
14524
- const fd = await fs30.open(tmp, "w");
14988
+ const fd = await fs33.open(tmp, "w");
14525
14989
  try {
14526
14990
  await fd.writeFile(contents, "utf8");
14527
14991
  await fd.sync();
14528
14992
  } finally {
14529
14993
  await fd.close();
14530
14994
  }
14531
- await fs30.rename(tmp, target);
14995
+ await fs33.rename(tmp, target);
14532
14996
  }
14533
14997
  async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaultLockHolderProbe) {
14534
14998
  const deadline = Date.now() + timeoutMs;
14535
- await fs30.mkdir(path63.dirname(lockPath), { recursive: true });
14999
+ await fs33.mkdir(path66.dirname(lockPath), { recursive: true });
14536
15000
  let probedHolder = false;
14537
15001
  while (true) {
14538
15002
  try {
14539
- const fd = await fs30.open(lockPath, "wx");
15003
+ const fd = await fs33.open(lockPath, "wx");
14540
15004
  try {
14541
15005
  await fd.writeFile(`${process.pid}
14542
15006
  `, "utf8");
@@ -14561,7 +15025,7 @@ async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaul
14561
15025
  }
14562
15026
  }
14563
15027
  async function releaseLock(lockPath) {
14564
- await fs30.unlink(lockPath).catch(() => {
15028
+ await fs33.unlink(lockPath).catch(() => {
14565
15029
  });
14566
15030
  }
14567
15031
  async function withLock(fn) {
@@ -14577,7 +15041,7 @@ async function readRegistry() {
14577
15041
  const file = registryPath();
14578
15042
  let raw;
14579
15043
  try {
14580
- raw = await fs30.readFile(file, "utf8");
15044
+ raw = await fs33.readFile(file, "utf8");
14581
15045
  } catch (err) {
14582
15046
  if (err.code === "ENOENT") {
14583
15047
  return { version: 1, projects: [] };
@@ -14682,7 +15146,7 @@ function pruneTtlMs() {
14682
15146
  }
14683
15147
  async function statPathStatus(p) {
14684
15148
  try {
14685
- const stat = await fs30.stat(p);
15149
+ const stat = await fs33.stat(p);
14686
15150
  return stat.isDirectory() ? "present" : "unknown";
14687
15151
  } catch (err) {
14688
15152
  return err.code === "ENOENT" ? "gone" : "unknown";
@@ -14727,14 +15191,14 @@ import cors from "@fastify/cors";
14727
15191
  import { DivergenceTypeSchema, PoliciesCheckBodySchema, PolicySeveritySchema } from "@neat.is/types";
14728
15192
 
14729
15193
  // src/extend/index.ts
14730
- import { promises as fs32 } from "fs";
14731
- import path65 from "path";
15194
+ import { promises as fs35 } from "fs";
15195
+ import path68 from "path";
14732
15196
  import os3 from "os";
14733
15197
  import { resolve as registryResolve, list as registryList } from "@neat.is/instrumentation-registry";
14734
15198
 
14735
15199
  // src/installers/package-manager.ts
14736
- import { promises as fs31 } from "fs";
14737
- import path64 from "path";
15200
+ import { promises as fs34 } from "fs";
15201
+ import path67 from "path";
14738
15202
  import { spawn } from "child_process";
14739
15203
  var LOCKFILE_PRIORITY = [
14740
15204
  { lockfile: "bun.lockb", pm: "bun", args: ["install", "--no-summary"] },
@@ -14749,29 +15213,29 @@ var LOCKFILE_PRIORITY = [
14749
15213
  var NPM_FALLBACK_ARGS = ["install", "--no-audit", "--no-fund", "--prefer-offline"];
14750
15214
  async function exists2(p) {
14751
15215
  try {
14752
- await fs31.access(p);
15216
+ await fs34.access(p);
14753
15217
  return true;
14754
15218
  } catch {
14755
15219
  return false;
14756
15220
  }
14757
15221
  }
14758
15222
  async function detectPackageManager(serviceDir) {
14759
- let dir = path64.resolve(serviceDir);
15223
+ let dir = path67.resolve(serviceDir);
14760
15224
  const stops = /* @__PURE__ */ new Set();
14761
15225
  for (let i = 0; i < 64; i++) {
14762
15226
  if (stops.has(dir)) break;
14763
15227
  stops.add(dir);
14764
15228
  for (const candidate of LOCKFILE_PRIORITY) {
14765
- const lockPath = path64.join(dir, candidate.lockfile);
15229
+ const lockPath = path67.join(dir, candidate.lockfile);
14766
15230
  if (await exists2(lockPath)) {
14767
15231
  return { pm: candidate.pm, cwd: dir, args: [...candidate.args] };
14768
15232
  }
14769
15233
  }
14770
- const parent = path64.dirname(dir);
15234
+ const parent = path67.dirname(dir);
14771
15235
  if (parent === dir) break;
14772
15236
  dir = parent;
14773
15237
  }
14774
- return { pm: "npm", cwd: path64.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
15238
+ return { pm: "npm", cwd: path67.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
14775
15239
  }
14776
15240
  async function runPackageManagerInstall(cmd) {
14777
15241
  return new Promise((resolve) => {
@@ -14813,15 +15277,15 @@ ${err.message}`
14813
15277
  // src/extend/index.ts
14814
15278
  async function fileExists2(p) {
14815
15279
  try {
14816
- await fs32.access(p);
15280
+ await fs35.access(p);
14817
15281
  return true;
14818
15282
  } catch {
14819
15283
  return false;
14820
15284
  }
14821
15285
  }
14822
15286
  async function readPackageJson(scanPath) {
14823
- const pkgPath = path65.join(scanPath, "package.json");
14824
- const raw = await fs32.readFile(pkgPath, "utf8");
15287
+ const pkgPath = path68.join(scanPath, "package.json");
15288
+ const raw = await fs35.readFile(pkgPath, "utf8");
14825
15289
  return JSON.parse(raw);
14826
15290
  }
14827
15291
  var HOOK_WALK_SKIP_DIRS = /* @__PURE__ */ new Set([
@@ -14835,15 +15299,15 @@ var HOOK_WALK_SKIP_DIRS = /* @__PURE__ */ new Set([
14835
15299
  async function findHookFiles(scanPath) {
14836
15300
  const found = [];
14837
15301
  const walk9 = async (dir) => {
14838
- const entries = await fs32.readdir(dir, { withFileTypes: true }).catch(() => []);
15302
+ const entries = await fs35.readdir(dir, { withFileTypes: true }).catch(() => []);
14839
15303
  for (const entry of entries) {
14840
15304
  if (entry.isDirectory()) {
14841
15305
  if (entry.name.startsWith(".") || HOOK_WALK_SKIP_DIRS.has(entry.name)) continue;
14842
- await walk9(path65.join(dir, entry.name));
15306
+ await walk9(path68.join(dir, entry.name));
14843
15307
  } else if (entry.isFile()) {
14844
15308
  if ((entry.name.startsWith("instrumentation") || entry.name.startsWith("otel-init")) && /\.(ts|js|cjs|mjs)$/.test(entry.name)) {
14845
- const rel = path65.relative(scanPath, path65.join(dir, entry.name));
14846
- found.push(rel.split(path65.sep).join("/"));
15309
+ const rel = path68.relative(scanPath, path68.join(dir, entry.name));
15310
+ found.push(rel.split(path68.sep).join("/"));
14847
15311
  }
14848
15312
  }
14849
15313
  }
@@ -14854,7 +15318,7 @@ async function findHookFiles(scanPath) {
14854
15318
  async function pickPrimaryHookFile(scanPath, hookFiles, snippet2) {
14855
15319
  let fallback = null;
14856
15320
  for (const file of hookFiles) {
14857
- const content = await fs32.readFile(path65.join(scanPath, file), "utf8");
15321
+ const content = await fs35.readFile(path68.join(scanPath, file), "utf8");
14858
15322
  const patched = splicedContent(content, snippet2);
14859
15323
  if (patched !== null) return { file, content, patched };
14860
15324
  if (fallback === null) fallback = { file, content };
@@ -14862,12 +15326,12 @@ async function pickPrimaryHookFile(scanPath, hookFiles, snippet2) {
14862
15326
  return { file: fallback.file, content: fallback.content, patched: null };
14863
15327
  }
14864
15328
  function extendLogPath() {
14865
- return process.env.NEAT_EXTEND_LOG ?? path65.join(os3.homedir(), ".neat", "extend-log.ndjson");
15329
+ return process.env.NEAT_EXTEND_LOG ?? path68.join(os3.homedir(), ".neat", "extend-log.ndjson");
14866
15330
  }
14867
15331
  async function appendExtendLog(entry) {
14868
15332
  const logPath = extendLogPath();
14869
- await fs32.mkdir(path65.dirname(logPath), { recursive: true });
14870
- await fs32.appendFile(logPath, JSON.stringify(entry) + "\n", "utf8");
15333
+ await fs35.mkdir(path68.dirname(logPath), { recursive: true });
15334
+ await fs35.appendFile(logPath, JSON.stringify(entry) + "\n", "utf8");
14871
15335
  }
14872
15336
  function splicedContent(fileContent, snippet2) {
14873
15337
  if (fileContent.includes("__INSTRUMENTATION_BLOCK__")) {
@@ -14925,7 +15389,7 @@ function lookupInstrumentation(library, installedVersion) {
14925
15389
  }
14926
15390
  async function describeProjectInstrumentation(ctx) {
14927
15391
  const hookFiles = await findHookFiles(ctx.scanPath);
14928
- const envNeat = await fileExists2(path65.join(ctx.scanPath, ".env.neat"));
15392
+ const envNeat = await fileExists2(path68.join(ctx.scanPath, ".env.neat"));
14929
15393
  const registryInstrPackages = new Set(
14930
15394
  registryList().map((e) => e.instrumentation_package).filter((p) => !!p)
14931
15395
  );
@@ -14947,7 +15411,7 @@ async function applyExtension(ctx, args, options) {
14947
15411
  );
14948
15412
  }
14949
15413
  for (const file of hookFiles) {
14950
- const content = await fs32.readFile(path65.join(ctx.scanPath, file), "utf8");
15414
+ const content = await fs35.readFile(path68.join(ctx.scanPath, file), "utf8");
14951
15415
  if (content.includes(args.registration_snippet)) {
14952
15416
  return { library: args.library, filesTouched: [], depsAdded: [], installOutput: "", alreadyApplied: true };
14953
15417
  }
@@ -14959,18 +15423,18 @@ async function applyExtension(ctx, args, options) {
14959
15423
  );
14960
15424
  }
14961
15425
  const primaryFile = primary.file;
14962
- const primaryPath = path65.join(ctx.scanPath, primaryFile);
15426
+ const primaryPath = path68.join(ctx.scanPath, primaryFile);
14963
15427
  const filesTouched = [];
14964
15428
  const depsAdded = [];
14965
- const pkgPath = path65.join(ctx.scanPath, "package.json");
15429
+ const pkgPath = path68.join(ctx.scanPath, "package.json");
14966
15430
  const pkg = await readPackageJson(ctx.scanPath);
14967
15431
  if (!(pkg.dependencies ?? {})[args.instrumentation_package]) {
14968
15432
  pkg.dependencies = { ...pkg.dependencies ?? {}, [args.instrumentation_package]: args.version };
14969
- await fs32.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
15433
+ await fs35.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
14970
15434
  filesTouched.push("package.json");
14971
15435
  depsAdded.push(`${args.instrumentation_package}@${args.version}`);
14972
15436
  }
14973
- await fs32.writeFile(primaryPath, primary.patched, "utf8");
15437
+ await fs35.writeFile(primaryPath, primary.patched, "utf8");
14974
15438
  filesTouched.push(primaryFile);
14975
15439
  const cmd = await detectPackageManager(ctx.scanPath);
14976
15440
  const installer = options?.runInstall ?? runPackageManagerInstall;
@@ -15001,7 +15465,7 @@ async function dryRunExtension(ctx, args) {
15001
15465
  };
15002
15466
  }
15003
15467
  for (const file of hookFiles) {
15004
- const content = await fs32.readFile(path65.join(ctx.scanPath, file), "utf8");
15468
+ const content = await fs35.readFile(path68.join(ctx.scanPath, file), "utf8");
15005
15469
  if (content.includes(args.registration_snippet)) {
15006
15470
  return {
15007
15471
  library: args.library,
@@ -15036,28 +15500,28 @@ async function rollbackExtension(ctx, args) {
15036
15500
  if (!await fileExists2(logPath)) {
15037
15501
  return { undone: false, message: "no apply found for library" };
15038
15502
  }
15039
- const raw = await fs32.readFile(logPath, "utf8");
15503
+ const raw = await fs35.readFile(logPath, "utf8");
15040
15504
  const entries = raw.trim().split("\n").filter(Boolean).map((line) => JSON.parse(line));
15041
15505
  const match = [...entries].reverse().find((e) => e.project === ctx.project && e.library === args.library);
15042
15506
  if (!match) {
15043
15507
  return { undone: false, message: "no apply found for library" };
15044
15508
  }
15045
- const pkgPath = path65.join(ctx.scanPath, "package.json");
15509
+ const pkgPath = path68.join(ctx.scanPath, "package.json");
15046
15510
  if (await fileExists2(pkgPath)) {
15047
15511
  const pkg = await readPackageJson(ctx.scanPath);
15048
15512
  if (pkg.dependencies?.[match.instrumentation_package]) {
15049
15513
  const { [match.instrumentation_package]: _removed, ...rest } = pkg.dependencies;
15050
15514
  pkg.dependencies = rest;
15051
- await fs32.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
15515
+ await fs35.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
15052
15516
  }
15053
15517
  }
15054
15518
  const hookFiles = await findHookFiles(ctx.scanPath);
15055
15519
  for (const file of hookFiles) {
15056
- const filePath = path65.join(ctx.scanPath, file);
15057
- const content = await fs32.readFile(filePath, "utf8");
15520
+ const filePath = path68.join(ctx.scanPath, file);
15521
+ const content = await fs35.readFile(filePath, "utf8");
15058
15522
  if (content.includes(match.registration_snippet)) {
15059
15523
  const filtered = content.split("\n").filter((line) => !line.includes(match.registration_snippet)).join("\n");
15060
- await fs32.writeFile(filePath, filtered, "utf8");
15524
+ await fs35.writeFile(filePath, filtered, "utf8");
15061
15525
  break;
15062
15526
  }
15063
15527
  }
@@ -15168,8 +15632,8 @@ data: ${JSON.stringify(envelope.payload)}
15168
15632
 
15169
15633
  // src/connectors-config.ts
15170
15634
  import os4 from "os";
15171
- import path66 from "path";
15172
- import { promises as fs33 } from "fs";
15635
+ import path69 from "path";
15636
+ import { promises as fs36 } from "fs";
15173
15637
  var CONNECTORS_CONFIG_VERSION = 1;
15174
15638
  var EnvRefUnsetError = class extends Error {
15175
15639
  ref;
@@ -15183,17 +15647,17 @@ var EnvRefUnsetError = class extends Error {
15183
15647
  };
15184
15648
  function neatHome2() {
15185
15649
  const override = process.env.NEAT_HOME;
15186
- if (override && override.length > 0) return path66.resolve(override);
15187
- return path66.join(os4.homedir(), ".neat");
15650
+ if (override && override.length > 0) return path69.resolve(override);
15651
+ return path69.join(os4.homedir(), ".neat");
15188
15652
  }
15189
15653
  function connectorsConfigPath(home = neatHome2()) {
15190
- return path66.join(home, "connectors.json");
15654
+ return path69.join(home, "connectors.json");
15191
15655
  }
15192
15656
  var MODE_MASK_LOOSER_THAN_0600 = 63;
15193
15657
  async function warnIfModeLooserThan0600(file) {
15194
15658
  if (process.platform === "win32") return;
15195
15659
  try {
15196
- const stat = await fs33.stat(file);
15660
+ const stat = await fs36.stat(file);
15197
15661
  if ((stat.mode & MODE_MASK_LOOSER_THAN_0600) !== 0) {
15198
15662
  const mode = (stat.mode & 511).toString(8).padStart(3, "0");
15199
15663
  console.warn(
@@ -15207,7 +15671,7 @@ async function readConnectorsConfig(home = neatHome2()) {
15207
15671
  const file = connectorsConfigPath(home);
15208
15672
  let raw;
15209
15673
  try {
15210
- raw = await fs33.readFile(file, "utf8");
15674
+ raw = await fs36.readFile(file, "utf8");
15211
15675
  } catch (err) {
15212
15676
  if (err.code === "ENOENT") {
15213
15677
  return { version: CONNECTORS_CONFIG_VERSION, connectors: [] };
@@ -15318,7 +15782,7 @@ function connectorMatchesProject(entry, project) {
15318
15782
  var CONNECTORS_LOCK_TIMEOUT_MS = 5e3;
15319
15783
  var CONNECTORS_LOCK_RETRY_MS = 50;
15320
15784
  function connectorsConfigLockPath(home = neatHome2()) {
15321
- return path66.join(home, "connectors.json.lock");
15785
+ return path69.join(home, "connectors.json.lock");
15322
15786
  }
15323
15787
  function isEnvRef(value) {
15324
15788
  return value.length > 1 && value.startsWith("$");
@@ -15331,9 +15795,9 @@ function redactCredentialRef(ref) {
15331
15795
  return out;
15332
15796
  }
15333
15797
  async function writeConfigAtomically0600(file, contents) {
15334
- await fs33.mkdir(path66.dirname(file), { recursive: true });
15798
+ await fs36.mkdir(path69.dirname(file), { recursive: true });
15335
15799
  const tmp = `${file}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
15336
- const fd = await fs33.open(tmp, "w", 384);
15800
+ const fd = await fs36.open(tmp, "w", 384);
15337
15801
  try {
15338
15802
  await fd.writeFile(contents, "utf8");
15339
15803
  await fd.chmod(384);
@@ -15341,14 +15805,14 @@ async function writeConfigAtomically0600(file, contents) {
15341
15805
  } finally {
15342
15806
  await fd.close();
15343
15807
  }
15344
- await fs33.rename(tmp, file);
15808
+ await fs36.rename(tmp, file);
15345
15809
  }
15346
15810
  async function acquireConnectorsLock(lockPath, timeoutMs = CONNECTORS_LOCK_TIMEOUT_MS) {
15347
15811
  const deadline = Date.now() + timeoutMs;
15348
- await fs33.mkdir(path66.dirname(lockPath), { recursive: true });
15812
+ await fs36.mkdir(path69.dirname(lockPath), { recursive: true });
15349
15813
  for (; ; ) {
15350
15814
  try {
15351
- const fd = await fs33.open(lockPath, "wx");
15815
+ const fd = await fs36.open(lockPath, "wx");
15352
15816
  try {
15353
15817
  await fd.writeFile(`${process.pid}
15354
15818
  `, "utf8");
@@ -15368,7 +15832,7 @@ async function acquireConnectorsLock(lockPath, timeoutMs = CONNECTORS_LOCK_TIMEO
15368
15832
  }
15369
15833
  }
15370
15834
  async function releaseConnectorsLock(lockPath) {
15371
- await fs33.unlink(lockPath).catch(() => {
15835
+ await fs36.unlink(lockPath).catch(() => {
15372
15836
  });
15373
15837
  }
15374
15838
  async function withConnectorsLock(home, fn) {
@@ -15493,7 +15957,7 @@ function getConnectorStatus(id, now = Date.now(), thresholdMs = CONNECTOR_STALE_
15493
15957
  }
15494
15958
 
15495
15959
  // src/connectors/index.ts
15496
- import { NodeType as NodeType29, parseFileId as parseFileId2, Provenance as Provenance26 } from "@neat.is/types";
15960
+ import { NodeType as NodeType32, parseFileId as parseFileId2, Provenance as Provenance26 } from "@neat.is/types";
15497
15961
  var NO_ENV = "unknown";
15498
15962
  function staticCallSiteFor(graph, serviceName, targetNodeId) {
15499
15963
  if (!graph.hasNode(targetNodeId)) return void 0;
@@ -15512,7 +15976,7 @@ function staticCallSiteFor(graph, serviceName, targetNodeId) {
15512
15976
  function routeCallSiteFor(graph, targetNodeId) {
15513
15977
  if (!graph.hasNode(targetNodeId)) return void 0;
15514
15978
  const attrs = graph.getNodeAttributes(targetNodeId);
15515
- if (attrs.type !== NodeType29.RouteNode || !attrs.path) return void 0;
15979
+ if (attrs.type !== NodeType32.RouteNode || !attrs.path) return void 0;
15516
15980
  const site = { relPath: attrs.path };
15517
15981
  if (attrs.line !== void 0) site.line = attrs.line;
15518
15982
  return site;
@@ -15997,10 +16461,10 @@ var SUPABASE_RPC_TARGET_KIND = "supabase-rpc";
15997
16461
  // src/connectors/supabase/map.ts
15998
16462
  var REST_RPC_PATH_RE = /^\/rest\/v1\/rpc\/([^/?]+)/;
15999
16463
  var REST_TABLE_PATH_RE = /^\/rest\/v1\/([^/?]+)/;
16000
- function targetFromRestPath(path67) {
16001
- const rpcMatch = REST_RPC_PATH_RE.exec(path67);
16464
+ function targetFromRestPath(path70) {
16465
+ const rpcMatch = REST_RPC_PATH_RE.exec(path70);
16002
16466
  if (rpcMatch) return { targetKind: SUPABASE_RPC_TARGET_KIND, name: rpcMatch[1] };
16003
- const tableMatch = REST_TABLE_PATH_RE.exec(path67);
16467
+ const tableMatch = REST_TABLE_PATH_RE.exec(path70);
16004
16468
  if (tableMatch) return { targetKind: SUPABASE_TABLE_TARGET_KIND, name: tableMatch[1] };
16005
16469
  return null;
16006
16470
  }
@@ -16217,7 +16681,7 @@ function createSupabaseConnector(graph, config, deps = {}) {
16217
16681
  }
16218
16682
 
16219
16683
  // src/connectors/railway/index.ts
16220
- import { EdgeType as EdgeType27, NodeType as NodeType30, serviceId as serviceId11 } from "@neat.is/types";
16684
+ import { EdgeType as EdgeType27, NodeType as NodeType33, serviceId as serviceId14 } from "@neat.is/types";
16221
16685
 
16222
16686
  // src/connectors/railway/client.ts
16223
16687
  var DEFAULT_RAILWAY_API_URL = "https://backboard.railway.com/graphql/v2";
@@ -16367,7 +16831,7 @@ function buildRailwayRouteIndex(graph, serviceName) {
16367
16831
  const out = [];
16368
16832
  graph.forEachNode((_id, attrs) => {
16369
16833
  const node = attrs;
16370
- if (node.type !== NodeType30.RouteNode) return;
16834
+ if (node.type !== NodeType33.RouteNode) return;
16371
16835
  const route = attrs;
16372
16836
  if (route.service !== serviceName) return;
16373
16837
  out.push({
@@ -16476,7 +16940,7 @@ function createRailwayResolveTarget(config) {
16476
16940
  if (signal.targetKind === PEER_SERVICE_TARGET_KIND) {
16477
16941
  const peerName = config.serviceNameById[signal.targetName];
16478
16942
  if (!peerName) return null;
16479
- return { targetNodeId: serviceId11(peerName), serviceName, edgeType: EdgeType27.CONNECTS_TO };
16943
+ return { targetNodeId: serviceId14(peerName), serviceName, edgeType: EdgeType27.CONNECTS_TO };
16480
16944
  }
16481
16945
  return null;
16482
16946
  };
@@ -16595,9 +17059,9 @@ function parseFirebaseTargetName(targetName) {
16595
17059
  const secondSep = rest.indexOf(FIELD_SEP);
16596
17060
  if (secondSep === -1) return null;
16597
17061
  const method = rest.slice(0, secondSep);
16598
- const path67 = rest.slice(secondSep + 1);
16599
- if (!resourceName || !method || !path67) return null;
16600
- return { resourceName, method, path: path67 };
17062
+ const path70 = rest.slice(secondSep + 1);
17063
+ if (!resourceName || !method || !path70) return null;
17064
+ return { resourceName, method, path: path70 };
16601
17065
  }
16602
17066
  function resourceNameFor(type, labels) {
16603
17067
  if (!labels) return null;
@@ -16635,14 +17099,14 @@ function mapLogEntryToSignal(entry) {
16635
17099
  if (!req) return null;
16636
17100
  if (typeof req.requestMethod !== "string" || req.requestMethod.length === 0) return null;
16637
17101
  const method = req.requestMethod.toUpperCase();
16638
- const path67 = pathFromRequestUrl(req.requestUrl);
16639
- if (path67 === null) return null;
17102
+ const path70 = pathFromRequestUrl(req.requestUrl);
17103
+ if (path70 === null) return null;
16640
17104
  const timestamp = entry.timestamp;
16641
17105
  if (typeof timestamp !== "string" || timestamp.length === 0) return null;
16642
17106
  const isError = typeof req.status === "number" && req.status >= ERROR_STATUS_THRESHOLD2;
16643
17107
  return {
16644
17108
  targetKind: resourceType,
16645
- targetName: packFirebaseTargetName({ resourceName, method, path: path67 }),
17109
+ targetName: packFirebaseTargetName({ resourceName, method, path: path70 }),
16646
17110
  callCount: 1,
16647
17111
  errorCount: isError ? 1 : 0,
16648
17112
  lastObservedIso: timestamp
@@ -16658,7 +17122,7 @@ function mapLogEntriesToSignals(entries) {
16658
17122
  }
16659
17123
 
16660
17124
  // src/connectors/firebase/resolve.ts
16661
- import { NodeType as NodeType31, EdgeType as EdgeType28 } from "@neat.is/types";
17125
+ import { NodeType as NodeType34, EdgeType as EdgeType28 } from "@neat.is/types";
16662
17126
  function neatServiceNameFor(resourceType, resourceName, serviceMap) {
16663
17127
  switch (resourceType) {
16664
17128
  case "cloud_function":
@@ -16673,7 +17137,7 @@ function routeEntriesFor(graph, serviceName) {
16673
17137
  const entries = [];
16674
17138
  graph.forEachNode((_id, attrs) => {
16675
17139
  const node = attrs;
16676
- if (node.type !== NodeType31.RouteNode) return;
17140
+ if (node.type !== NodeType34.RouteNode) return;
16677
17141
  const route = attrs;
16678
17142
  if (route.service !== serviceName) return;
16679
17143
  entries.push({
@@ -16728,7 +17192,7 @@ function createFirebaseConnector(graph, serviceMap) {
16728
17192
  }
16729
17193
 
16730
17194
  // src/connectors/cloudflare/connector.ts
16731
- import { EdgeType as EdgeType29, NodeType as NodeType32, fileId as fileId5, infraId as infraId21 } from "@neat.is/types";
17195
+ import { EdgeType as EdgeType29, NodeType as NodeType35, fileId as fileId5, infraId as infraId21 } from "@neat.is/types";
16732
17196
 
16733
17197
  // src/connectors/cloudflare/client.ts
16734
17198
  import { randomUUID } from "crypto";
@@ -16839,7 +17303,7 @@ function mapEventToSignal(event) {
16839
17303
  if (Number.isNaN(observedAt.getTime())) return null;
16840
17304
  const statusCode = metadata?.statusCode;
16841
17305
  const isError = typeof statusCode === "number" && statusCode >= ERROR_STATUS_THRESHOLD3;
16842
- const path67 = metadata?.trigger ? parsePathFromTrigger(metadata.trigger) : void 0;
17306
+ const path70 = metadata?.trigger ? parsePathFromTrigger(metadata.trigger) : void 0;
16843
17307
  return {
16844
17308
  targetKind: CLOUDFLARE_TARGET_KIND,
16845
17309
  targetName: scriptName,
@@ -16847,7 +17311,7 @@ function mapEventToSignal(event) {
16847
17311
  errorCount: isError ? 1 : 0,
16848
17312
  lastObservedIso: observedAt.toISOString(),
16849
17313
  method,
16850
- ...path67 ? { path: path67 } : {},
17314
+ ...path70 ? { path: path70 } : {},
16851
17315
  ...typeof statusCode === "number" ? { statusCode } : {},
16852
17316
  ...typeof metadata?.duration === "number" ? { duration: metadata.duration } : {}
16853
17317
  };
@@ -16887,19 +17351,19 @@ function findTaggedWorkerFileNode(graph, workerName) {
16887
17351
  graph.forEachNode((id, attrs) => {
16888
17352
  if (found) return;
16889
17353
  const a = attrs;
16890
- if (a.type === NodeType32.FileNode && a.platform === "cloudflare" && a.platformName === workerName) {
17354
+ if (a.type === NodeType35.FileNode && a.platform === "cloudflare" && a.platformName === workerName) {
16891
17355
  found = id;
16892
17356
  }
16893
17357
  });
16894
17358
  return found;
16895
17359
  }
16896
- function findMatchingRouteNode(graph, serviceName, method, path67) {
16897
- const normalizedPath = normalizePathTemplate(path67);
17360
+ function findMatchingRouteNode(graph, serviceName, method, path70) {
17361
+ const normalizedPath = normalizePathTemplate(path70);
16898
17362
  let found = null;
16899
17363
  graph.forEachNode((id, attrs) => {
16900
17364
  if (found) return;
16901
17365
  const a = attrs;
16902
- if (a.type !== NodeType32.RouteNode || a.service !== serviceName) return;
17366
+ if (a.type !== NodeType35.RouteNode || a.service !== serviceName) return;
16903
17367
  if (!a.pathTemplate || normalizePathTemplate(a.pathTemplate) !== normalizedPath) return;
16904
17368
  const routeMethod = (a.method ?? "").toUpperCase();
16905
17369
  if (routeMethod !== "ALL" && routeMethod !== method) return;
@@ -16911,10 +17375,10 @@ function createCloudflareResolveTarget(config, graph) {
16911
17375
  return (signal) => {
16912
17376
  if (signal.targetKind !== CLOUDFLARE_TARGET_KIND) return null;
16913
17377
  const scriptName = signal.targetName;
16914
- const { method, path: path67 } = signal;
17378
+ const { method, path: path70 } = signal;
16915
17379
  const resolveRouteGrain = (serviceName, wholeFileId) => {
16916
- if (!method || !path67) return wholeFileId;
16917
- return findMatchingRouteNode(graph, serviceName, method, path67) ?? wholeFileId;
17380
+ if (!method || !path70) return wholeFileId;
17381
+ return findMatchingRouteNode(graph, serviceName, method, path70) ?? wholeFileId;
16918
17382
  };
16919
17383
  const mapping = config.workers?.[scriptName];
16920
17384
  if (mapping) {
@@ -17244,9 +17708,9 @@ function parseCloudRunTargetName(targetName) {
17244
17708
  const secondSep = rest.indexOf(FIELD_SEP2);
17245
17709
  if (secondSep === -1) return null;
17246
17710
  const method = rest.slice(0, secondSep);
17247
- const path67 = rest.slice(secondSep + 1);
17248
- if (!serviceName || !method || !path67) return null;
17249
- return { serviceName, method, path: path67 };
17711
+ const path70 = rest.slice(secondSep + 1);
17712
+ if (!serviceName || !method || !path70) return null;
17713
+ return { serviceName, method, path: path70 };
17250
17714
  }
17251
17715
 
17252
17716
  // src/connectors/cloud-run/map.ts
@@ -17275,14 +17739,14 @@ function mapLogEntryToSignal2(entry) {
17275
17739
  if (!req) return null;
17276
17740
  if (typeof req.requestMethod !== "string" || req.requestMethod.length === 0) return null;
17277
17741
  const method = req.requestMethod.toUpperCase();
17278
- const path67 = pathFromRequestUrl2(req.requestUrl);
17279
- if (path67 === null) return null;
17742
+ const path70 = pathFromRequestUrl2(req.requestUrl);
17743
+ if (path70 === null) return null;
17280
17744
  const timestamp = entry.timestamp;
17281
17745
  if (typeof timestamp !== "string" || timestamp.length === 0) return null;
17282
17746
  const isError = typeof req.status === "number" && req.status >= ERROR_STATUS_THRESHOLD4;
17283
17747
  return {
17284
17748
  targetKind: CLOUD_RUN_TARGET_KIND,
17285
- targetName: packCloudRunTargetName({ serviceName, method, path: path67 }),
17749
+ targetName: packCloudRunTargetName({ serviceName, method, path: path70 }),
17286
17750
  callCount: 1,
17287
17751
  errorCount: isError ? 1 : 0,
17288
17752
  lastObservedIso: timestamp
@@ -17298,14 +17762,14 @@ function mapLogEntriesToSignals2(entries) {
17298
17762
  }
17299
17763
 
17300
17764
  // src/connectors/cloud-run/resolve.ts
17301
- import { EdgeType as EdgeType31, NodeType as NodeType33, infraId as infraId23 } from "@neat.is/types";
17765
+ import { EdgeType as EdgeType31, NodeType as NodeType36, infraId as infraId23 } from "@neat.is/types";
17302
17766
  var CLOUD_RUN_SERVICE_INFRA_KIND = "cloud-run-service";
17303
17767
  function findMatchingRouteNode2(graph, serviceName, method, normalizedPath) {
17304
17768
  let found = null;
17305
17769
  graph.forEachNode((_id, attrs) => {
17306
17770
  if (found) return;
17307
17771
  const node = attrs;
17308
- if (node.type !== NodeType33.RouteNode) return;
17772
+ if (node.type !== NodeType36.RouteNode) return;
17309
17773
  const route = attrs;
17310
17774
  if (route.service !== serviceName || !route.pathTemplate) return;
17311
17775
  if (normalizePathTemplate(route.pathTemplate) !== normalizedPath) return;
@@ -17320,14 +17784,14 @@ function createCloudRunResolveTarget(graph, config) {
17320
17784
  if (signal.targetKind !== CLOUD_RUN_TARGET_KIND) return null;
17321
17785
  const identity = parseCloudRunTargetName(signal.targetName);
17322
17786
  if (!identity) return null;
17323
- const { serviceName: gcpServiceName, method, path: path67 } = identity;
17787
+ const { serviceName: gcpServiceName, method, path: path70 } = identity;
17324
17788
  const mappedService = config.serviceMap?.[gcpServiceName];
17325
17789
  if (mappedService) {
17326
17790
  const routeNodeId = findMatchingRouteNode2(
17327
17791
  graph,
17328
17792
  mappedService,
17329
17793
  method,
17330
- normalizePathTemplate(path67)
17794
+ normalizePathTemplate(path70)
17331
17795
  );
17332
17796
  if (routeNodeId) {
17333
17797
  return { targetNodeId: routeNodeId, serviceName: mappedService, edgeType: EdgeType31.CALLS };
@@ -17376,7 +17840,7 @@ function createCloudRunConnector(graph, config = {}) {
17376
17840
  }
17377
17841
 
17378
17842
  // src/connectors/render/index.ts
17379
- import { EdgeType as EdgeType32, NodeType as NodeType34 } from "@neat.is/types";
17843
+ import { EdgeType as EdgeType32, NodeType as NodeType37 } from "@neat.is/types";
17380
17844
 
17381
17845
  // src/connectors/render/types.ts
17382
17846
  function readRenderToken(credentials) {
@@ -17452,7 +17916,7 @@ function buildRenderRouteIndex(graph, serviceName) {
17452
17916
  const out = [];
17453
17917
  graph.forEachNode((_id, attrs) => {
17454
17918
  const node = attrs;
17455
- if (node.type !== NodeType34.RouteNode) return;
17919
+ if (node.type !== NodeType37.RouteNode) return;
17456
17920
  const route = attrs;
17457
17921
  if (route.service !== serviceName) return;
17458
17922
  out.push({
@@ -17936,7 +18400,7 @@ function mapBuildsToSignals(builds, serviceName) {
17936
18400
  }
17937
18401
 
17938
18402
  // src/connectors/eas/resolve.ts
17939
- import { EdgeType as EdgeType34, NodeType as NodeType35, parseFileId as parseFileId3 } from "@neat.is/types";
18403
+ import { EdgeType as EdgeType34, NodeType as NodeType38, parseFileId as parseFileId3 } from "@neat.is/types";
17940
18404
  var NO_ENV2 = "unknown";
17941
18405
  var EAS_JSON_PHASES = /* @__PURE__ */ new Set(["READ_EAS_JSON"]);
17942
18406
  var APP_CONFIG_PHASES = /* @__PURE__ */ new Set([
@@ -17964,7 +18428,7 @@ function findConfigNode(graph, basenames, serviceName) {
17964
18428
  graph.forEachNode((id, attrs) => {
17965
18429
  if (scoped) return;
17966
18430
  const node = attrs;
17967
- if (node.type !== NodeType35.ConfigNode) return;
18431
+ if (node.type !== NodeType38.ConfigNode) return;
17968
18432
  if (typeof node.name !== "string" || !basenames.includes(node.name)) return;
17969
18433
  if (anyMatch === null) anyMatch = id;
17970
18434
  if (configNodeService(graph, id) === serviceName) scoped = id;
@@ -19576,4 +20040,4 @@ export {
19576
20040
  deprovisionConnector,
19577
20041
  buildApi
19578
20042
  };
19579
- //# sourceMappingURL=chunk-T4UGEMWN.js.map
20043
+ //# sourceMappingURL=chunk-RDLDSIEY.js.map