@neat.is/core 0.8.1 → 0.9.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -961,7 +961,7 @@ import { promises as fs3 } from "fs";
961
961
  import path3 from "path";
962
962
  import { parse as parseYaml } from "yaml";
963
963
  import { extractedEdgeId } from "@neat.is/types";
964
- var SERVICE_FILE_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".mjs", ".cjs", ".ts", ".tsx", ".py", ".go", ".rb", ".php"]);
964
+ var SERVICE_FILE_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".mjs", ".cjs", ".ts", ".tsx", ".py", ".go", ".rb", ".php", ".cs", ".java", ".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 {
@@ -1301,6 +1303,23 @@ function languageForPath(relPath) {
1301
1303
  return "ruby";
1302
1304
  case ".php":
1303
1305
  return "php";
1306
+ case ".cs":
1307
+ return "csharp";
1308
+ case ".java":
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";
1304
1323
  case ".ts":
1305
1324
  case ".tsx":
1306
1325
  return "typescript";
@@ -1639,6 +1658,7 @@ async function resolveGoImport(specifier, modulePath, serviceDir) {
1639
1658
  function emitImportEdge(graph, serviceName, importerFileId, importerRelPath, importeeRelPath, line, snippet2) {
1640
1659
  const importeeFileId = fileId2(serviceName, importeeRelPath);
1641
1660
  if (!graph.hasNode(importeeFileId)) return 0;
1661
+ if (importerFileId === importeeFileId) return 0;
1642
1662
  const edgeId = extractedEdgeId3(importerFileId, importeeFileId, EdgeType3.IMPORTS);
1643
1663
  if (graph.hasEdge(edgeId)) return 0;
1644
1664
  const edge = {
@@ -1660,7 +1680,7 @@ async function addImports(graph, services) {
1660
1680
  let edgesAdded = 0;
1661
1681
  for (const service of services) {
1662
1682
  const tsPaths = await loadTsPathConfig(service.dir);
1663
- const files = await loadSourceFiles(service.dir);
1683
+ const files = await loadSourceFiles(service.dir, service.excludeDirs);
1664
1684
  for (const file of files) {
1665
1685
  if (isTestPath(file.path)) continue;
1666
1686
  const relFile = toPosix(path6.relative(service.dir, file.path));
@@ -1842,8 +1862,8 @@ function chiRoutesFromSource(source, parser) {
1842
1862
  chiWalk(tree.rootNode, "", out);
1843
1863
  return out;
1844
1864
  }
1845
- function stripChiRegex(path64) {
1846
- return path64.replace(/\{([^{}:]+):[^{}]*\}/g, "{$1}");
1865
+ function stripChiRegex(path70) {
1866
+ return path70.replace(/\{([^{}:]+):[^{}]*\}/g, "{$1}");
1847
1867
  }
1848
1868
  function chiWalk(node, prefix, out) {
1849
1869
  for (let i = 0; i < node.namedChildCount; i++) {
@@ -2515,9 +2535,9 @@ function rubyRocketRoute(args) {
2515
2535
  if (!pair || pair.type !== "pair") continue;
2516
2536
  const k = pair.childForFieldName("key");
2517
2537
  if (k?.type !== "string") continue;
2518
- const path64 = rubyLiteral(k);
2519
- if (path64 === null) continue;
2520
- return { path: path64, target: rubyLiteral(pair.childForFieldName("value")) };
2538
+ const path70 = rubyLiteral(k);
2539
+ if (path70 === null) continue;
2540
+ return { path: path70, target: rubyLiteral(pair.childForFieldName("value")) };
2521
2541
  }
2522
2542
  return null;
2523
2543
  }
@@ -3202,7 +3222,7 @@ async function addRoutes(graph, services) {
3202
3222
  const hasLaravel = deps["laravel/framework"] !== void 0;
3203
3223
  if (!hasExpress && !hasFastify && !hasHono && !hasNext && !hasNestjs && !hasFastapi && !hasFlask && !hasDjango && !hasGin && !hasEcho && !hasFiber && !hasChi && !isGoService && !hasRails && !hasLaravel)
3204
3224
  continue;
3205
- const files = await loadSourceFiles(service.dir);
3225
+ const files = await loadSourceFiles(service.dir, service.excludeDirs);
3206
3226
  const mountPrefixes = hasExpress ? await expressMountPrefixes(files, service.dir, await loadTsPathConfig(service.dir)) : /* @__PURE__ */ new Map();
3207
3227
  for (const file of files) {
3208
3228
  if (isTestPath(file.path)) continue;
@@ -4634,29 +4654,29 @@ function promoteFrontierNodes(graph, opts = {}) {
4634
4654
  toPromote.push({ frontierId: id, serviceId: target });
4635
4655
  });
4636
4656
  let promoted = 0;
4637
- for (const { frontierId: frontierId2, serviceId: serviceId9 } of toPromote) {
4657
+ for (const { frontierId: frontierId2, serviceId: serviceId15 } of toPromote) {
4638
4658
  if (opts.policies && opts.policies.length > 0 && opts.policyCtx) {
4639
4659
  const gate = canPromoteFrontier(graph, frontierId2, opts.policies, opts.policyCtx);
4640
4660
  if (!gate.allowed) {
4641
4661
  continue;
4642
4662
  }
4643
4663
  }
4644
- rewireFrontierEdges(graph, frontierId2, serviceId9);
4664
+ rewireFrontierEdges(graph, frontierId2, serviceId15);
4645
4665
  graph.dropNode(frontierId2);
4646
4666
  promoted++;
4647
4667
  }
4648
4668
  return promoted;
4649
4669
  }
4650
- function rewireFrontierEdges(graph, frontierId2, serviceId9) {
4670
+ function rewireFrontierEdges(graph, frontierId2, serviceId15) {
4651
4671
  const inbound = [...graph.inboundEdges(frontierId2)];
4652
4672
  const outbound = [...graph.outboundEdges(frontierId2)];
4653
4673
  for (const edgeId of inbound) {
4654
4674
  const edge = graph.getEdgeAttributes(edgeId);
4655
- rebuildEdge(graph, edge, edge.source, serviceId9, edgeId);
4675
+ rebuildEdge(graph, edge, edge.source, serviceId15, edgeId);
4656
4676
  }
4657
4677
  for (const edgeId of outbound) {
4658
4678
  const edge = graph.getEdgeAttributes(edgeId);
4659
- rebuildEdge(graph, edge, serviceId9, edge.target, edgeId);
4679
+ rebuildEdge(graph, edge, serviceId15, edge.target, edgeId);
4660
4680
  }
4661
4681
  }
4662
4682
  function rebuildEdge(graph, edge, newSource, newTarget, oldEdgeId) {
@@ -4988,19 +5008,19 @@ function confidenceFromMix(edges, now = Date.now()) {
4988
5008
  function longestIncomingWalk(graph, start, maxDepth) {
4989
5009
  let best = { path: [start], edges: [] };
4990
5010
  const visited = /* @__PURE__ */ new Set([start]);
4991
- function step(node, path64, edges) {
4992
- if (path64.length > best.path.length) {
4993
- best = { path: [...path64], edges: [...edges] };
5011
+ function step(node, path70, edges) {
5012
+ if (path70.length > best.path.length) {
5013
+ best = { path: [...path70], edges: [...edges] };
4994
5014
  }
4995
- if (path64.length - 1 >= maxDepth) return;
5015
+ if (path70.length - 1 >= maxDepth) return;
4996
5016
  const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
4997
5017
  for (const [srcId, edge] of incoming) {
4998
5018
  if (visited.has(srcId)) continue;
4999
5019
  visited.add(srcId);
5000
- path64.push(srcId);
5020
+ path70.push(srcId);
5001
5021
  edges.push(edge);
5002
- step(srcId, path64, edges);
5003
- path64.pop();
5022
+ step(srcId, path70, edges);
5023
+ path70.pop();
5004
5024
  edges.pop();
5005
5025
  visited.delete(srcId);
5006
5026
  }
@@ -5015,7 +5035,7 @@ function databaseRootCauseShape(graph, origin, walk9) {
5015
5035
  for (const id of walk9.path) {
5016
5036
  const owner = resolveOwningService(graph, id);
5017
5037
  if (!owner) continue;
5018
- const { id: serviceId9, svc } = owner;
5038
+ const { id: serviceId15, svc } = owner;
5019
5039
  const deps = svc.dependencies ?? {};
5020
5040
  for (const pair of candidatePairs) {
5021
5041
  const declared = deps[pair.driver];
@@ -5028,7 +5048,7 @@ function databaseRootCauseShape(graph, origin, walk9) {
5028
5048
  );
5029
5049
  if (!result.compatible) {
5030
5050
  return {
5031
- rootCauseNode: serviceId9,
5051
+ rootCauseNode: serviceId15,
5032
5052
  rootCauseReason: result.reason ?? "incompatible driver",
5033
5053
  ...result.minDriverVersion ? {
5034
5054
  fixRecommendation: `Upgrade ${svc.name} ${pair.driver} driver to >= ${result.minDriverVersion}`
@@ -5043,7 +5063,7 @@ function serviceRootCauseShape(graph, _origin, walk9) {
5043
5063
  for (const id of walk9.path) {
5044
5064
  const owner = resolveOwningService(graph, id);
5045
5065
  if (!owner) continue;
5046
- const { id: serviceId9, svc } = owner;
5066
+ const { id: serviceId15, svc } = owner;
5047
5067
  const deps = svc.dependencies ?? {};
5048
5068
  const serviceNodeEngine = svc.nodeEngine;
5049
5069
  for (const constraint of nodeEngineConstraints()) {
@@ -5052,7 +5072,7 @@ function serviceRootCauseShape(graph, _origin, walk9) {
5052
5072
  const result = checkNodeEngineConstraint(constraint, declared, serviceNodeEngine);
5053
5073
  if (!result.compatible && result.reason) {
5054
5074
  return {
5055
- rootCauseNode: serviceId9,
5075
+ rootCauseNode: serviceId15,
5056
5076
  rootCauseReason: result.reason,
5057
5077
  ...result.requiredNodeVersion ? {
5058
5078
  fixRecommendation: `Bump ${svc.name}'s engines.node to >= ${result.requiredNodeVersion}`
@@ -5067,7 +5087,7 @@ function serviceRootCauseShape(graph, _origin, walk9) {
5067
5087
  const result = checkPackageConflict(conflict, declared, requiredDeclared);
5068
5088
  if (!result.compatible && result.reason) {
5069
5089
  return {
5070
- rootCauseNode: serviceId9,
5090
+ rootCauseNode: serviceId15,
5071
5091
  rootCauseReason: result.reason,
5072
5092
  fixRecommendation: `Upgrade ${svc.name}'s ${conflict.requires.name} to >= ${conflict.requires.minVersion}`
5073
5093
  };
@@ -5168,9 +5188,9 @@ function rootCauseFromIncidents(nodeId, incidents, errorEvent) {
5168
5188
  function isFailingCallEdge(e) {
5169
5189
  return e.type === EdgeType6.CALLS && (e.signal?.errorCount ?? 0) > 0;
5170
5190
  }
5171
- function callSourcesForService(graph, serviceId9) {
5172
- const ids = [serviceId9];
5173
- for (const edgeId of graph.outboundEdges(serviceId9)) {
5191
+ function callSourcesForService(graph, serviceId15) {
5192
+ const ids = [serviceId15];
5193
+ for (const edgeId of graph.outboundEdges(serviceId15)) {
5174
5194
  const e = graph.getEdgeAttributes(edgeId);
5175
5195
  if (e.type !== EdgeType6.CONTAINS) continue;
5176
5196
  const tgt = graph.getNodeAttributes(e.target);
@@ -5194,9 +5214,9 @@ function failingCallDominates(e, id, curEdge, curId) {
5194
5214
  }
5195
5215
  return id < curId;
5196
5216
  }
5197
- function dominantFailingCall(graph, serviceId9, visited) {
5217
+ function dominantFailingCall(graph, serviceId15, visited) {
5198
5218
  let best = null;
5199
- for (const src of callSourcesForService(graph, serviceId9)) {
5219
+ for (const src of callSourcesForService(graph, serviceId15)) {
5200
5220
  for (const edgeId of graph.outboundEdges(src)) {
5201
5221
  const e = graph.getEdgeAttributes(edgeId);
5202
5222
  if (!isFailingCallEdge(e)) continue;
@@ -5211,26 +5231,26 @@ function dominantFailingCall(graph, serviceId9, visited) {
5211
5231
  return best;
5212
5232
  }
5213
5233
  function followFailingCallChain(graph, originServiceId, maxDepth) {
5214
- const path64 = [originServiceId];
5234
+ const path70 = [originServiceId];
5215
5235
  const edges = [];
5216
5236
  const visited = /* @__PURE__ */ new Set([originServiceId]);
5217
5237
  let current = originServiceId;
5218
5238
  for (let depth = 0; depth < maxDepth; depth++) {
5219
5239
  const hop = dominantFailingCall(graph, current, visited);
5220
5240
  if (!hop) break;
5221
- path64.push(hop.nextService);
5241
+ path70.push(hop.nextService);
5222
5242
  edges.push(hop.edge);
5223
5243
  visited.add(hop.nextService);
5224
5244
  current = hop.nextService;
5225
5245
  }
5226
5246
  if (edges.length === 0) return null;
5227
- return { path: path64, edges, culprit: current };
5247
+ return { path: path70, edges, culprit: current };
5228
5248
  }
5229
5249
  function crossServiceRootCause(graph, originId, incidents, errorEvent) {
5230
5250
  const chain = followFailingCallChain(graph, originId, ROOT_CAUSE_MAX_DEPTH);
5231
5251
  if (!chain) return null;
5232
5252
  const culprit = chain.culprit;
5233
- const path64 = [...chain.path];
5253
+ const path70 = [...chain.path];
5234
5254
  const edgeProvenances = chain.edges.map((e) => e.provenance);
5235
5255
  const baseConfidence = confidenceFromMix(chain.edges);
5236
5256
  const confidence = Math.max(0, Math.min(1, baseConfidence * INCIDENT_ROOT_CAUSE_CONFIDENCE));
@@ -5238,14 +5258,14 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
5238
5258
  if (loc) {
5239
5259
  let rootCauseNode = culprit;
5240
5260
  if (loc.fileNode) {
5241
- path64.push(loc.fileNode);
5261
+ path70.push(loc.fileNode);
5242
5262
  edgeProvenances.push(Provenance6.OBSERVED);
5243
5263
  rootCauseNode = loc.fileNode;
5244
5264
  }
5245
5265
  return RootCauseResultSchema.parse({
5246
5266
  rootCauseNode,
5247
5267
  rootCauseReason: loc.rootCauseReason,
5248
- traversalPath: path64,
5268
+ traversalPath: path70,
5249
5269
  edgeProvenances,
5250
5270
  confidence,
5251
5271
  ...loc.fixRecommendation ? { fixRecommendation: loc.fixRecommendation } : {}
@@ -5257,7 +5277,7 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
5257
5277
  return RootCauseResultSchema.parse({
5258
5278
  rootCauseNode: culprit,
5259
5279
  rootCauseReason: `${culpritName} is failing downstream calls (${errs} observed error${errs === 1 ? "" : "s"})`,
5260
- traversalPath: path64,
5280
+ traversalPath: path70,
5261
5281
  edgeProvenances,
5262
5282
  confidence,
5263
5283
  fixRecommendation: `Inspect ${culpritName}'s failing handler`
@@ -5693,10 +5713,10 @@ function enrichWithNavigation(graph, errorNodeId, legacy, incidents, now) {
5693
5713
  let traversalPath = legacy.traversalPath;
5694
5714
  let edgeProvenances = legacy.edgeProvenances;
5695
5715
  if (top.node !== seedNode) {
5696
- const path64 = findPath(graph, errorNodeId, top.node, "up", ROOT_CAUSE_MAX_DEPTH);
5697
- if (path64) {
5698
- traversalPath = path64.nodes;
5699
- edgeProvenances = path64.edges.map((e) => e.provenance);
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);
5700
5720
  } else {
5701
5721
  traversalPath = [errorNodeId, top.node];
5702
5722
  edgeProvenances = [top.provenance ?? Provenance6.OBSERVED];
@@ -5725,11 +5745,11 @@ function fixRecommendationForTop(top, seedNode, legacy) {
5725
5745
  }
5726
5746
 
5727
5747
  // src/extract/services.ts
5728
- import { promises as fs12 } from "fs";
5729
- import path14 from "path";
5748
+ import { promises as fs18 } from "fs";
5749
+ import path20 from "path";
5730
5750
  import ignore from "ignore";
5731
5751
  import { minimatch as minimatch2 } from "minimatch";
5732
- import { NodeType as NodeType9, serviceId as serviceId5 } from "@neat.is/types";
5752
+ import { NodeType as NodeType15, serviceId as serviceId11 } from "@neat.is/types";
5733
5753
 
5734
5754
  // src/extract/python.ts
5735
5755
  import { promises as fs8 } from "fs";
@@ -5918,18 +5938,391 @@ async function discoverPhpService(scanPath, dir) {
5918
5938
  return { pkg, dir, node };
5919
5939
  }
5920
5940
 
5921
- // src/extract/owners.ts
5941
+ // src/extract/csharp.ts
5922
5942
  import { promises as fs11 } from "fs";
5923
5943
  import path13 from "path";
5944
+ import { NodeType as NodeType9, serviceId as serviceId5 } from "@neat.is/types";
5945
+ var CSHARP_PROJECT_EXTS = /* @__PURE__ */ new Set([".csproj", ".sln"]);
5946
+ async function projectFiles(dir) {
5947
+ const entries = await fs11.readdir(dir, { withFileTypes: true }).catch(() => []);
5948
+ const out = [];
5949
+ for (const entry of entries) {
5950
+ if (!entry.isFile()) continue;
5951
+ if (CSHARP_PROJECT_EXTS.has(path13.extname(entry.name).toLowerCase())) out.push(entry.name);
5952
+ }
5953
+ return out.sort();
5954
+ }
5955
+ var NESTED_PROJECT_DIR = "src";
5956
+ async function resolveProjectDir(dir) {
5957
+ if ((await projectFiles(dir)).length > 0) return dir;
5958
+ const nested = path13.join(dir, NESTED_PROJECT_DIR);
5959
+ if ((await projectFiles(nested)).length > 0) return nested;
5960
+ return null;
5961
+ }
5962
+ async function hasCsharpProject(dir) {
5963
+ return await resolveProjectDir(dir) !== null;
5964
+ }
5965
+ function parseCsproj(source) {
5966
+ const dependencies = {};
5967
+ const re = /<PackageReference\b([^>]*?)\/?>/g;
5968
+ let m;
5969
+ while ((m = re.exec(source)) !== null) {
5970
+ const attrs = m[1] ?? "";
5971
+ const include = attrs.match(/\bInclude\s*=\s*"([^"]+)"/)?.[1];
5972
+ if (!include) continue;
5973
+ const version = attrs.match(/\bVersion\s*=\s*"([^"]+)"/)?.[1];
5974
+ dependencies[include] = version ?? "";
5975
+ }
5976
+ return { dependencies };
5977
+ }
5978
+ async function discoverCsharpService(scanPath, dir) {
5979
+ const projectDir = await resolveProjectDir(dir);
5980
+ if (projectDir === null) return null;
5981
+ const projects = await projectFiles(projectDir);
5982
+ const csproj = projects.find((p) => p.toLowerCase().endsWith(".csproj"));
5983
+ const name = csproj ? path13.basename(csproj, path13.extname(csproj)) : path13.basename(dir);
5984
+ let dependencies = {};
5985
+ if (csproj) {
5986
+ const raw = await fs11.readFile(path13.join(projectDir, csproj), "utf8").catch(() => "");
5987
+ dependencies = parseCsproj(raw).dependencies;
5988
+ }
5989
+ const pkg = { name, dependencies };
5990
+ const node = {
5991
+ id: serviceId5(name),
5992
+ type: NodeType9.ServiceNode,
5993
+ name,
5994
+ language: "csharp",
5995
+ dependencies,
5996
+ // Anchor on the directory that holds the project and its `.cs` files, so file
5997
+ // attribution reaches the source whether it sits at the root or under `src/`.
5998
+ repoPath: path13.relative(scanPath, projectDir)
5999
+ };
6000
+ return { pkg, dir: projectDir, node };
6001
+ }
6002
+
6003
+ // src/extract/java.ts
6004
+ import { promises as fs12 } from "fs";
6005
+ import path14 from "path";
6006
+ import { NodeType as NodeType10, serviceId as serviceId6 } from "@neat.is/types";
6007
+ var JAVA_MANIFESTS = ["pom.xml", "build.gradle", "build.gradle.kts"];
6008
+ async function hasJavaManifest(dir) {
6009
+ for (const manifest of JAVA_MANIFESTS) {
6010
+ if (await exists(path14.join(dir, manifest))) return true;
6011
+ }
6012
+ return false;
6013
+ }
6014
+ function parsePomXml(source) {
6015
+ const dependencies = {};
6016
+ const depBlock = /<dependency\b[^>]*>([\s\S]*?)<\/dependency>/g;
6017
+ let d;
6018
+ while ((d = depBlock.exec(source)) !== null) {
6019
+ const body = d[1] ?? "";
6020
+ const groupId = body.match(/<groupId>\s*([^<]+?)\s*<\/groupId>/)?.[1];
6021
+ const artifactId = body.match(/<artifactId>\s*([^<]+?)\s*<\/artifactId>/)?.[1];
6022
+ if (!groupId || !artifactId) continue;
6023
+ const version = body.match(/<version>\s*([^<]+?)\s*<\/version>/)?.[1];
6024
+ dependencies[`${groupId}:${artifactId}`] = version ?? "";
6025
+ }
6026
+ const withoutBlocks = source.replace(/<parent\b[^>]*>[\s\S]*?<\/parent>/g, "").replace(/<dependencyManagement\b[^>]*>[\s\S]*?<\/dependencyManagement>/g, "").replace(/<dependencies\b[^>]*>[\s\S]*?<\/dependencies>/g, "").replace(/<build\b[^>]*>[\s\S]*?<\/build>/g, "");
6027
+ const name = withoutBlocks.match(/<artifactId>\s*([^<]+?)\s*<\/artifactId>/)?.[1];
6028
+ return { name, dependencies };
6029
+ }
6030
+ var GRADLE_CONFIGS = "implementation|api|compileOnly|runtimeOnly|compileOnlyApi|testImplementation|testRuntimeOnly|testCompileOnly|annotationProcessor|developmentOnly|runtimeClasspath";
6031
+ function parseGradle(source) {
6032
+ const dependencies = {};
6033
+ const re = new RegExp(`\\b(?:${GRADLE_CONFIGS})\\s*\\(?\\s*(['"])([^'"]+)\\1`, "g");
6034
+ let m;
6035
+ while ((m = re.exec(source)) !== null) {
6036
+ const coord = m[2] ?? "";
6037
+ const parts = coord.split(":");
6038
+ if (parts.length < 2 || !parts[0] || !parts[1]) continue;
6039
+ const key = `${parts[0]}:${parts[1]}`;
6040
+ dependencies[key] = parts.slice(2).join(":");
6041
+ }
6042
+ return { dependencies };
6043
+ }
6044
+ async function discoverJavaService(scanPath, dir) {
6045
+ const pomPath = path14.join(dir, "pom.xml");
6046
+ const gradlePath = path14.join(dir, "build.gradle");
6047
+ const gradleKtsPath = path14.join(dir, "build.gradle.kts");
6048
+ let manifest = null;
6049
+ if (await exists(pomPath)) {
6050
+ manifest = parsePomXml(await fs12.readFile(pomPath, "utf8").catch(() => ""));
6051
+ } else if (await exists(gradlePath)) {
6052
+ manifest = parseGradle(await fs12.readFile(gradlePath, "utf8").catch(() => ""));
6053
+ } else if (await exists(gradleKtsPath)) {
6054
+ manifest = parseGradle(await fs12.readFile(gradleKtsPath, "utf8").catch(() => ""));
6055
+ }
6056
+ if (!manifest) return null;
6057
+ const name = manifest.name?.trim() || path14.basename(dir);
6058
+ const dependencies = manifest.dependencies;
6059
+ const pkg = { name, dependencies };
6060
+ const node = {
6061
+ id: serviceId6(name),
6062
+ type: NodeType10.ServiceNode,
6063
+ name,
6064
+ language: "java",
6065
+ dependencies,
6066
+ repoPath: path14.relative(scanPath, dir)
6067
+ };
6068
+ return { pkg, dir, node };
6069
+ }
6070
+
6071
+ // src/extract/kotlin.ts
6072
+ import { promises as fs13 } from "fs";
6073
+ import path15 from "path";
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";
6219
+ function languageForSourceExt(ext) {
6220
+ switch (ext) {
6221
+ case ".py":
6222
+ return "python";
6223
+ case ".go":
6224
+ return "go";
6225
+ case ".rb":
6226
+ return "ruby";
6227
+ case ".php":
6228
+ return "php";
6229
+ case ".cs":
6230
+ return "csharp";
6231
+ case ".java":
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";
6246
+ case ".ts":
6247
+ case ".tsx":
6248
+ return "typescript";
6249
+ case ".js":
6250
+ case ".mjs":
6251
+ case ".cjs":
6252
+ return "javascript";
6253
+ default:
6254
+ return null;
6255
+ }
6256
+ }
6257
+ var LANGUAGE_PRECEDENCE = ["typescript", "javascript", "python", "go", "ruby", "php", "csharp", "java", "kotlin", "rust", "cpp"];
6258
+ async function inferServiceLanguage(dir) {
6259
+ const entries = await fs16.readdir(dir, { withFileTypes: true }).catch(() => []);
6260
+ const counts = /* @__PURE__ */ new Map();
6261
+ for (const entry of entries) {
6262
+ if (!entry.isFile()) continue;
6263
+ if (isNeatAuthoredSourceFile(entry.name)) continue;
6264
+ const ext = path18.extname(entry.name).toLowerCase();
6265
+ if (!SERVICE_FILE_EXTENSIONS.has(ext)) continue;
6266
+ const lang = languageForSourceExt(ext);
6267
+ if (!lang) continue;
6268
+ counts.set(lang, (counts.get(lang) ?? 0) + 1);
6269
+ }
6270
+ if (counts.size === 0) return null;
6271
+ let best = null;
6272
+ let bestCount = -1;
6273
+ for (const lang of LANGUAGE_PRECEDENCE) {
6274
+ const c = counts.get(lang) ?? 0;
6275
+ if (c > bestCount) {
6276
+ best = lang;
6277
+ bestCount = c;
6278
+ }
6279
+ }
6280
+ return best;
6281
+ }
6282
+ async function hasLanguageManifest(dir) {
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
6284
+ // rather than probing a fixed filename (ADR-196).
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);
6291
+ }
6292
+ async function hasDockerfileDeclaredService(dir) {
6293
+ if (!await exists(path18.join(dir, "Dockerfile"))) return false;
6294
+ return await inferServiceLanguage(dir) !== null;
6295
+ }
6296
+ async function discoverDockerfileService(scanPath, dir) {
6297
+ if (!await exists(path18.join(dir, "Dockerfile"))) return null;
6298
+ if (await hasLanguageManifest(dir)) return null;
6299
+ const language = await inferServiceLanguage(dir);
6300
+ if (!language) return null;
6301
+ const name = path18.basename(dir);
6302
+ const pkg = { name, dependencies: {} };
6303
+ const node = {
6304
+ id: serviceId10(name),
6305
+ type: NodeType14.ServiceNode,
6306
+ name,
6307
+ language,
6308
+ dependencies: {},
6309
+ repoPath: path18.relative(scanPath, dir)
6310
+ };
6311
+ return { pkg, dir, node };
6312
+ }
6313
+
6314
+ // src/extract/owners.ts
6315
+ import { promises as fs17 } from "fs";
6316
+ import path19 from "path";
5924
6317
  import { minimatch } from "minimatch";
5925
6318
  async function loadCodeowners(scanPath) {
5926
6319
  const candidates = [
5927
- path13.join(scanPath, "CODEOWNERS"),
5928
- path13.join(scanPath, ".github", "CODEOWNERS")
6320
+ path19.join(scanPath, "CODEOWNERS"),
6321
+ path19.join(scanPath, ".github", "CODEOWNERS")
5929
6322
  ];
5930
6323
  for (const file of candidates) {
5931
6324
  if (await exists(file)) {
5932
- const raw = await fs11.readFile(file, "utf8");
6325
+ const raw = await fs17.readFile(file, "utf8");
5933
6326
  return parseCodeowners(raw);
5934
6327
  }
5935
6328
  }
@@ -5947,7 +6340,7 @@ function parseCodeowners(raw) {
5947
6340
  return { rules };
5948
6341
  }
5949
6342
  function matchOwner(file, repoPath) {
5950
- const normalized = repoPath.split(path13.sep).join("/");
6343
+ const normalized = repoPath.split(path19.sep).join("/");
5951
6344
  for (const rule of file.rules) {
5952
6345
  if (matchesPattern(rule.pattern, normalized)) return rule.owners;
5953
6346
  }
@@ -5963,7 +6356,7 @@ function matchesPattern(rawPattern, repoPath) {
5963
6356
  return false;
5964
6357
  }
5965
6358
  async function readPackageJsonAuthor(serviceDir) {
5966
- const pkgPath = path13.join(serviceDir, "package.json");
6359
+ const pkgPath = path19.join(serviceDir, "package.json");
5967
6360
  if (!await exists(pkgPath)) return null;
5968
6361
  try {
5969
6362
  const pkg = await readJson(pkgPath);
@@ -6000,33 +6393,36 @@ function workspaceGlobs(pkg) {
6000
6393
  return null;
6001
6394
  }
6002
6395
  async function hasPythonManifest(dir) {
6003
- return await exists(path14.join(dir, "pyproject.toml")) || await exists(path14.join(dir, "requirements.txt")) || await exists(path14.join(dir, "setup.py"));
6396
+ return await exists(path20.join(dir, "pyproject.toml")) || await exists(path20.join(dir, "requirements.txt")) || await exists(path20.join(dir, "setup.py"));
6004
6397
  }
6005
6398
  async function hasGoManifest(dir) {
6006
- return exists(path14.join(dir, "go.mod"));
6399
+ return exists(path20.join(dir, "go.mod"));
6007
6400
  }
6008
6401
  async function hasRubyManifest(dir) {
6009
- return exists(path14.join(dir, "Gemfile"));
6402
+ return exists(path20.join(dir, "Gemfile"));
6010
6403
  }
6011
6404
  async function hasPhpManifest(dir) {
6012
- return exists(path14.join(dir, "composer.json"));
6405
+ return exists(path20.join(dir, "composer.json"));
6406
+ }
6407
+ async function hasCsharpManifest(dir) {
6408
+ return hasCsharpProject(dir);
6013
6409
  }
6014
6410
  async function loadGitignore(scanPath) {
6015
- const gitignorePath = path14.join(scanPath, ".gitignore");
6411
+ const gitignorePath = path20.join(scanPath, ".gitignore");
6016
6412
  if (!await exists(gitignorePath)) return null;
6017
- const raw = await fs12.readFile(gitignorePath, "utf8");
6413
+ const raw = await fs18.readFile(gitignorePath, "utf8");
6018
6414
  return ignore().add(raw);
6019
6415
  }
6020
6416
  async function walkDirs(start, scanPath, options, visit) {
6021
6417
  async function recurse(current, depth) {
6022
6418
  if (depth > options.maxDepth) return;
6023
- const entries = await fs12.readdir(current, { withFileTypes: true }).catch(() => []);
6419
+ const entries = await fs18.readdir(current, { withFileTypes: true }).catch(() => []);
6024
6420
  for (const entry of entries) {
6025
6421
  if (!entry.isDirectory()) continue;
6026
6422
  if (IGNORED_DIRS.has(entry.name)) continue;
6027
- const child = path14.join(current, entry.name);
6423
+ const child = path20.join(current, entry.name);
6028
6424
  if (options.ig) {
6029
- const rel = path14.relative(scanPath, child).split(path14.sep).join("/");
6425
+ const rel = path20.relative(scanPath, child).split(path20.sep).join("/");
6030
6426
  if (rel && options.ig.ignores(rel + "/")) continue;
6031
6427
  }
6032
6428
  if (await isPythonVenvDir(child)) continue;
@@ -6042,8 +6438,8 @@ async function expandWorkspaceGlobs(scanPath, globs) {
6042
6438
  for (const raw of globs) {
6043
6439
  const pattern = raw.replace(/^\.\//, "");
6044
6440
  if (!pattern.includes("*")) {
6045
- const candidate = path14.join(scanPath, pattern);
6046
- if (await exists(path14.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);
6047
6443
  continue;
6048
6444
  }
6049
6445
  const segments = pattern.split("/");
@@ -6052,13 +6448,13 @@ async function expandWorkspaceGlobs(scanPath, globs) {
6052
6448
  if (seg.includes("*")) break;
6053
6449
  staticSegments.push(seg);
6054
6450
  }
6055
- const start = path14.join(scanPath, ...staticSegments);
6451
+ const start = path20.join(scanPath, ...staticSegments);
6056
6452
  if (!await exists(start)) continue;
6057
6453
  const hasDoubleStar = pattern.includes("**");
6058
6454
  const walkDepth = hasDoubleStar ? scanDepth : Math.max(0, segments.length - staticSegments.length - 1);
6059
6455
  await walkDirs(start, scanPath, { maxDepth: walkDepth, ig: null }, async (dir) => {
6060
- const rel = path14.relative(scanPath, dir).split(path14.sep).join("/");
6061
- if (minimatch2(rel, pattern) && await exists(path14.join(dir, "package.json"))) {
6456
+ const rel = path20.relative(scanPath, dir).split(path20.sep).join("/");
6457
+ if (minimatch2(rel, pattern) && await exists(path20.join(dir, "package.json"))) {
6062
6458
  found.add(dir);
6063
6459
  }
6064
6460
  });
@@ -6081,31 +6477,31 @@ function detectJsFramework(pkg) {
6081
6477
  async function detectJsServiceLanguage(dir, pkg) {
6082
6478
  const deps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
6083
6479
  if (deps["typescript"] !== void 0) return "typescript";
6084
- const entries = await fs12.readdir(dir).catch(() => []);
6480
+ const entries = await fs18.readdir(dir).catch(() => []);
6085
6481
  if (entries.some((name) => /^tsconfig(\..+)?\.json$/.test(name))) return "typescript";
6086
6482
  return "javascript";
6087
6483
  }
6088
6484
  async function discoverNodeService(scanPath, dir) {
6089
- const pkgPath = path14.join(dir, "package.json");
6485
+ const pkgPath = path20.join(dir, "package.json");
6090
6486
  if (!await exists(pkgPath)) return null;
6091
6487
  let pkg;
6092
6488
  try {
6093
6489
  pkg = await readJson(pkgPath);
6094
6490
  } catch (err) {
6095
- recordExtractionError("services", path14.relative(scanPath, pkgPath), err);
6491
+ recordExtractionError("services", path20.relative(scanPath, pkgPath), err);
6096
6492
  return null;
6097
6493
  }
6098
6494
  if (!pkg.name) return null;
6099
6495
  const framework = detectJsFramework(pkg);
6100
6496
  const language = await detectJsServiceLanguage(dir, pkg);
6101
6497
  const node = {
6102
- id: serviceId5(pkg.name),
6103
- type: NodeType9.ServiceNode,
6498
+ id: serviceId11(pkg.name),
6499
+ type: NodeType15.ServiceNode,
6104
6500
  name: pkg.name,
6105
6501
  language,
6106
6502
  version: pkg.version,
6107
6503
  dependencies: pkg.dependencies ?? {},
6108
- repoPath: path14.relative(scanPath, dir),
6504
+ repoPath: path20.relative(scanPath, dir),
6109
6505
  ...pkg.engines?.node ? { nodeEngine: pkg.engines.node } : {},
6110
6506
  ...framework ? { framework } : {}
6111
6507
  };
@@ -6116,18 +6512,18 @@ async function discoverPyService(scanPath, dir) {
6116
6512
  if (!py) return null;
6117
6513
  const pkg = pythonToPackage(py);
6118
6514
  const node = {
6119
- id: serviceId5(py.name),
6120
- type: NodeType9.ServiceNode,
6515
+ id: serviceId11(py.name),
6516
+ type: NodeType15.ServiceNode,
6121
6517
  name: py.name,
6122
6518
  language: "python",
6123
6519
  version: py.version,
6124
6520
  dependencies: py.dependencies,
6125
- repoPath: path14.relative(scanPath, dir)
6521
+ repoPath: path20.relative(scanPath, dir)
6126
6522
  };
6127
6523
  return { pkg, dir, node };
6128
6524
  }
6129
6525
  async function discoverServices(scanPath) {
6130
- const rootPkgPath = path14.join(scanPath, "package.json");
6526
+ const rootPkgPath = path20.join(scanPath, "package.json");
6131
6527
  let rootPkg = null;
6132
6528
  if (await exists(rootPkgPath)) {
6133
6529
  try {
@@ -6135,7 +6531,7 @@ async function discoverServices(scanPath) {
6135
6531
  } catch (err) {
6136
6532
  recordExtractionError(
6137
6533
  "services workspaces",
6138
- path14.relative(scanPath, rootPkgPath),
6534
+ path20.relative(scanPath, rootPkgPath),
6139
6535
  err
6140
6536
  );
6141
6537
  }
@@ -6147,7 +6543,7 @@ async function discoverServices(scanPath) {
6147
6543
  } else {
6148
6544
  if (rootPkg && rootPkg.name) {
6149
6545
  candidateDirs.push(scanPath);
6150
- } else if (await hasPythonManifest(scanPath) || await hasGoManifest(scanPath) || await hasRubyManifest(scanPath) || await hasPhpManifest(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)) {
6151
6547
  candidateDirs.push(scanPath);
6152
6548
  }
6153
6549
  const ig = await loadGitignore(scanPath);
@@ -6156,9 +6552,11 @@ async function discoverServices(scanPath) {
6156
6552
  scanPath,
6157
6553
  { maxDepth: parseScanDepth(), ig },
6158
6554
  async (dir) => {
6159
- if (await exists(path14.join(dir, "package.json"))) {
6555
+ if (await exists(path20.join(dir, "package.json"))) {
6160
6556
  candidateDirs.push(dir);
6161
- } else if (await hasPythonManifest(dir) || await hasGoManifest(dir) || await hasRubyManifest(dir) || await hasPhpManifest(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)) {
6558
+ candidateDirs.push(dir);
6559
+ } else if (await hasDockerfileDeclaredService(dir)) {
6162
6560
  candidateDirs.push(dir);
6163
6561
  }
6164
6562
  }
@@ -6166,14 +6564,24 @@ async function discoverServices(scanPath) {
6166
6564
  }
6167
6565
  candidateDirs.sort();
6168
6566
  const seen = /* @__PURE__ */ new Map();
6567
+ const seenDirs = /* @__PURE__ */ new Set();
6169
6568
  const out = [];
6170
6569
  for (const dir of candidateDirs) {
6171
- const service = await discoverNodeService(scanPath, dir) ?? await discoverPyService(scanPath, dir) ?? await discoverGoService(scanPath, dir) ?? await discoverRubyService(scanPath, dir) ?? await discoverPhpService(scanPath, dir);
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);
6172
6578
  if (!service) continue;
6579
+ if (seenDirs.has(service.dir)) continue;
6580
+ seenDirs.add(service.dir);
6173
6581
  const existingDir = seen.get(service.node.name);
6174
6582
  if (existingDir !== void 0) {
6175
- const a = path14.relative(scanPath, existingDir) || ".";
6176
- const b = path14.relative(scanPath, dir) || ".";
6583
+ const a = path20.relative(scanPath, existingDir) || ".";
6584
+ const b = path20.relative(scanPath, dir) || ".";
6177
6585
  console.warn(
6178
6586
  `[neat] duplicate package name "${service.node.name}" \u2014 keeping ${a}, ignoring ${b}`
6179
6587
  );
@@ -6187,6 +6595,17 @@ async function discoverServices(scanPath) {
6187
6595
  const owner = await computeServiceOwner(codeowners, service.node.repoPath, service.dir);
6188
6596
  if (owner !== void 0) service.node.owner = owner;
6189
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
+ }
6190
6609
  return out;
6191
6610
  }
6192
6611
  function addServiceNodes(graph, services) {
@@ -6209,20 +6628,20 @@ function addServiceNodes(graph, services) {
6209
6628
  }
6210
6629
 
6211
6630
  // src/extract/aliases.ts
6212
- import path15 from "path";
6213
- import { promises as fs13 } from "fs";
6631
+ import path21 from "path";
6632
+ import { promises as fs19 } from "fs";
6214
6633
  import { parseAllDocuments } from "yaml";
6215
- import { NodeType as NodeType10 } from "@neat.is/types";
6634
+ import { NodeType as NodeType16 } from "@neat.is/types";
6216
6635
  var K8S_KINDS_WITH_HOSTNAMES = /* @__PURE__ */ new Set([
6217
6636
  "Service",
6218
6637
  "Deployment",
6219
6638
  "StatefulSet",
6220
6639
  "DaemonSet"
6221
6640
  ]);
6222
- function addAliases(graph, serviceId9, candidates) {
6223
- if (!graph.hasNode(serviceId9)) return;
6224
- const node = graph.getNodeAttributes(serviceId9);
6225
- if (node.type !== NodeType10.ServiceNode) return;
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;
6226
6645
  const set = new Set(node.aliases ?? []);
6227
6646
  for (const c of candidates) {
6228
6647
  if (!c) continue;
@@ -6231,20 +6650,20 @@ function addAliases(graph, serviceId9, candidates) {
6231
6650
  }
6232
6651
  if (set.size === 0) return;
6233
6652
  const updated = { ...node, aliases: [...set].sort() };
6234
- graph.replaceNodeAttributes(serviceId9, updated);
6653
+ graph.replaceNodeAttributes(serviceId15, updated);
6235
6654
  }
6236
6655
  function indexServicesByName(services) {
6237
6656
  const map = /* @__PURE__ */ new Map();
6238
6657
  for (const s of services) {
6239
6658
  map.set(s.node.name, s.node.id);
6240
- map.set(path15.basename(s.dir), s.node.id);
6659
+ map.set(path21.basename(s.dir), s.node.id);
6241
6660
  }
6242
6661
  return map;
6243
6662
  }
6244
6663
  async function collectComposeAliases(graph, scanPath, serviceIndex) {
6245
6664
  let composePath = null;
6246
6665
  for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
6247
- const abs = path15.join(scanPath, name);
6666
+ const abs = path21.join(scanPath, name);
6248
6667
  if (await exists(abs)) {
6249
6668
  composePath = abs;
6250
6669
  break;
@@ -6257,19 +6676,19 @@ async function collectComposeAliases(graph, scanPath, serviceIndex) {
6257
6676
  } catch (err) {
6258
6677
  recordExtractionError(
6259
6678
  "aliases compose",
6260
- path15.relative(scanPath, composePath),
6679
+ path21.relative(scanPath, composePath),
6261
6680
  err
6262
6681
  );
6263
6682
  return;
6264
6683
  }
6265
6684
  if (!compose?.services) return;
6266
6685
  for (const [composeName, svc] of Object.entries(compose.services)) {
6267
- const serviceId9 = serviceIndex.get(composeName);
6268
- if (!serviceId9) continue;
6686
+ const serviceId15 = serviceIndex.get(composeName);
6687
+ if (!serviceId15) continue;
6269
6688
  const aliases = /* @__PURE__ */ new Set([composeName]);
6270
6689
  if (svc.container_name) aliases.add(svc.container_name);
6271
6690
  if (svc.hostname) aliases.add(svc.hostname);
6272
- addAliases(graph, serviceId9, aliases);
6691
+ addAliases(graph, serviceId15, aliases);
6273
6692
  }
6274
6693
  }
6275
6694
  var LABEL_KEYS = /* @__PURE__ */ new Set([
@@ -6300,11 +6719,11 @@ function parseDockerfileLabels(content) {
6300
6719
  }
6301
6720
  async function collectDockerfileAliases(graph, services) {
6302
6721
  for (const service of services) {
6303
- const dockerfilePath = path15.join(service.dir, "Dockerfile");
6722
+ const dockerfilePath = path21.join(service.dir, "Dockerfile");
6304
6723
  if (!await exists(dockerfilePath)) continue;
6305
6724
  let content;
6306
6725
  try {
6307
- content = await fs13.readFile(dockerfilePath, "utf8");
6726
+ content = await fs19.readFile(dockerfilePath, "utf8");
6308
6727
  } catch (err) {
6309
6728
  recordExtractionError("aliases dockerfile", dockerfilePath, err);
6310
6729
  continue;
@@ -6316,15 +6735,15 @@ async function collectDockerfileAliases(graph, services) {
6316
6735
  async function walkYamlFiles(start, depth = 0, max = 5) {
6317
6736
  if (depth > max) return [];
6318
6737
  const out = [];
6319
- const entries = await fs13.readdir(start, { withFileTypes: true }).catch(() => []);
6738
+ const entries = await fs19.readdir(start, { withFileTypes: true }).catch(() => []);
6320
6739
  for (const entry of entries) {
6321
6740
  if (entry.isDirectory()) {
6322
6741
  if (IGNORED_DIRS.has(entry.name)) continue;
6323
- const child = path15.join(start, entry.name);
6742
+ const child = path21.join(start, entry.name);
6324
6743
  if (await isPythonVenvDir(child)) continue;
6325
6744
  out.push(...await walkYamlFiles(child, depth + 1, max));
6326
- } else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(path15.extname(entry.name))) {
6327
- out.push(path15.join(start, entry.name));
6745
+ } else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(path21.extname(entry.name))) {
6746
+ out.push(path21.join(start, entry.name));
6328
6747
  }
6329
6748
  }
6330
6749
  return out;
@@ -6351,7 +6770,7 @@ function k8sServiceTarget(doc, byName) {
6351
6770
  async function collectK8sAliases(graph, scanPath, serviceIndex) {
6352
6771
  const files = await walkYamlFiles(scanPath);
6353
6772
  for (const file of files) {
6354
- const content = await fs13.readFile(file, "utf8");
6773
+ const content = await fs19.readFile(file, "utf8");
6355
6774
  let docs;
6356
6775
  try {
6357
6776
  docs = parseAllDocuments(content).map((d) => d.toJSON());
@@ -6375,14 +6794,14 @@ async function addServiceAliases(graph, scanPath, services) {
6375
6794
  }
6376
6795
 
6377
6796
  // src/extract/files.ts
6378
- import path16 from "path";
6797
+ import path22 from "path";
6379
6798
  async function addFiles(graph, services) {
6380
6799
  let nodesAdded = 0;
6381
6800
  let edgesAdded = 0;
6382
6801
  for (const service of services) {
6383
- const filePaths = await walkSourceFiles(service.dir);
6802
+ const filePaths = await walkSourceFiles(service.dir, service.excludeDirs);
6384
6803
  for (const filePath of filePaths) {
6385
- const relPath = toPosix(path16.relative(service.dir, filePath));
6804
+ const relPath = toPosix(path22.relative(service.dir, filePath));
6386
6805
  const { nodesAdded: n, edgesAdded: e } = ensureFileNode(
6387
6806
  graph,
6388
6807
  service.pkg.name,
@@ -6397,13 +6816,22 @@ async function addFiles(graph, services) {
6397
6816
  }
6398
6817
 
6399
6818
  // src/extract/symbols.ts
6400
- import path17 from "path";
6819
+ import path23 from "path";
6401
6820
  import Parser3 from "tree-sitter";
6402
6821
  import JavaScript3 from "tree-sitter-javascript";
6403
6822
  import TypeScript from "tree-sitter-typescript";
6823
+ import Python3 from "tree-sitter-python";
6824
+ import Go3 from "tree-sitter-go";
6825
+ import Ruby2 from "tree-sitter-ruby";
6826
+ import Php2 from "tree-sitter-php";
6827
+ import CSharp from "tree-sitter-c-sharp";
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";
6404
6832
  import {
6405
6833
  EdgeType as EdgeType7,
6406
- NodeType as NodeType11,
6834
+ NodeType as NodeType17,
6407
6835
  Provenance as Provenance7,
6408
6836
  confidenceForExtracted as confidenceForExtracted4,
6409
6837
  extractedEdgeId as extractedEdgeId6,
@@ -6418,6 +6846,33 @@ var GRAMMAR_BY_EXT = {
6418
6846
  ".mjs": JavaScript3,
6419
6847
  ".cjs": JavaScript3
6420
6848
  };
6849
+ var SYMBOL_GRAMMAR_BY_EXT = {
6850
+ ...GRAMMAR_BY_EXT,
6851
+ ".py": Python3,
6852
+ ".go": Go3,
6853
+ ".rb": Ruby2,
6854
+ ".php": Php2.php_only,
6855
+ ".cs": CSharp,
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
6875
+ };
6421
6876
  function parseSource3(parser, source) {
6422
6877
  return parser.parse(
6423
6878
  (index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK3)
@@ -6427,7 +6882,479 @@ function methodName(node) {
6427
6882
  const name = node.childForFieldName("name");
6428
6883
  return name ? name.text : null;
6429
6884
  }
6430
- function collectSymbolDefs(root) {
6885
+ function collectSymbolDefs(root) {
6886
+ const out = [];
6887
+ const push = (kind, qualname, node) => {
6888
+ out.push({
6889
+ kind,
6890
+ qualname,
6891
+ startLine: node.startPosition.row + 1,
6892
+ endLine: node.endPosition.row + 1
6893
+ });
6894
+ };
6895
+ const visit = (node, classCtx) => {
6896
+ switch (node.type) {
6897
+ case "function_declaration":
6898
+ case "generator_function_declaration": {
6899
+ const name = node.childForFieldName("name")?.text;
6900
+ if (name) push("function", name, node);
6901
+ break;
6902
+ }
6903
+ case "class_declaration":
6904
+ case "abstract_class_declaration":
6905
+ case "class": {
6906
+ const name = node.childForFieldName("name")?.text;
6907
+ if (name) push("class", name, node);
6908
+ const body = node.childForFieldName("body");
6909
+ if (body) {
6910
+ for (let i = 0; i < body.namedChildCount; i++) {
6911
+ const child = body.namedChild(i);
6912
+ if (child) visit(child, name ?? classCtx);
6913
+ }
6914
+ }
6915
+ return;
6916
+ }
6917
+ case "method_definition": {
6918
+ const name = methodName(node);
6919
+ if (name) {
6920
+ const kind = name === "constructor" ? "constructor" : "method";
6921
+ push(kind, classCtx ? `${classCtx}.${name}` : name, node);
6922
+ }
6923
+ break;
6924
+ }
6925
+ case "variable_declarator": {
6926
+ const value = node.childForFieldName("value");
6927
+ if (value && (value.type === "arrow_function" || value.type === "function" || value.type === "function_expression" || value.type === "generator_function")) {
6928
+ const nameNode = node.childForFieldName("name");
6929
+ if (nameNode && nameNode.type === "identifier") {
6930
+ push("function", nameNode.text, node);
6931
+ }
6932
+ }
6933
+ break;
6934
+ }
6935
+ }
6936
+ for (let i = 0; i < node.namedChildCount; i++) {
6937
+ const child = node.namedChild(i);
6938
+ if (child) visit(child, classCtx);
6939
+ }
6940
+ };
6941
+ visit(root, void 0);
6942
+ return out;
6943
+ }
6944
+ function collectPythonSymbolDefs(root) {
6945
+ const out = [];
6946
+ const push = (kind, qualname, node) => {
6947
+ out.push({
6948
+ kind,
6949
+ qualname,
6950
+ startLine: node.startPosition.row + 1,
6951
+ endLine: node.endPosition.row + 1
6952
+ });
6953
+ };
6954
+ const visit = (node, classCtx) => {
6955
+ switch (node.type) {
6956
+ case "function_definition": {
6957
+ const name = node.childForFieldName("name")?.text;
6958
+ if (name) {
6959
+ if (classCtx) {
6960
+ const kind = name === "__init__" ? "constructor" : "method";
6961
+ push(kind, `${classCtx}.${name}`, node);
6962
+ } else {
6963
+ push("function", name, node);
6964
+ }
6965
+ }
6966
+ const body = node.childForFieldName("body");
6967
+ if (body) {
6968
+ for (let i = 0; i < body.namedChildCount; i++) {
6969
+ const child = body.namedChild(i);
6970
+ if (child) visit(child, void 0);
6971
+ }
6972
+ }
6973
+ return;
6974
+ }
6975
+ case "class_definition": {
6976
+ const name = node.childForFieldName("name")?.text;
6977
+ if (name) push("class", name, node);
6978
+ const body = node.childForFieldName("body");
6979
+ if (body) {
6980
+ for (let i = 0; i < body.namedChildCount; i++) {
6981
+ const child = body.namedChild(i);
6982
+ if (child) visit(child, name ?? classCtx);
6983
+ }
6984
+ }
6985
+ return;
6986
+ }
6987
+ case "decorated_definition": {
6988
+ const def = node.childForFieldName("definition");
6989
+ if (def) visit(def, classCtx);
6990
+ return;
6991
+ }
6992
+ }
6993
+ for (let i = 0; i < node.namedChildCount; i++) {
6994
+ const child = node.namedChild(i);
6995
+ if (child) visit(child, classCtx);
6996
+ }
6997
+ };
6998
+ visit(root, void 0);
6999
+ return out;
7000
+ }
7001
+ function goReceiverType(node) {
7002
+ const receiver = node.childForFieldName("receiver");
7003
+ if (!receiver) return void 0;
7004
+ for (let i = 0; i < receiver.namedChildCount; i++) {
7005
+ const param = receiver.namedChild(i);
7006
+ if (param?.type !== "parameter_declaration") continue;
7007
+ let typeNode = param.childForFieldName("type");
7008
+ if (typeNode?.type === "pointer_type") typeNode = typeNode.namedChild(0) ?? typeNode;
7009
+ if (typeNode?.type === "generic_type") {
7010
+ typeNode = typeNode.childForFieldName("type") ?? typeNode.namedChild(0) ?? typeNode;
7011
+ }
7012
+ if (typeNode?.type === "type_identifier") return typeNode.text;
7013
+ return typeNode?.text;
7014
+ }
7015
+ return void 0;
7016
+ }
7017
+ function collectGoSymbolDefs(root) {
7018
+ const out = [];
7019
+ const push = (kind, qualname, node) => {
7020
+ out.push({
7021
+ kind,
7022
+ qualname,
7023
+ startLine: node.startPosition.row + 1,
7024
+ endLine: node.endPosition.row + 1
7025
+ });
7026
+ };
7027
+ const visit = (node) => {
7028
+ switch (node.type) {
7029
+ case "function_declaration": {
7030
+ const name = node.childForFieldName("name")?.text;
7031
+ if (name) push("function", name, node);
7032
+ break;
7033
+ }
7034
+ case "method_declaration": {
7035
+ const name = node.childForFieldName("name")?.text;
7036
+ if (name) {
7037
+ const recv = goReceiverType(node);
7038
+ push("method", recv ? `${recv}.${name}` : name, node);
7039
+ }
7040
+ break;
7041
+ }
7042
+ }
7043
+ for (let i = 0; i < node.namedChildCount; i++) {
7044
+ const child = node.namedChild(i);
7045
+ if (child) visit(child);
7046
+ }
7047
+ };
7048
+ visit(root);
7049
+ return out;
7050
+ }
7051
+ function collectRubySymbolDefs(root) {
7052
+ const out = [];
7053
+ const push = (kind, qualname, node) => {
7054
+ out.push({
7055
+ kind,
7056
+ qualname,
7057
+ startLine: node.startPosition.row + 1,
7058
+ endLine: node.endPosition.row + 1
7059
+ });
7060
+ };
7061
+ const nameText = (node) => node.childForFieldName("name")?.text.replace(/::/g, ".");
7062
+ const walkBody = (node, ctx) => {
7063
+ const body = node.childForFieldName("body");
7064
+ if (!body) return;
7065
+ for (let i = 0; i < body.namedChildCount; i++) {
7066
+ const child = body.namedChild(i);
7067
+ if (child) visit(child, ctx);
7068
+ }
7069
+ };
7070
+ const visit = (node, classCtx) => {
7071
+ switch (node.type) {
7072
+ case "method":
7073
+ case "singleton_method": {
7074
+ const name = node.childForFieldName("name")?.text;
7075
+ if (name) {
7076
+ if (classCtx) {
7077
+ const kind = node.type === "method" && name === "initialize" ? "constructor" : "method";
7078
+ push(kind, `${classCtx}.${name}`, node);
7079
+ } else {
7080
+ push("function", name, node);
7081
+ }
7082
+ }
7083
+ walkBody(node, void 0);
7084
+ return;
7085
+ }
7086
+ case "class":
7087
+ case "module": {
7088
+ const name = nameText(node);
7089
+ if (name) push("class", classCtx ? `${classCtx}.${name}` : name, node);
7090
+ walkBody(node, name ? classCtx ? `${classCtx}.${name}` : name : classCtx);
7091
+ return;
7092
+ }
7093
+ }
7094
+ for (let i = 0; i < node.namedChildCount; i++) {
7095
+ const child = node.namedChild(i);
7096
+ if (child) visit(child, classCtx);
7097
+ }
7098
+ };
7099
+ visit(root, void 0);
7100
+ return out;
7101
+ }
7102
+ function collectPhpSymbolDefs(root) {
7103
+ const out = [];
7104
+ const push = (kind, qualname, node) => {
7105
+ out.push({
7106
+ kind,
7107
+ qualname,
7108
+ startLine: node.startPosition.row + 1,
7109
+ endLine: node.endPosition.row + 1
7110
+ });
7111
+ };
7112
+ const dot = (s) => s.replace(/\\/g, ".").replace(/::/g, ".").replace(/^\.+/, "");
7113
+ const join = (prefix, name) => prefix ? `${prefix}.${name}` : name;
7114
+ const walkChildren = (container, nsCtx, classCtx) => {
7115
+ let ns = nsCtx;
7116
+ for (let i = 0; i < container.namedChildCount; i++) {
7117
+ const child = container.namedChild(i);
7118
+ if (!child) continue;
7119
+ if (child.type === "namespace_definition" && !child.childForFieldName("body")) {
7120
+ const nameNode = child.childForFieldName("name");
7121
+ if (nameNode) ns = dot(nameNode.text);
7122
+ continue;
7123
+ }
7124
+ visit(child, ns, classCtx);
7125
+ }
7126
+ };
7127
+ const walkBody = (node, nsCtx, classCtx) => {
7128
+ const body = node.childForFieldName("body");
7129
+ if (body) walkChildren(body, nsCtx, classCtx);
7130
+ };
7131
+ const visit = (node, nsCtx, classCtx) => {
7132
+ switch (node.type) {
7133
+ case "function_definition": {
7134
+ const name = node.childForFieldName("name")?.text;
7135
+ if (name) push("function", join(nsCtx, name), node);
7136
+ walkBody(node, nsCtx, void 0);
7137
+ return;
7138
+ }
7139
+ case "class_declaration":
7140
+ case "trait_declaration":
7141
+ case "interface_declaration": {
7142
+ const name = node.childForFieldName("name")?.text;
7143
+ const full = name ? join(nsCtx, name) : classCtx;
7144
+ if (name) push("class", full, node);
7145
+ walkBody(node, nsCtx, full);
7146
+ return;
7147
+ }
7148
+ case "method_declaration": {
7149
+ const name = node.childForFieldName("name")?.text;
7150
+ if (name) {
7151
+ const kind = name === "__construct" ? "constructor" : "method";
7152
+ push(kind, join(classCtx ?? nsCtx, name), node);
7153
+ }
7154
+ return;
7155
+ }
7156
+ case "namespace_definition": {
7157
+ const nameNode = node.childForFieldName("name");
7158
+ const ns = nameNode ? dot(nameNode.text) : nsCtx;
7159
+ walkBody(node, ns, classCtx);
7160
+ return;
7161
+ }
7162
+ }
7163
+ walkChildren(node, nsCtx, classCtx);
7164
+ };
7165
+ walkChildren(root, void 0, void 0);
7166
+ return out;
7167
+ }
7168
+ function collectCsharpSymbolDefs(root) {
7169
+ const out = [];
7170
+ const push = (kind, qualname, node) => {
7171
+ out.push({
7172
+ kind,
7173
+ qualname,
7174
+ startLine: node.startPosition.row + 1,
7175
+ endLine: node.endPosition.row + 1
7176
+ });
7177
+ };
7178
+ const join = (prefix, name) => prefix ? `${prefix}.${name}` : name;
7179
+ const walkChildren = (container, nsCtx, classCtx) => {
7180
+ let ns = nsCtx;
7181
+ for (let i = 0; i < container.namedChildCount; i++) {
7182
+ const child = container.namedChild(i);
7183
+ if (!child) continue;
7184
+ if (child.type === "file_scoped_namespace_declaration") {
7185
+ const nameNode = child.childForFieldName("name");
7186
+ if (nameNode) ns = join(nsCtx, nameNode.text);
7187
+ continue;
7188
+ }
7189
+ visit(child, ns, classCtx);
7190
+ }
7191
+ };
7192
+ const walkBody = (node, nsCtx, classCtx) => {
7193
+ const body = node.childForFieldName("body");
7194
+ if (body) walkChildren(body, nsCtx, classCtx);
7195
+ };
7196
+ const visit = (node, nsCtx, classCtx) => {
7197
+ switch (node.type) {
7198
+ case "namespace_declaration": {
7199
+ const nameNode = node.childForFieldName("name");
7200
+ const ns = nameNode ? join(nsCtx, nameNode.text) : nsCtx;
7201
+ walkBody(node, ns, classCtx);
7202
+ return;
7203
+ }
7204
+ case "class_declaration":
7205
+ case "interface_declaration":
7206
+ case "struct_declaration":
7207
+ case "record_declaration": {
7208
+ const name = node.childForFieldName("name")?.text;
7209
+ const full = name ? join(classCtx ?? nsCtx, name) : classCtx;
7210
+ if (name) push("class", full, node);
7211
+ walkBody(node, nsCtx, full);
7212
+ return;
7213
+ }
7214
+ case "method_declaration": {
7215
+ const name = node.childForFieldName("name")?.text;
7216
+ if (name) push("method", join(classCtx ?? nsCtx, name), node);
7217
+ return;
7218
+ }
7219
+ case "constructor_declaration": {
7220
+ const name = node.childForFieldName("name")?.text;
7221
+ if (name) push("constructor", join(classCtx ?? nsCtx, name), node);
7222
+ return;
7223
+ }
7224
+ }
7225
+ walkChildren(node, nsCtx, classCtx);
7226
+ };
7227
+ walkChildren(root, void 0, void 0);
7228
+ return out;
7229
+ }
7230
+ function collectJavaSymbolDefs(root) {
7231
+ const out = [];
7232
+ const push = (kind, qualname, node) => {
7233
+ out.push({
7234
+ kind,
7235
+ qualname,
7236
+ startLine: node.startPosition.row + 1,
7237
+ endLine: node.endPosition.row + 1
7238
+ });
7239
+ };
7240
+ const join = (prefix, name) => prefix ? `${prefix}.${name}` : name;
7241
+ let pkg;
7242
+ for (let i = 0; i < root.namedChildCount; i++) {
7243
+ const child = root.namedChild(i);
7244
+ if (child?.type === "package_declaration") {
7245
+ pkg = child.namedChild(0)?.text;
7246
+ break;
7247
+ }
7248
+ }
7249
+ const walkChildren = (container, classCtx) => {
7250
+ for (let i = 0; i < container.namedChildCount; i++) {
7251
+ const child = container.namedChild(i);
7252
+ if (child) visit(child, classCtx);
7253
+ }
7254
+ };
7255
+ const walkBody = (node, classCtx) => {
7256
+ const body = node.childForFieldName("body");
7257
+ if (body) walkChildren(body, classCtx);
7258
+ };
7259
+ const visit = (node, classCtx) => {
7260
+ switch (node.type) {
7261
+ case "class_declaration":
7262
+ case "interface_declaration":
7263
+ case "enum_declaration":
7264
+ case "record_declaration": {
7265
+ const name = node.childForFieldName("name")?.text;
7266
+ const full = name ? join(classCtx ?? pkg, name) : classCtx;
7267
+ if (name) push("class", full, node);
7268
+ walkBody(node, full);
7269
+ return;
7270
+ }
7271
+ case "method_declaration": {
7272
+ const name = node.childForFieldName("name")?.text;
7273
+ if (name) push("method", join(classCtx ?? pkg, name), node);
7274
+ return;
7275
+ }
7276
+ case "constructor_declaration": {
7277
+ const name = node.childForFieldName("name")?.text;
7278
+ if (name) push("constructor", join(classCtx ?? pkg, name), node);
7279
+ return;
7280
+ }
7281
+ }
7282
+ walkChildren(node, classCtx);
7283
+ };
7284
+ walkChildren(root, void 0);
7285
+ return out;
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) {
6431
7358
  const out = [];
6432
7359
  const push = (kind, qualname, node) => {
6433
7360
  out.push({
@@ -6437,55 +7364,188 @@ function collectSymbolDefs(root) {
6437
7364
  endLine: node.endPosition.row + 1
6438
7365
  });
6439
7366
  };
6440
- const visit = (node, classCtx) => {
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) => {
6441
7385
  switch (node.type) {
6442
- case "function_declaration":
6443
- case "generator_function_declaration": {
7386
+ case "mod_item": {
6444
7387
  const name = node.childForFieldName("name")?.text;
6445
- if (name) push("function", name, node);
6446
- break;
7388
+ const full = name ? join(modCtx, name) : modCtx;
7389
+ const body = node.childForFieldName("body");
7390
+ if (body) walkChildren(body, full);
7391
+ return;
6447
7392
  }
6448
- case "class_declaration":
6449
- case "abstract_class_declaration":
6450
- case "class": {
7393
+ case "struct_item":
7394
+ case "enum_item": {
6451
7395
  const name = node.childForFieldName("name")?.text;
6452
- if (name) push("class", name, node);
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);
6453
7403
  const body = node.childForFieldName("body");
6454
- if (body) {
6455
- for (let i = 0; i < body.namedChildCount; i++) {
6456
- const child = body.namedChild(i);
6457
- if (child) visit(child, name ?? classCtx);
6458
- }
6459
- }
7404
+ if (body && full) walkTypeBody(body, full);
6460
7405
  return;
6461
7406
  }
6462
- case "method_definition": {
6463
- const name = methodName(node);
6464
- if (name) {
6465
- const kind = name === "constructor" ? "constructor" : "method";
6466
- push(kind, classCtx ? `${classCtx}.${name}` : name, node);
6467
- }
6468
- break;
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;
6469
7413
  }
6470
- case "variable_declarator": {
6471
- const value = node.childForFieldName("value");
6472
- if (value && (value.type === "arrow_function" || value.type === "function" || value.type === "function_expression" || value.type === "generator_function")) {
6473
- const nameNode = node.childForFieldName("name");
6474
- if (nameNode && nameNode.type === "identifier") {
6475
- push("function", nameNode.text, node);
6476
- }
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;
6477
7454
  }
6478
- break;
6479
7455
  }
7456
+ decl = next;
6480
7457
  }
6481
- for (let i = 0; i < node.namedChildCount; i++) {
6482
- const child = node.namedChild(i);
6483
- if (child) visit(child, classCtx);
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);
6484
7484
  }
6485
7485
  };
6486
- visit(root, void 0);
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);
6487
7516
  return out;
6488
7517
  }
7518
+ function collectSymbolDefsForExt(ext, root) {
7519
+ switch (ext) {
7520
+ case ".py":
7521
+ return collectPythonSymbolDefs(root);
7522
+ case ".go":
7523
+ return collectGoSymbolDefs(root);
7524
+ case ".rb":
7525
+ return collectRubySymbolDefs(root);
7526
+ case ".php":
7527
+ return collectPhpSymbolDefs(root);
7528
+ case ".cs":
7529
+ return collectCsharpSymbolDefs(root);
7530
+ case ".java":
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);
7545
+ default:
7546
+ return collectSymbolDefs(root);
7547
+ }
7548
+ }
6489
7549
  function disambiguate(defs) {
6490
7550
  const counts = /* @__PURE__ */ new Map();
6491
7551
  for (const def of defs) counts.set(def.qualname, (counts.get(def.qualname) ?? 0) + 1);
@@ -6500,7 +7560,7 @@ function disambiguate(defs) {
6500
7560
  async function addSymbols(graph, services) {
6501
7561
  const parsers = /* @__PURE__ */ new Map();
6502
7562
  const parserForExt5 = (ext) => {
6503
- const grammar = GRAMMAR_BY_EXT[ext];
7563
+ const grammar = SYMBOL_GRAMMAR_BY_EXT[ext];
6504
7564
  if (!grammar) return null;
6505
7565
  let parser = parsers.get(ext);
6506
7566
  if (!parser) {
@@ -6513,15 +7573,16 @@ async function addSymbols(graph, services) {
6513
7573
  let nodesAdded = 0;
6514
7574
  let edgesAdded = 0;
6515
7575
  for (const service of services) {
6516
- const files = await loadSourceFiles(service.dir);
7576
+ const files = await loadSourceFiles(service.dir, service.excludeDirs);
6517
7577
  for (const file of files) {
6518
- const parser = parserForExt5(path17.extname(file.path));
7578
+ const ext = path23.extname(file.path);
7579
+ const parser = parserForExt5(ext);
6519
7580
  if (!parser) continue;
6520
- const relPath = toPosix(path17.relative(service.dir, file.path));
7581
+ const relPath = toPosix(path23.relative(service.dir, file.path));
6521
7582
  let defs;
6522
7583
  try {
6523
7584
  const tree = parseSource3(parser, file.content);
6524
- defs = collectSymbolDefs(tree.rootNode);
7585
+ defs = collectSymbolDefsForExt(ext, tree.rootNode);
6525
7586
  } catch (err) {
6526
7587
  recordExtractionError("symbol extraction", file.path, err);
6527
7588
  continue;
@@ -6540,7 +7601,7 @@ async function addSymbols(graph, services) {
6540
7601
  if (!graph.hasNode(sid)) {
6541
7602
  const node = {
6542
7603
  id: sid,
6543
- type: NodeType11.SymbolNode,
7604
+ type: NodeType17.SymbolNode,
6544
7605
  kind: def.kind,
6545
7606
  qualname: def.qualname,
6546
7607
  span: { startLine: def.startLine, endLine: def.endLine },
@@ -6576,11 +7637,11 @@ async function addSymbols(graph, services) {
6576
7637
  }
6577
7638
 
6578
7639
  // src/extract/symbol-edges.ts
6579
- import path18 from "path";
7640
+ import path24 from "path";
6580
7641
  import Parser4 from "tree-sitter";
6581
7642
  import {
6582
7643
  EdgeType as EdgeType8,
6583
- NodeType as NodeType12,
7644
+ NodeType as NodeType18,
6584
7645
  Provenance as Provenance8,
6585
7646
  confidenceForExtracted as confidenceForExtracted5,
6586
7647
  extractedEdgeId as extractedEdgeId7,
@@ -6688,12 +7749,12 @@ async function addSymbolEdges(graph, services) {
6688
7749
  for (const service of services) {
6689
7750
  const serviceName = service.pkg.name;
6690
7751
  const tsPaths = await loadTsPathConfig(service.dir);
6691
- const files = await loadSourceFiles(service.dir);
7752
+ const files = await loadSourceFiles(service.dir, service.excludeDirs);
6692
7753
  for (const file of files) {
6693
- const parser = parserForExt5(path18.extname(file.path));
7754
+ const parser = parserForExt5(path24.extname(file.path));
6694
7755
  if (!parser) continue;
6695
- const relPath = toPosix(path18.relative(service.dir, file.path));
6696
- const fileDir = path18.dirname(file.path);
7756
+ const relPath = toPosix(path24.relative(service.dir, file.path));
7757
+ const fileDir = path24.dirname(file.path);
6697
7758
  let root;
6698
7759
  try {
6699
7760
  root = parseSource3(parser, file.content).rootNode;
@@ -6735,7 +7796,7 @@ async function addSymbolEdges(graph, services) {
6735
7796
  const candidate = symbolId3(serviceName, imp.targetRelPath, imp.importedName);
6736
7797
  if (graph.hasNode(candidate)) {
6737
7798
  const node = graph.getNodeAttributes(candidate);
6738
- if (node.type === NodeType12.SymbolNode && node.kind === wantKind) return candidate;
7799
+ if (node.type === NodeType18.SymbolNode && node.kind === wantKind) return candidate;
6739
7800
  }
6740
7801
  }
6741
7802
  return null;
@@ -6827,11 +7888,11 @@ async function addSymbolEdges(graph, services) {
6827
7888
  }
6828
7889
 
6829
7890
  // src/extract/actions.ts
6830
- import path19 from "path";
7891
+ import path25 from "path";
6831
7892
  import Parser5 from "tree-sitter";
6832
7893
  import {
6833
7894
  EdgeType as EdgeType9,
6834
- NodeType as NodeType13,
7895
+ NodeType as NodeType19,
6835
7896
  Provenance as Provenance9,
6836
7897
  confidenceForExtracted as confidenceForExtracted6,
6837
7898
  extractedEdgeId as extractedEdgeId8,
@@ -6978,12 +8039,12 @@ async function addServerActions(graph, services) {
6978
8039
  };
6979
8040
  if (deps["next"] === void 0) continue;
6980
8041
  const tsPaths = await loadTsPathConfig(service.dir);
6981
- const files = await loadSourceFiles(service.dir);
8042
+ const files = await loadSourceFiles(service.dir, service.excludeDirs);
6982
8043
  for (const file of files) {
6983
8044
  if (isTestPath(file.path)) continue;
6984
- const parser = parserForExt5(path19.extname(file.path));
8045
+ const parser = parserForExt5(path25.extname(file.path));
6985
8046
  if (!parser) continue;
6986
- const relPath = toPosix(path19.relative(service.dir, file.path));
8047
+ const relPath = toPosix(path25.relative(service.dir, file.path));
6987
8048
  let root;
6988
8049
  try {
6989
8050
  root = parseSource3(parser, file.content).rootNode;
@@ -7010,7 +8071,7 @@ async function addServerActions(graph, services) {
7010
8071
  if (!graph.hasNode(aid)) {
7011
8072
  const node = {
7012
8073
  id: aid,
7013
- type: NodeType13.ServerActionNode,
8074
+ type: NodeType19.ServerActionNode,
7014
8075
  name: action.exportName,
7015
8076
  service: service.pkg.name,
7016
8077
  module: relPath,
@@ -7044,10 +8105,10 @@ async function addServerActions(graph, services) {
7044
8105
  }
7045
8106
  for (const file of files) {
7046
8107
  if (isTestPath(file.path)) continue;
7047
- const parser = parserForExt5(path19.extname(file.path));
8108
+ const parser = parserForExt5(path25.extname(file.path));
7048
8109
  if (!parser) continue;
7049
- const relPath = toPosix(path19.relative(service.dir, file.path));
7050
- const fileDir = path19.dirname(file.path);
8110
+ const relPath = toPosix(path25.relative(service.dir, file.path));
8111
+ const fileDir = path25.dirname(file.path);
7051
8112
  let root;
7052
8113
  try {
7053
8114
  root = parseSource3(parser, file.content).rootNode;
@@ -7099,10 +8160,10 @@ async function addServerActions(graph, services) {
7099
8160
  }
7100
8161
 
7101
8162
  // src/extract/databases/index.ts
7102
- import path27 from "path";
8163
+ import path33 from "path";
7103
8164
  import {
7104
8165
  EdgeType as EdgeType10,
7105
- NodeType as NodeType14,
8166
+ NodeType as NodeType20,
7106
8167
  Provenance as Provenance10,
7107
8168
  configId,
7108
8169
  databaseId as databaseId2,
@@ -7110,9 +8171,9 @@ import {
7110
8171
  } from "@neat.is/types";
7111
8172
 
7112
8173
  // src/extract/databases/db-config-yaml.ts
7113
- import path20 from "path";
8174
+ import path26 from "path";
7114
8175
  async function parse(serviceDir) {
7115
- const yamlPath = path20.join(serviceDir, "db-config.yaml");
8176
+ const yamlPath = path26.join(serviceDir, "db-config.yaml");
7116
8177
  if (!await exists(yamlPath)) return [];
7117
8178
  const raw = await readYaml(yamlPath);
7118
8179
  return [
@@ -7129,12 +8190,12 @@ async function parse(serviceDir) {
7129
8190
  var dbConfigYamlParser = { name: "db-config.yaml", parse };
7130
8191
 
7131
8192
  // src/extract/databases/dotenv.ts
7132
- import { promises as fs15 } from "fs";
7133
- import path22 from "path";
8193
+ import { promises as fs21 } from "fs";
8194
+ import path28 from "path";
7134
8195
 
7135
8196
  // src/extract/databases/shared.ts
7136
- import { promises as fs14 } from "fs";
7137
- import path21 from "path";
8197
+ import { promises as fs20 } from "fs";
8198
+ import path27 from "path";
7138
8199
  function schemeToEngine(scheme) {
7139
8200
  const s = scheme.toLowerCase().split("+")[0];
7140
8201
  switch (s) {
@@ -7173,18 +8234,18 @@ function parseConnectionString(url) {
7173
8234
  }
7174
8235
  async function readIfExists(filePath) {
7175
8236
  try {
7176
- return await fs14.readFile(filePath, "utf8");
8237
+ return await fs20.readFile(filePath, "utf8");
7177
8238
  } catch {
7178
8239
  return null;
7179
8240
  }
7180
8241
  }
7181
8242
  async function resolveEnvVar(serviceDir, name) {
7182
- const entries = await fs14.readdir(serviceDir, { withFileTypes: true }).catch(() => []);
8243
+ const entries = await fs20.readdir(serviceDir, { withFileTypes: true }).catch(() => []);
7183
8244
  const envNames = entries.filter((e) => e.isFile() && (e.name === ".env" || e.name.startsWith(".env."))).map((e) => e.name);
7184
8245
  const rank = (n) => n === ".env.local" ? 0 : n === ".env" ? 1 : 2;
7185
8246
  envNames.sort((a, b) => rank(a) - rank(b) || a.localeCompare(b));
7186
8247
  for (const fileName of envNames) {
7187
- const content = await readIfExists(path21.join(serviceDir, fileName));
8248
+ const content = await readIfExists(path27.join(serviceDir, fileName));
7188
8249
  if (!content) continue;
7189
8250
  for (const line of content.split("\n")) {
7190
8251
  const trimmed = line.trim();
@@ -7203,7 +8264,7 @@ async function resolveEnvVar(serviceDir, name) {
7203
8264
  }
7204
8265
  async function findFirst(serviceDir, candidates) {
7205
8266
  for (const rel of candidates) {
7206
- const abs = path21.join(serviceDir, rel);
8267
+ const abs = path27.join(serviceDir, rel);
7207
8268
  const content = await readIfExists(abs);
7208
8269
  if (content !== null) return abs;
7209
8270
  }
@@ -7261,15 +8322,15 @@ function parseDotenvLine(line) {
7261
8322
  return { key, value };
7262
8323
  }
7263
8324
  async function parse2(serviceDir) {
7264
- const entries = await fs15.readdir(serviceDir, { withFileTypes: true }).catch(() => []);
8325
+ const entries = await fs21.readdir(serviceDir, { withFileTypes: true }).catch(() => []);
7265
8326
  const configs = [];
7266
8327
  const seen = /* @__PURE__ */ new Set();
7267
8328
  for (const entry of entries) {
7268
8329
  if (!entry.isFile()) continue;
7269
8330
  const match = isConfigFile(entry.name);
7270
8331
  if (!match.match || match.fileType !== "env") continue;
7271
- const filePath = path22.join(serviceDir, entry.name);
7272
- const content = await fs15.readFile(filePath, "utf8");
8332
+ const filePath = path28.join(serviceDir, entry.name);
8333
+ const content = await fs21.readFile(filePath, "utf8");
7273
8334
  for (const line of content.split("\n")) {
7274
8335
  const parsed = parseDotenvLine(line);
7275
8336
  if (!parsed) continue;
@@ -7287,9 +8348,9 @@ async function parse2(serviceDir) {
7287
8348
  var dotenvParser = { name: ".env", parse: parse2 };
7288
8349
 
7289
8350
  // src/extract/databases/prisma.ts
7290
- import path23 from "path";
8351
+ import path29 from "path";
7291
8352
  async function parse3(serviceDir) {
7292
- const schemaPath = path23.join(serviceDir, "prisma", "schema.prisma");
8353
+ const schemaPath = path29.join(serviceDir, "prisma", "schema.prisma");
7293
8354
  const content = await readIfExists(schemaPath);
7294
8355
  if (!content) return [];
7295
8356
  const block = content.match(/datasource\s+\w+\s*\{([^}]*)\}/s);
@@ -7444,10 +8505,10 @@ async function parse5(serviceDir) {
7444
8505
  var knexParser = { name: "knex", parse: parse5 };
7445
8506
 
7446
8507
  // src/extract/databases/ormconfig.ts
7447
- import path24 from "path";
8508
+ import path30 from "path";
7448
8509
  async function parse6(serviceDir) {
7449
8510
  for (const candidate of ["ormconfig.json", "ormconfig.yaml", "ormconfig.yml"]) {
7450
- const abs = path24.join(serviceDir, candidate);
8511
+ const abs = path30.join(serviceDir, candidate);
7451
8512
  if (!await exists(abs)) continue;
7452
8513
  const raw = candidate.endsWith(".json") ? await readJson(abs) : await readYaml(abs);
7453
8514
  const entries = Array.isArray(raw) ? raw : [raw];
@@ -7505,9 +8566,9 @@ async function parse7(serviceDir) {
7505
8566
  var typeormParser = { name: "typeorm", parse: parse7 };
7506
8567
 
7507
8568
  // src/extract/databases/sequelize.ts
7508
- import path25 from "path";
8569
+ import path31 from "path";
7509
8570
  async function parse8(serviceDir) {
7510
- const configPath = path25.join(serviceDir, "config", "config.json");
8571
+ const configPath = path31.join(serviceDir, "config", "config.json");
7511
8572
  if (!await exists(configPath)) return [];
7512
8573
  const raw = await readJson(configPath);
7513
8574
  const out = [];
@@ -7533,7 +8594,7 @@ async function parse8(serviceDir) {
7533
8594
  var sequelizeParser = { name: "sequelize", parse: parse8 };
7534
8595
 
7535
8596
  // src/extract/databases/docker-compose.ts
7536
- import path26 from "path";
8597
+ import path32 from "path";
7537
8598
  function portFromService(svc) {
7538
8599
  for (const raw of svc.ports ?? []) {
7539
8600
  const str = String(raw);
@@ -7560,7 +8621,7 @@ function databaseFromEnv(svc) {
7560
8621
  }
7561
8622
  async function parse9(serviceDir) {
7562
8623
  for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
7563
- const abs = path26.join(serviceDir, name);
8624
+ const abs = path32.join(serviceDir, name);
7564
8625
  if (!await exists(abs)) continue;
7565
8626
  const raw = await readYaml(abs);
7566
8627
  if (!raw?.services) return [];
@@ -7602,7 +8663,7 @@ function compatibleDriversFor(engine) {
7602
8663
  function toDatabaseNode(config) {
7603
8664
  return {
7604
8665
  id: databaseId2(config.host),
7605
- type: NodeType14.DatabaseNode,
8666
+ type: NodeType20.DatabaseNode,
7606
8667
  name: config.database || config.host,
7607
8668
  engine: config.engine,
7608
8669
  engineVersion: config.engineVersion,
@@ -7732,7 +8793,7 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
7732
8793
  discoveredVia: mergedDiscoveredVia
7733
8794
  });
7734
8795
  }
7735
- const relConfigFile = toPosix(path27.relative(service.dir, config.sourceFile));
8796
+ const relConfigFile = toPosix(path33.relative(service.dir, config.sourceFile));
7736
8797
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
7737
8798
  graph,
7738
8799
  service.pkg.name,
@@ -7741,7 +8802,7 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
7741
8802
  );
7742
8803
  nodesAdded += fn;
7743
8804
  edgesAdded += fe;
7744
- const evidenceFile = toPosix(path27.relative(scanPath, config.sourceFile));
8805
+ const evidenceFile = toPosix(path33.relative(scanPath, config.sourceFile));
7745
8806
  const edge = {
7746
8807
  id: extractedEdgeId(fileNodeId, dbNode.id, EdgeType10.CONNECTS_TO),
7747
8808
  source: fileNodeId,
@@ -7759,15 +8820,15 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
7759
8820
  if (allConfigs.length === 1) {
7760
8821
  const primary = allConfigs[0];
7761
8822
  service.node.dbConnectionTarget = primary.port ? `${primary.host}:${primary.port}` : primary.host;
7762
- const relPath = path27.relative(scanPath, primary.sourceFile);
8823
+ const relPath = path33.relative(scanPath, primary.sourceFile);
7763
8824
  const cfgId = configId(relPath);
7764
8825
  if (!graph.hasNode(cfgId)) {
7765
8826
  const cfgNode = {
7766
8827
  id: cfgId,
7767
- type: NodeType14.ConfigNode,
7768
- name: path27.basename(primary.sourceFile),
8828
+ type: NodeType20.ConfigNode,
8829
+ name: path33.basename(primary.sourceFile),
7769
8830
  path: relPath,
7770
- fileType: isConfigFile(path27.basename(primary.sourceFile)).fileType || "config"
8831
+ fileType: isConfigFile(path33.basename(primary.sourceFile)).fileType || "config"
7771
8832
  };
7772
8833
  graph.addNode(cfgId, cfgNode);
7773
8834
  nodesAdded++;
@@ -7807,23 +8868,25 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
7807
8868
  }
7808
8869
 
7809
8870
  // src/extract/configs.ts
7810
- import { promises as fs16 } from "fs";
7811
- import path28 from "path";
8871
+ import { promises as fs22 } from "fs";
8872
+ import path34 from "path";
7812
8873
  import {
7813
8874
  EdgeType as EdgeType11,
7814
- NodeType as NodeType15,
8875
+ NodeType as NodeType21,
7815
8876
  Provenance as Provenance11,
7816
8877
  configId as configId2,
7817
8878
  confidenceForExtracted as confidenceForExtracted8
7818
8879
  } from "@neat.is/types";
7819
- async function walkConfigFiles(dir) {
8880
+ async function walkConfigFiles(dir, excludeDirs = []) {
8881
+ const excluded = new Set(excludeDirs.map((d) => path34.resolve(d)));
7820
8882
  const out = [];
7821
8883
  async function walk9(current) {
7822
- const entries = await fs16.readdir(current, { withFileTypes: true });
8884
+ const entries = await fs22.readdir(current, { withFileTypes: true });
7823
8885
  for (const entry of entries) {
7824
- const full = path28.join(current, entry.name);
8886
+ const full = path34.join(current, entry.name);
7825
8887
  if (entry.isDirectory()) {
7826
8888
  if (IGNORED_DIRS.has(entry.name)) continue;
8889
+ if (excluded.has(path34.resolve(full))) continue;
7827
8890
  if (await isPythonVenvDir(full)) continue;
7828
8891
  await walk9(full);
7829
8892
  } else if (entry.isFile() && isConfigFile(entry.name).match) {
@@ -7838,21 +8901,21 @@ async function addConfigNodes(graph, services, scanPath) {
7838
8901
  let nodesAdded = 0;
7839
8902
  let edgesAdded = 0;
7840
8903
  for (const service of services) {
7841
- const configFiles = await walkConfigFiles(service.dir);
8904
+ const configFiles = await walkConfigFiles(service.dir, service.excludeDirs);
7842
8905
  for (const file of configFiles) {
7843
- const relPath = path28.relative(scanPath, file);
8906
+ const relPath = path34.relative(scanPath, file);
7844
8907
  const node = {
7845
8908
  id: configId2(relPath),
7846
- type: NodeType15.ConfigNode,
7847
- name: path28.basename(file),
8909
+ type: NodeType21.ConfigNode,
8910
+ name: path34.basename(file),
7848
8911
  path: relPath,
7849
- fileType: isConfigFile(path28.basename(file)).fileType
8912
+ fileType: isConfigFile(path34.basename(file)).fileType
7850
8913
  };
7851
8914
  if (!graph.hasNode(node.id)) {
7852
8915
  graph.addNode(node.id, node);
7853
8916
  nodesAdded++;
7854
8917
  }
7855
- const relToService = toPosix(path28.relative(service.dir, file));
8918
+ const relToService = toPosix(path34.relative(service.dir, file));
7856
8919
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
7857
8920
  graph,
7858
8921
  service.pkg.name,
@@ -7868,7 +8931,7 @@ async function addConfigNodes(graph, services, scanPath) {
7868
8931
  type: EdgeType11.CONFIGURED_BY,
7869
8932
  provenance: Provenance11.EXTRACTED,
7870
8933
  confidence: confidenceForExtracted8("structural"),
7871
- evidence: { file: relPath.split(path28.sep).join("/") }
8934
+ evidence: { file: relPath.split(path34.sep).join("/") }
7872
8935
  };
7873
8936
  if (!graph.hasEdge(edge.id)) {
7874
8937
  graph.addEdgeWithKey(edge.id, edge.source, edge.target, edge);
@@ -7880,11 +8943,11 @@ async function addConfigNodes(graph, services, scanPath) {
7880
8943
  }
7881
8944
 
7882
8945
  // src/extract/proto.ts
7883
- import { promises as fs17 } from "fs";
7884
- import path29 from "path";
8946
+ import { promises as fs23 } from "fs";
8947
+ import path35 from "path";
7885
8948
  import {
7886
8949
  EdgeType as EdgeType12,
7887
- NodeType as NodeType16,
8950
+ NodeType as NodeType22,
7888
8951
  Provenance as Provenance12,
7889
8952
  confidenceForExtracted as confidenceForExtracted9,
7890
8953
  extractedEdgeId as extractedEdgeId9,
@@ -7925,17 +8988,19 @@ function grpcMethodsFromProto(content, fqPackage) {
7925
8988
  }
7926
8989
  return out;
7927
8990
  }
7928
- async function walkProtoFiles(dir) {
8991
+ async function walkProtoFiles(dir, excludeDirs = []) {
8992
+ const excluded = new Set(excludeDirs.map((d) => path35.resolve(d)));
7929
8993
  const out = [];
7930
8994
  async function walk9(current) {
7931
- const entries = await fs17.readdir(current, { withFileTypes: true }).catch(() => []);
8995
+ const entries = await fs23.readdir(current, { withFileTypes: true }).catch(() => []);
7932
8996
  for (const entry of entries) {
7933
- const full = path29.join(current, entry.name);
8997
+ const full = path35.join(current, entry.name);
7934
8998
  if (entry.isDirectory()) {
7935
8999
  if (IGNORED_DIRS.has(entry.name)) continue;
9000
+ if (excluded.has(path35.resolve(full))) continue;
7936
9001
  if (await isPythonVenvDir(full)) continue;
7937
9002
  await walk9(full);
7938
- } else if (entry.isFile() && path29.extname(entry.name) === PROTO_EXTENSION) {
9003
+ } else if (entry.isFile() && path35.extname(entry.name) === PROTO_EXTENSION) {
7939
9004
  out.push(full);
7940
9005
  }
7941
9006
  }
@@ -7947,13 +9012,13 @@ async function addGrpcMethods(graph, services) {
7947
9012
  let nodesAdded = 0;
7948
9013
  let edgesAdded = 0;
7949
9014
  for (const service of services) {
7950
- const protoPaths = await walkProtoFiles(service.dir);
9015
+ const protoPaths = await walkProtoFiles(service.dir, service.excludeDirs);
7951
9016
  for (const protoPath of protoPaths) {
7952
9017
  if (isTestPath(protoPath)) continue;
7953
- const relFile = toPosix(path29.relative(service.dir, protoPath));
9018
+ const relFile = toPosix(path35.relative(service.dir, protoPath));
7954
9019
  let content;
7955
9020
  try {
7956
- content = await fs17.readFile(protoPath, "utf8");
9021
+ content = await fs23.readFile(protoPath, "utf8");
7957
9022
  } catch (err) {
7958
9023
  recordExtractionError("proto extraction", protoPath, err);
7959
9024
  continue;
@@ -7971,7 +9036,7 @@ async function addGrpcMethods(graph, services) {
7971
9036
  if (!graph.hasNode(mid)) {
7972
9037
  const node = {
7973
9038
  id: mid,
7974
- type: NodeType16.GrpcMethodNode,
9039
+ type: NodeType22.GrpcMethodNode,
7975
9040
  name: `${method.rpcService}/${method.rpcMethod}`,
7976
9041
  rpcService: method.rpcService,
7977
9042
  rpcMethod: method.rpcMethod,
@@ -8009,18 +9074,18 @@ async function addGrpcMethods(graph, services) {
8009
9074
  // src/extract/calls/index.ts
8010
9075
  import {
8011
9076
  EdgeType as EdgeType15,
8012
- NodeType as NodeType18,
9077
+ NodeType as NodeType24,
8013
9078
  Provenance as Provenance15,
8014
9079
  confidenceForExtracted as confidenceForExtracted12,
8015
9080
  passesExtractedFloor as passesExtractedFloor3
8016
9081
  } from "@neat.is/types";
8017
9082
 
8018
9083
  // src/extract/calls/http.ts
8019
- import path30 from "path";
9084
+ import path36 from "path";
8020
9085
  import Parser6 from "tree-sitter";
8021
9086
  import JavaScript4 from "tree-sitter-javascript";
8022
9087
  import TypeScript2 from "tree-sitter-typescript";
8023
- import Python3 from "tree-sitter-python";
9088
+ import Python4 from "tree-sitter-python";
8024
9089
  import {
8025
9090
  EdgeType as EdgeType13,
8026
9091
  Provenance as Provenance13,
@@ -8082,7 +9147,7 @@ var GRAMMAR_BY_EXT2 = {
8082
9147
  ".jsx": JavaScript4,
8083
9148
  ".mjs": JavaScript4,
8084
9149
  ".cjs": JavaScript4,
8085
- ".py": Python3
9150
+ ".py": Python4
8086
9151
  };
8087
9152
  function parserForExt(ext, cache) {
8088
9153
  const grammar = GRAMMAR_BY_EXT2[ext] ?? JavaScript4;
@@ -8100,11 +9165,11 @@ async function addHttpCallEdges(graph, services) {
8100
9165
  let nodesAdded = 0;
8101
9166
  let edgesAdded = 0;
8102
9167
  for (const service of services) {
8103
- const files = await loadSourceFiles(service.dir);
9168
+ const files = await loadSourceFiles(service.dir, service.excludeDirs);
8104
9169
  const seen = /* @__PURE__ */ new Set();
8105
9170
  for (const file of files) {
8106
9171
  if (isTestPath(file.path)) continue;
8107
- const parser = parserForExt(path30.extname(file.path), parserCache);
9172
+ const parser = parserForExt(path36.extname(file.path), parserCache);
8108
9173
  let sites;
8109
9174
  try {
8110
9175
  sites = callsFromSource(file.content, parser, knownHosts);
@@ -8113,7 +9178,7 @@ async function addHttpCallEdges(graph, services) {
8113
9178
  continue;
8114
9179
  }
8115
9180
  if (sites.length === 0) continue;
8116
- const relFile = toPosix(path30.relative(service.dir, file.path));
9181
+ const relFile = toPosix(path36.relative(service.dir, file.path));
8117
9182
  for (const site of sites) {
8118
9183
  const targetId = hostToNodeId.get(site.host);
8119
9184
  if (!targetId || targetId === service.node.id) continue;
@@ -8166,16 +9231,16 @@ async function addHttpCallEdges(graph, services) {
8166
9231
  }
8167
9232
 
8168
9233
  // src/extract/calls/route-match.ts
8169
- import path31 from "path";
9234
+ import path37 from "path";
8170
9235
  import Parser7 from "tree-sitter";
8171
9236
  import JavaScript5 from "tree-sitter-javascript";
8172
9237
  import {
8173
9238
  EdgeType as EdgeType14,
8174
- NodeType as NodeType17,
9239
+ NodeType as NodeType23,
8175
9240
  Provenance as Provenance14,
8176
9241
  confidenceForExtracted as confidenceForExtracted11,
8177
9242
  passesExtractedFloor as passesExtractedFloor2,
8178
- serviceId as serviceId6
9243
+ serviceId as serviceId12
8179
9244
  } from "@neat.is/types";
8180
9245
  var PARSE_CHUNK5 = 16384;
8181
9246
  function parseSource5(parser, source) {
@@ -8341,9 +9406,9 @@ function buildRouteIndex(graph) {
8341
9406
  const index = /* @__PURE__ */ new Map();
8342
9407
  graph.forEachNode((_id, attrs) => {
8343
9408
  const node = attrs;
8344
- if (node.type !== NodeType17.RouteNode) return;
9409
+ if (node.type !== NodeType23.RouteNode) return;
8345
9410
  const route = attrs;
8346
- const owner = serviceId6(route.service);
9411
+ const owner = serviceId12(route.service);
8347
9412
  const entry = {
8348
9413
  method: route.method.toUpperCase(),
8349
9414
  normalizedPath: normalizePathTemplate(route.pathTemplate),
@@ -8368,11 +9433,11 @@ async function addRouteCallEdges(graph, services) {
8368
9433
  let nodesAdded = 0;
8369
9434
  let edgesAdded = 0;
8370
9435
  for (const service of services) {
8371
- const files = await loadSourceFiles(service.dir);
9436
+ const files = await loadSourceFiles(service.dir, service.excludeDirs);
8372
9437
  const seen = /* @__PURE__ */ new Set();
8373
9438
  for (const file of files) {
8374
9439
  if (isTestPath(file.path)) continue;
8375
- if (!JS_CLIENT_EXTENSIONS.has(path31.extname(file.path))) continue;
9440
+ if (!JS_CLIENT_EXTENSIONS.has(path37.extname(file.path))) continue;
8376
9441
  let sites;
8377
9442
  try {
8378
9443
  sites = clientCallSitesFromSource(file.content, jsParser, knownHosts);
@@ -8381,7 +9446,7 @@ async function addRouteCallEdges(graph, services) {
8381
9446
  continue;
8382
9447
  }
8383
9448
  if (sites.length === 0) continue;
8384
- const relFile = toPosix(path31.relative(service.dir, file.path));
9449
+ const relFile = toPosix(path37.relative(service.dir, file.path));
8385
9450
  for (const site of sites) {
8386
9451
  const serverServiceId = hostToNodeId.get(site.host);
8387
9452
  if (!serverServiceId || serverServiceId === service.node.id) continue;
@@ -8441,7 +9506,7 @@ async function addRouteCallEdges(graph, services) {
8441
9506
  }
8442
9507
 
8443
9508
  // src/extract/calls/kafka.ts
8444
- import path32 from "path";
9509
+ import path38 from "path";
8445
9510
  import { infraId as infraId2 } from "@neat.is/types";
8446
9511
  var PRODUCER_TOPIC_RE = /(?:producer|kafkaProducer)[\s\S]{0,40}?\.send\s*\(\s*\{[\s\S]{0,200}?topic\s*:\s*['"`]([^'"`]+)['"`]/g;
8447
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;
@@ -8472,7 +9537,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
8472
9537
  // tier (ADR-066).
8473
9538
  confidenceKind: "verified-call-site",
8474
9539
  evidence: {
8475
- file: path32.relative(serviceDir, file.path),
9540
+ file: path38.relative(serviceDir, file.path),
8476
9541
  line,
8477
9542
  snippet: snippet(file.content, line)
8478
9543
  }
@@ -8484,7 +9549,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
8484
9549
  }
8485
9550
 
8486
9551
  // src/extract/calls/redis.ts
8487
- import path33 from "path";
9552
+ import path39 from "path";
8488
9553
  import { infraId as infraId3 } from "@neat.is/types";
8489
9554
  var REDIS_URL_RE = /redis(?:s)?:\/\/(?:[^@'"`\s]+@)?([^:/'"`\s]+)(?::(\d+))?/g;
8490
9555
  function redisEndpointsFromFile(file, serviceDir) {
@@ -8507,7 +9572,7 @@ function redisEndpointsFromFile(file, serviceDir) {
8507
9572
  // support tier (ADR-066).
8508
9573
  confidenceKind: "url-with-structural-support",
8509
9574
  evidence: {
8510
- file: path33.relative(serviceDir, file.path),
9575
+ file: path39.relative(serviceDir, file.path),
8511
9576
  line,
8512
9577
  snippet: snippet(file.content, line)
8513
9578
  }
@@ -8517,7 +9582,7 @@ function redisEndpointsFromFile(file, serviceDir) {
8517
9582
  }
8518
9583
 
8519
9584
  // src/extract/calls/aws.ts
8520
- import path34 from "path";
9585
+ import path40 from "path";
8521
9586
  import { infraId as infraId4 } from "@neat.is/types";
8522
9587
  var S3_BUCKET_RE = /Bucket\s*:\s*['"`]([^'"`]+)['"`]/g;
8523
9588
  var DYNAMO_TABLE_RE = /TableName\s*:\s*['"`]([^'"`]+)['"`]/g;
@@ -8551,7 +9616,7 @@ function awsEndpointsFromFile(file, serviceDir) {
8551
9616
  // (ADR-066).
8552
9617
  confidenceKind: "verified-call-site",
8553
9618
  evidence: {
8554
- file: path34.relative(serviceDir, file.path),
9619
+ file: path40.relative(serviceDir, file.path),
8555
9620
  line,
8556
9621
  snippet: snippet(file.content, line)
8557
9622
  }
@@ -8575,7 +9640,7 @@ function awsEndpointsFromFile(file, serviceDir) {
8575
9640
  }
8576
9641
 
8577
9642
  // src/extract/calls/grpc.ts
8578
- import path35 from "path";
9643
+ import path41 from "path";
8579
9644
  import { infraId as infraId5 } from "@neat.is/types";
8580
9645
  var GRPC_CLIENT_RE = /new\s+([A-Z][A-Za-z0-9_]*)Client\s*\(\s*['"`]?([^,'"`)]+)?/g;
8581
9646
  var AWS_SDK_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@aws-sdk\/client-([a-z0-9-]+)['"`]/g;
@@ -8634,7 +9699,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
8634
9699
  // tier (ADR-066).
8635
9700
  confidenceKind: "verified-call-site",
8636
9701
  evidence: {
8637
- file: path35.relative(serviceDir, file.path),
9702
+ file: path41.relative(serviceDir, file.path),
8638
9703
  line,
8639
9704
  snippet: snippet(file.content, line)
8640
9705
  }
@@ -8644,7 +9709,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
8644
9709
  }
8645
9710
 
8646
9711
  // src/extract/calls/supabase.ts
8647
- import path36 from "path";
9712
+ import path42 from "path";
8648
9713
  import { infraId as infraId6 } from "@neat.is/types";
8649
9714
  var SUPABASE_JS_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@supabase\/supabase-js['"`]/;
8650
9715
  var SUPABASE_SSR_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@supabase\/ssr['"`]/;
@@ -8703,7 +9768,7 @@ function supabaseEndpointsFromFile(file, serviceDir) {
8703
9768
  // tier (ADR-066), the same grade aws.ts / grpc.ts emit at.
8704
9769
  confidenceKind: "verified-call-site",
8705
9770
  evidence: {
8706
- file: path36.relative(serviceDir, file.path),
9771
+ file: path42.relative(serviceDir, file.path),
8707
9772
  line,
8708
9773
  snippet: snippet(file.content, line)
8709
9774
  }
@@ -8730,7 +9795,7 @@ function supabaseEndpointsFromFile(file, serviceDir) {
8730
9795
  edgeType: "CALLS",
8731
9796
  confidenceKind: "verified-call-site",
8732
9797
  evidence: {
8733
- file: path36.relative(serviceDir, file.path),
9798
+ file: path42.relative(serviceDir, file.path),
8734
9799
  line,
8735
9800
  snippet: snippet(file.content, line)
8736
9801
  }
@@ -8741,7 +9806,7 @@ function supabaseEndpointsFromFile(file, serviceDir) {
8741
9806
  }
8742
9807
 
8743
9808
  // src/extract/calls/firestore.ts
8744
- import path37 from "path";
9809
+ import path43 from "path";
8745
9810
  import Parser8 from "tree-sitter";
8746
9811
  import JavaScript6 from "tree-sitter-javascript";
8747
9812
  import { infraId as infraId7 } from "@neat.is/types";
@@ -8912,7 +9977,7 @@ function firestoreEndpointsFromFile(file, serviceDir) {
8912
9977
  const hasAdmin = FIRESTORE_ADMIN_IMPORT_RE.test(file.content);
8913
9978
  if (!hasClient && !hasAdmin) return [];
8914
9979
  const fileSdk = hasClient && !hasAdmin ? "client" : hasAdmin && !hasClient ? "admin" : null;
8915
- const tree = parseSource3(parserForExt2(path37.extname(file.path)), file.content);
9980
+ const tree = parseSource3(parserForExt2(path43.extname(file.path)), file.content);
8916
9981
  const clientVars = firestoreClientVars(tree.rootNode);
8917
9982
  const collLine = /* @__PURE__ */ new Map();
8918
9983
  const writes = /* @__PURE__ */ new Map();
@@ -9016,7 +10081,7 @@ function firestoreEndpointsFromFile(file, serviceDir) {
9016
10081
  ...columnSet.size > 0 ? { columns: [...columnSet] } : {},
9017
10082
  ...sdkWrites ? { sdkWrites } : {},
9018
10083
  evidence: {
9019
- file: path37.relative(serviceDir, file.path),
10084
+ file: path43.relative(serviceDir, file.path),
9020
10085
  line,
9021
10086
  snippet: snippet(file.content, line)
9022
10087
  }
@@ -9026,7 +10091,7 @@ function firestoreEndpointsFromFile(file, serviceDir) {
9026
10091
  }
9027
10092
 
9028
10093
  // src/extract/calls/mongoose.ts
9029
- import path38 from "path";
10094
+ import path44 from "path";
9030
10095
  import { infraId as infraId8 } from "@neat.is/types";
9031
10096
  var MONGOOSE_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])mongoose['"`]/;
9032
10097
  var MONGODB_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])mongodb['"`]/;
@@ -9177,7 +10242,7 @@ function endpoint(r, file, serviceDir, matchText) {
9177
10242
  kind: r.kind,
9178
10243
  edgeType: "CALLS",
9179
10244
  confidenceKind: "verified-call-site",
9180
- evidence: { file: path38.relative(serviceDir, file.path), line, snippet: snippet(file.content, line) }
10245
+ evidence: { file: path44.relative(serviceDir, file.path), line, snippet: snippet(file.content, line) }
9181
10246
  };
9182
10247
  }
9183
10248
  function mongooseEndpointsFromFile(file, serviceDir) {
@@ -9267,7 +10332,7 @@ async function mongooseCrossFileEndpoints(files, serviceDir) {
9267
10332
  const registry = /* @__PURE__ */ new Map();
9268
10333
  for (const f of mongooseFiles) {
9269
10334
  const fx = fileExportsOf(f.content, pluralizeOn);
9270
- if (fx) registry.set(toPosix(path38.relative(serviceDir, f.path)), fx);
10335
+ if (fx) registry.set(toPosix(path44.relative(serviceDir, f.path)), fx);
9271
10336
  }
9272
10337
  if (registry.size === 0) return [];
9273
10338
  const out = [];
@@ -9278,7 +10343,7 @@ async function mongooseCrossFileEndpoints(files, serviceDir) {
9278
10343
  const directColl = /* @__PURE__ */ new Map();
9279
10344
  const nsExports = /* @__PURE__ */ new Map();
9280
10345
  for (const b of bindings) {
9281
- const resolvedRel = await resolveJsImport(b.specifier, path38.dirname(f.path), serviceDir, null);
10346
+ const resolvedRel = await resolveJsImport(b.specifier, path44.dirname(f.path), serviceDir, null);
9282
10347
  if (!resolvedRel) continue;
9283
10348
  const fx = registry.get(resolvedRel);
9284
10349
  if (!fx) continue;
@@ -9321,15 +10386,15 @@ async function mongooseCrossFileEndpoints(files, serviceDir) {
9321
10386
  }
9322
10387
 
9323
10388
  // src/extract/calls/sqlalchemy.ts
9324
- import path39 from "path";
10389
+ import path45 from "path";
9325
10390
  import Parser9 from "tree-sitter";
9326
- import Python4 from "tree-sitter-python";
10391
+ import Python5 from "tree-sitter-python";
9327
10392
  import { infraId as infraId9 } from "@neat.is/types";
9328
10393
  var SQLALCHEMY_IMPORT_RE = /(?:from|import)\s+(?:flask_sqlalchemy|sqlalchemy)\b/;
9329
10394
  var PARSE_CHUNK6 = 16384;
9330
10395
  function makePyParser3() {
9331
10396
  const p = new Parser9();
9332
- p.setLanguage(Python4);
10397
+ p.setLanguage(Python5);
9333
10398
  return p;
9334
10399
  }
9335
10400
  function parseSource6(parser, source) {
@@ -9460,7 +10525,7 @@ function sqlalchemyForeignKeys(file, serviceDir) {
9460
10525
  childTable,
9461
10526
  parentTable,
9462
10527
  evidence: {
9463
- file: path39.relative(serviceDir, file.path),
10528
+ file: path45.relative(serviceDir, file.path),
9464
10529
  line,
9465
10530
  snippet: snippet(file.content, line)
9466
10531
  }
@@ -9485,7 +10550,7 @@ function sqlalchemyEndpointsFromFile(file, serviceDir) {
9485
10550
  confidenceKind: "verified-call-site",
9486
10551
  ...columns && columns.length > 0 ? { columns } : {},
9487
10552
  evidence: {
9488
- file: path39.relative(serviceDir, file.path),
10553
+ file: path45.relative(serviceDir, file.path),
9489
10554
  line,
9490
10555
  snippet: snippet(file.content, line)
9491
10556
  }
@@ -9592,7 +10657,7 @@ function pythonOrmCrossFileEndpoints(files, serviceDir) {
9592
10657
  edgeType: "CALLS",
9593
10658
  confidenceKind: "verified-call-site",
9594
10659
  evidence: {
9595
- file: path39.relative(serviceDir, file.path),
10660
+ file: path45.relative(serviceDir, file.path),
9596
10661
  line,
9597
10662
  snippet: snippet(file.content, line)
9598
10663
  }
@@ -9603,15 +10668,15 @@ function pythonOrmCrossFileEndpoints(files, serviceDir) {
9603
10668
  }
9604
10669
 
9605
10670
  // src/extract/calls/django-orm.ts
9606
- import path40 from "path";
10671
+ import path46 from "path";
9607
10672
  import Parser10 from "tree-sitter";
9608
- import Python5 from "tree-sitter-python";
10673
+ import Python6 from "tree-sitter-python";
9609
10674
  import { infraId as infraId10 } from "@neat.is/types";
9610
10675
  var DJANGO_IMPORT_RE = /(?:from|import)\s+django\b/;
9611
10676
  var PARSE_CHUNK7 = 16384;
9612
10677
  function makePyParser4() {
9613
10678
  const p = new Parser10();
9614
- p.setLanguage(Python5);
10679
+ p.setLanguage(Python6);
9615
10680
  return p;
9616
10681
  }
9617
10682
  function parseSource7(parser, source) {
@@ -9673,7 +10738,7 @@ function djangoOrmEndpointsFromFile(file, serviceDir) {
9673
10738
  const tree = parseSource7(makePyParser4(), file.content);
9674
10739
  const out = [];
9675
10740
  const seen = /* @__PURE__ */ new Set();
9676
- const defaultAppLabel = path40.basename(path40.dirname(file.path));
10741
+ const defaultAppLabel = path46.basename(path46.dirname(file.path));
9677
10742
  walk4(tree.rootNode, (node) => {
9678
10743
  if (node.type !== "class_definition") return;
9679
10744
  if (!extendsDjangoModel(node)) return;
@@ -9691,14 +10756,14 @@ function djangoOrmEndpointsFromFile(file, serviceDir) {
9691
10756
  kind: "sql-table",
9692
10757
  edgeType: "CALLS",
9693
10758
  confidenceKind: "verified-call-site",
9694
- evidence: { file: path40.relative(serviceDir, file.path), line, snippet: snippet(file.content, line) }
10759
+ evidence: { file: path46.relative(serviceDir, file.path), line, snippet: snippet(file.content, line) }
9695
10760
  });
9696
10761
  });
9697
10762
  return out;
9698
10763
  }
9699
10764
 
9700
10765
  // src/extract/calls/drizzle.ts
9701
- import path41 from "path";
10766
+ import path47 from "path";
9702
10767
  import Parser11 from "tree-sitter";
9703
10768
  import JavaScript7 from "tree-sitter-javascript";
9704
10769
  import { infraId as infraId11 } from "@neat.is/types";
@@ -9776,7 +10841,7 @@ function columnsFromObject(obj) {
9776
10841
  }
9777
10842
  function drizzleEndpointsFromFile(file, serviceDir) {
9778
10843
  if (!DRIZZLE_IMPORT_RE.test(file.content)) return [];
9779
- const tree = parseSource3(parserForExt3(path41.extname(file.path)), file.content);
10844
+ const tree = parseSource3(parserForExt3(path47.extname(file.path)), file.content);
9780
10845
  const out = [];
9781
10846
  const seen = /* @__PURE__ */ new Set();
9782
10847
  const walk9 = (node) => {
@@ -9799,7 +10864,7 @@ function drizzleEndpointsFromFile(file, serviceDir) {
9799
10864
  confidenceKind: "structural",
9800
10865
  columns,
9801
10866
  evidence: {
9802
- file: path41.relative(serviceDir, file.path),
10867
+ file: path47.relative(serviceDir, file.path),
9803
10868
  line,
9804
10869
  snippet: snippet(file.content, line)
9805
10870
  }
@@ -9865,7 +10930,7 @@ function referencesTargetVar(call) {
9865
10930
  }
9866
10931
  function drizzleForeignKeys(file, serviceDir) {
9867
10932
  if (!DRIZZLE_IMPORT_RE.test(file.content)) return [];
9868
- const tree = parseSource3(parserForExt3(path41.extname(file.path)), file.content);
10933
+ const tree = parseSource3(parserForExt3(path47.extname(file.path)), file.content);
9869
10934
  const { tables, varToTable } = collectDrizzleTables(tree.rootNode);
9870
10935
  const out = [];
9871
10936
  const seen = /* @__PURE__ */ new Set();
@@ -9884,7 +10949,7 @@ function drizzleForeignKeys(file, serviceDir) {
9884
10949
  childTable: table.tableName,
9885
10950
  parentTable,
9886
10951
  evidence: {
9887
- file: path41.relative(serviceDir, file.path),
10952
+ file: path47.relative(serviceDir, file.path),
9888
10953
  line,
9889
10954
  snippet: snippet(file.content, line)
9890
10955
  }
@@ -9900,7 +10965,7 @@ function drizzleForeignKeys(file, serviceDir) {
9900
10965
  }
9901
10966
 
9902
10967
  // src/extract/calls/prisma.ts
9903
- import path42 from "path";
10968
+ import path48 from "path";
9904
10969
  import { infraId as infraId12 } from "@neat.is/types";
9905
10970
  var SCALAR_TYPES = /* @__PURE__ */ new Set([
9906
10971
  "Int",
@@ -9956,7 +11021,7 @@ function prismaColumnsFromSchema(file, serviceDir) {
9956
11021
  confidenceKind: "structural",
9957
11022
  columns: b.columns,
9958
11023
  evidence: {
9959
- file: path42.relative(serviceDir, file.path),
11024
+ file: path48.relative(serviceDir, file.path),
9960
11025
  line: b.startLine,
9961
11026
  snippet: snippet(content, b.startLine)
9962
11027
  }
@@ -10017,7 +11082,7 @@ function prismaColumnsFromSchema(file, serviceDir) {
10017
11082
  }
10018
11083
  async function prismaColumnEndpoints(serviceDir) {
10019
11084
  const schemaPath = await findFirst(serviceDir, [
10020
- path42.join("prisma", "schema.prisma"),
11085
+ path48.join("prisma", "schema.prisma"),
10021
11086
  "schema.prisma"
10022
11087
  ]);
10023
11088
  if (!schemaPath) return [];
@@ -10092,7 +11157,7 @@ function prismaForeignKeysFromSchema(file, serviceDir) {
10092
11157
  childTable: current.table,
10093
11158
  parentTable,
10094
11159
  evidence: {
10095
- file: path42.relative(serviceDir, file.path),
11160
+ file: path48.relative(serviceDir, file.path),
10096
11161
  line: lineNo,
10097
11162
  snippet: snippet(content, lineNo)
10098
11163
  }
@@ -10107,7 +11172,7 @@ function prismaForeignKeysFromSchema(file, serviceDir) {
10107
11172
  }
10108
11173
  async function prismaForeignKeys(serviceDir) {
10109
11174
  const schemaPath = await findFirst(serviceDir, [
10110
- path42.join("prisma", "schema.prisma"),
11175
+ path48.join("prisma", "schema.prisma"),
10111
11176
  "schema.prisma"
10112
11177
  ]);
10113
11178
  if (!schemaPath) return [];
@@ -10117,15 +11182,15 @@ async function prismaForeignKeys(serviceDir) {
10117
11182
  }
10118
11183
 
10119
11184
  // src/extract/calls/activerecord.ts
10120
- import path43 from "path";
11185
+ import path49 from "path";
10121
11186
  import Parser12 from "tree-sitter";
10122
- import Ruby2 from "tree-sitter-ruby";
11187
+ import Ruby3 from "tree-sitter-ruby";
10123
11188
  import { infraId as infraId13 } from "@neat.is/types";
10124
11189
  var AR_SCHEMA_RE = /ActiveRecord::Schema/;
10125
11190
  var PARSE_CHUNK8 = 16384;
10126
11191
  function makeRubyParser2() {
10127
11192
  const p = new Parser12();
10128
- p.setLanguage(Ruby2);
11193
+ p.setLanguage(Ruby3);
10129
11194
  return p;
10130
11195
  }
10131
11196
  function parseSource8(parser, source) {
@@ -10325,7 +11390,7 @@ function railsSchemaEndpointsFromFile(file, serviceDir) {
10325
11390
  confidenceKind: "structural",
10326
11391
  ...table.columns.length > 0 ? { columns: table.columns } : {},
10327
11392
  evidence: {
10328
- file: path43.relative(serviceDir, file.path),
11393
+ file: path49.relative(serviceDir, file.path),
10329
11394
  line: table.line,
10330
11395
  snippet: snippet(file.content, table.line)
10331
11396
  }
@@ -10347,7 +11412,7 @@ function railsSchemaForeignKeys(file, serviceDir) {
10347
11412
  childTable,
10348
11413
  parentTable,
10349
11414
  evidence: {
10350
- file: path43.relative(serviceDir, file.path),
11415
+ file: path49.relative(serviceDir, file.path),
10351
11416
  line,
10352
11417
  snippet: snippet(file.content, line)
10353
11418
  }
@@ -10430,7 +11495,7 @@ function railsModelEndpointsFromFile(file, serviceDir) {
10430
11495
  edgeType: "CALLS",
10431
11496
  confidenceKind: "verified-call-site",
10432
11497
  evidence: {
10433
- file: path43.relative(serviceDir, file.path),
11498
+ file: path49.relative(serviceDir, file.path),
10434
11499
  line,
10435
11500
  snippet: snippet(file.content, line)
10436
11501
  }
@@ -10467,7 +11532,7 @@ function railsModelForeignKeys(file, serviceDir) {
10467
11532
  childTable,
10468
11533
  parentTable,
10469
11534
  evidence: {
10470
- file: path43.relative(serviceDir, file.path),
11535
+ file: path49.relative(serviceDir, file.path),
10471
11536
  line,
10472
11537
  snippet: snippet(file.content, line)
10473
11538
  }
@@ -10478,15 +11543,15 @@ function railsModelForeignKeys(file, serviceDir) {
10478
11543
  }
10479
11544
 
10480
11545
  // src/extract/calls/eloquent.ts
10481
- import path44 from "path";
11546
+ import path50 from "path";
10482
11547
  import Parser13 from "tree-sitter";
10483
- import Php2 from "tree-sitter-php";
11548
+ import Php3 from "tree-sitter-php";
10484
11549
  import { infraId as infraId14 } from "@neat.is/types";
10485
11550
  var LARAVEL_SCHEMA_RE = /\bSchema::(create|createIfNotExists|table)\b/;
10486
11551
  var PARSE_CHUNK9 = 16384;
10487
11552
  function makePhpParser2() {
10488
11553
  const p = new Parser13();
10489
- p.setLanguage(Php2.php_only);
11554
+ p.setLanguage(Php3.php_only);
10490
11555
  return p;
10491
11556
  }
10492
11557
  function parseSource9(parser, source) {
@@ -10735,7 +11800,7 @@ function laravelMigrationEndpointsFromFile(file, serviceDir) {
10735
11800
  confidenceKind: "structural",
10736
11801
  ...columns.length > 0 ? { columns } : {},
10737
11802
  evidence: {
10738
- file: path44.relative(serviceDir, file.path),
11803
+ file: path50.relative(serviceDir, file.path),
10739
11804
  line: bp.line,
10740
11805
  snippet: snippet(file.content, bp.line)
10741
11806
  }
@@ -10757,7 +11822,7 @@ function laravelMigrationForeignKeys(file, serviceDir) {
10757
11822
  childTable,
10758
11823
  parentTable,
10759
11824
  evidence: {
10760
- file: path44.relative(serviceDir, file.path),
11825
+ file: path50.relative(serviceDir, file.path),
10761
11826
  line,
10762
11827
  snippet: snippet(file.content, line)
10763
11828
  }
@@ -10877,7 +11942,7 @@ function laravelModelEndpointsFromFile(file, serviceDir) {
10877
11942
  edgeType: "CALLS",
10878
11943
  confidenceKind: "verified-call-site",
10879
11944
  evidence: {
10880
- file: path44.relative(serviceDir, file.path),
11945
+ file: path50.relative(serviceDir, file.path),
10881
11946
  line,
10882
11947
  snippet: snippet(file.content, line)
10883
11948
  }
@@ -10913,7 +11978,7 @@ function laravelModelForeignKeys(file, serviceDir) {
10913
11978
  childTable,
10914
11979
  parentTable,
10915
11980
  evidence: {
10916
- file: path44.relative(serviceDir, file.path),
11981
+ file: path50.relative(serviceDir, file.path),
10917
11982
  line,
10918
11983
  snippet: snippet(file.content, line)
10919
11984
  }
@@ -10924,9 +11989,9 @@ function laravelModelForeignKeys(file, serviceDir) {
10924
11989
  }
10925
11990
 
10926
11991
  // src/extract/calls/go.ts
10927
- import path45 from "path";
11992
+ import path51 from "path";
10928
11993
  import Parser14 from "tree-sitter";
10929
- import Go3 from "tree-sitter-go";
11994
+ import Go4 from "tree-sitter-go";
10930
11995
  import { infraId as infraId15 } from "@neat.is/types";
10931
11996
  var PARSE_CHUNK10 = 16384;
10932
11997
  var DATABASE_SQL_METHODS = /* @__PURE__ */ new Set([
@@ -10955,7 +12020,7 @@ var DATABASE_SQL_IMPORT = "database/sql";
10955
12020
  var SQLX_IMPORT = "github.com/jmoiron/sqlx";
10956
12021
  function makeGoParser3() {
10957
12022
  const p = new Parser14();
10958
- p.setLanguage(Go3);
12023
+ p.setLanguage(Go4);
10959
12024
  return p;
10960
12025
  }
10961
12026
  function parseSource10(parser, source) {
@@ -10998,7 +12063,7 @@ function firstStringLiteralArg(argsNode) {
10998
12063
  return null;
10999
12064
  }
11000
12065
  function goSqlEndpointsFromFile(file, serviceDir) {
11001
- if (path45.extname(file.path) !== ".go") return [];
12066
+ if (path51.extname(file.path) !== ".go") return [];
11002
12067
  if (!file.content.includes(DATABASE_SQL_IMPORT) && !file.content.includes(SQLX_IMPORT)) return [];
11003
12068
  const tree = parseSource10(makeGoParser3(), file.content);
11004
12069
  const importsDatabaseSql = goImportsAny(tree.rootNode, /* @__PURE__ */ new Set([DATABASE_SQL_IMPORT]));
@@ -11027,7 +12092,7 @@ function goSqlEndpointsFromFile(file, serviceDir) {
11027
12092
  confidenceKind: "verified-call-site",
11028
12093
  ...columns.length > 0 ? { columns } : {},
11029
12094
  evidence: {
11030
- file: toPosix(path45.relative(serviceDir, file.path)),
12095
+ file: toPosix(path51.relative(serviceDir, file.path)),
11031
12096
  line,
11032
12097
  snippet: snippet(file.content, line)
11033
12098
  }
@@ -11037,15 +12102,15 @@ function goSqlEndpointsFromFile(file, serviceDir) {
11037
12102
  }
11038
12103
 
11039
12104
  // src/extract/calls/gorm.ts
11040
- import path46 from "path";
12105
+ import path52 from "path";
11041
12106
  import Parser15 from "tree-sitter";
11042
- import Go4 from "tree-sitter-go";
12107
+ import Go5 from "tree-sitter-go";
11043
12108
  import { infraId as infraId16 } from "@neat.is/types";
11044
12109
  var GORM_IMPORT_RE = /gorm\.io\/gorm/;
11045
12110
  var PARSE_CHUNK11 = 16384;
11046
12111
  function makeGoParser4() {
11047
12112
  const p = new Parser15();
11048
- p.setLanguage(Go4);
12113
+ p.setLanguage(Go5);
11049
12114
  return p;
11050
12115
  }
11051
12116
  function parseSource11(parser, source) {
@@ -11470,7 +12535,7 @@ function collectColumns(struct, structs, seen, prefix, out, emitted) {
11470
12535
  seen.delete(struct.name);
11471
12536
  }
11472
12537
  function gormEndpointsFromFile(file, serviceDir) {
11473
- if (path46.extname(file.path) !== ".go") return [];
12538
+ if (path52.extname(file.path) !== ".go") return [];
11474
12539
  if (!GORM_IMPORT_RE.test(file.content)) return [];
11475
12540
  const tree = parseSource11(makeGoParser4(), file.content);
11476
12541
  const { structs, models, tableFor } = analyze(tree);
@@ -11492,7 +12557,7 @@ function gormEndpointsFromFile(file, serviceDir) {
11492
12557
  confidenceKind: "structural",
11493
12558
  ...columns.length > 0 ? { columns } : {},
11494
12559
  evidence: {
11495
- file: toPosix(path46.relative(serviceDir, file.path)),
12560
+ file: toPosix(path52.relative(serviceDir, file.path)),
11496
12561
  line: struct.line,
11497
12562
  snippet: snippet(file.content, struct.line)
11498
12563
  }
@@ -11501,7 +12566,7 @@ function gormEndpointsFromFile(file, serviceDir) {
11501
12566
  return out;
11502
12567
  }
11503
12568
  function gormForeignKeys(file, serviceDir) {
11504
- if (path46.extname(file.path) !== ".go") return [];
12569
+ if (path52.extname(file.path) !== ".go") return [];
11505
12570
  if (!GORM_IMPORT_RE.test(file.content)) return [];
11506
12571
  const tree = parseSource11(makeGoParser4(), file.content);
11507
12572
  const { structs, models, tableFor } = analyze(tree);
@@ -11516,7 +12581,7 @@ function gormForeignKeys(file, serviceDir) {
11516
12581
  childTable,
11517
12582
  parentTable,
11518
12583
  evidence: {
11519
- file: toPosix(path46.relative(serviceDir, file.path)),
12584
+ file: toPosix(path52.relative(serviceDir, file.path)),
11520
12585
  line,
11521
12586
  snippet: snippet(file.content, line)
11522
12587
  }
@@ -11569,7 +12634,7 @@ async function addExternalEndpointEdges(graph, services) {
11569
12634
  let nodesAdded = 0;
11570
12635
  let edgesAdded = 0;
11571
12636
  for (const service of services) {
11572
- const files = await loadSourceFiles(service.dir);
12637
+ const files = await loadSourceFiles(service.dir, service.excludeDirs);
11573
12638
  const endpoints = [];
11574
12639
  const maskedFiles = [];
11575
12640
  for (const file of files) {
@@ -11619,7 +12684,7 @@ async function addExternalEndpointEdges(graph, services) {
11619
12684
  if (!graph.hasNode(ep.infraId)) {
11620
12685
  const node = {
11621
12686
  id: ep.infraId,
11622
- type: NodeType18.InfraNode,
12687
+ type: NodeType24.InfraNode,
11623
12688
  name: ep.name,
11624
12689
  // #238 — `aws-*` covers AWS-SDK client kinds (aws-s3, aws-dynamodb,
11625
12690
  // aws-cognito-identity-provider, …); `s3-` / `dynamodb-` cover the
@@ -11632,7 +12697,7 @@ async function addExternalEndpointEdges(graph, services) {
11632
12697
  }
11633
12698
  if (ep.columns && ep.columns.length > 0) {
11634
12699
  const node = graph.getNodeAttributes(ep.infraId);
11635
- if (node.type === NodeType18.InfraNode) {
12700
+ if (node.type === NodeType24.InfraNode) {
11636
12701
  graph.replaceNodeAttributes(ep.infraId, {
11637
12702
  ...node,
11638
12703
  columns: foldColumns(
@@ -11646,7 +12711,7 @@ async function addExternalEndpointEdges(graph, services) {
11646
12711
  }
11647
12712
  if (ep.sdkWrites && Object.keys(ep.sdkWrites).length > 0) {
11648
12713
  const node = graph.getNodeAttributes(ep.infraId);
11649
- if (node.type === NodeType18.InfraNode) {
12714
+ if (node.type === NodeType24.InfraNode) {
11650
12715
  graph.replaceNodeAttributes(ep.infraId, {
11651
12716
  ...node,
11652
12717
  columns: foldSdkWrites(node.columns, ep.sdkWrites)
@@ -11708,7 +12773,7 @@ async function addCallEdges(graph, services) {
11708
12773
  // src/extract/table-edges.ts
11709
12774
  import {
11710
12775
  EdgeType as EdgeType16,
11711
- NodeType as NodeType19,
12776
+ NodeType as NodeType25,
11712
12777
  Provenance as Provenance16,
11713
12778
  confidenceForExtracted as confidenceForExtracted13,
11714
12779
  extractedEdgeId as extractedEdgeId10,
@@ -11718,7 +12783,7 @@ async function addTableEdges(graph, services) {
11718
12783
  let nodesAdded = 0;
11719
12784
  let edgesAdded = 0;
11720
12785
  for (const service of services) {
11721
- const files = await loadSourceFiles(service.dir);
12786
+ const files = await loadSourceFiles(service.dir, service.excludeDirs);
11722
12787
  const refs = [];
11723
12788
  const modelRefs = [];
11724
12789
  for (const file of files) {
@@ -11767,7 +12832,7 @@ function ensureTableNode(graph, id, name) {
11767
12832
  if (graph.hasNode(id)) return 0;
11768
12833
  const node = {
11769
12834
  id,
11770
- type: NodeType19.InfraNode,
12835
+ type: NodeType25.InfraNode,
11771
12836
  name,
11772
12837
  provider: "self",
11773
12838
  kind: "sql-table"
@@ -11777,15 +12842,15 @@ function ensureTableNode(graph, id, name) {
11777
12842
  }
11778
12843
 
11779
12844
  // src/extract/infra/docker-compose.ts
11780
- import path47 from "path";
12845
+ import path53 from "path";
11781
12846
  import { EdgeType as EdgeType17, Provenance as Provenance18, confidenceForExtracted as confidenceForExtracted15 } from "@neat.is/types";
11782
12847
 
11783
12848
  // src/extract/infra/shared.ts
11784
- import { NodeType as NodeType20, 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";
11785
12850
  function makeInfraNode(kind, name, provider = "self", extras) {
11786
12851
  return {
11787
12852
  id: infraId18(kind, name),
11788
- type: NodeType20.InfraNode,
12853
+ type: NodeType26.InfraNode,
11789
12854
  name,
11790
12855
  provider,
11791
12856
  kind,
@@ -11847,7 +12912,7 @@ function dependsOnList(value) {
11847
12912
  }
11848
12913
  function serviceNameToServiceNode(name, services) {
11849
12914
  for (const s of services) {
11850
- if (s.node.name === name || path47.basename(s.dir) === name) return s.node.id;
12915
+ if (s.node.name === name || path53.basename(s.dir) === name) return s.node.id;
11851
12916
  }
11852
12917
  return null;
11853
12918
  }
@@ -11856,7 +12921,7 @@ async function addComposeInfra(graph, scanPath, services) {
11856
12921
  let edgesAdded = 0;
11857
12922
  let composePath = null;
11858
12923
  for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
11859
- const abs = path47.join(scanPath, name);
12924
+ const abs = path53.join(scanPath, name);
11860
12925
  if (await exists(abs)) {
11861
12926
  composePath = abs;
11862
12927
  break;
@@ -11869,13 +12934,13 @@ async function addComposeInfra(graph, scanPath, services) {
11869
12934
  } catch (err) {
11870
12935
  recordExtractionError(
11871
12936
  "infra docker-compose",
11872
- path47.relative(scanPath, composePath),
12937
+ path53.relative(scanPath, composePath),
11873
12938
  err
11874
12939
  );
11875
12940
  return { nodesAdded, edgesAdded };
11876
12941
  }
11877
12942
  if (!compose?.services) return { nodesAdded, edgesAdded };
11878
- const evidenceFile = path47.relative(scanPath, composePath).split(path47.sep).join("/");
12943
+ const evidenceFile = path53.relative(scanPath, composePath).split(path53.sep).join("/");
11879
12944
  const composeNameToNodeId = /* @__PURE__ */ new Map();
11880
12945
  for (const [composeName, svc] of Object.entries(compose.services)) {
11881
12946
  const matchedServiceId = serviceNameToServiceNode(composeName, services);
@@ -11916,8 +12981,8 @@ async function addComposeInfra(graph, scanPath, services) {
11916
12981
  }
11917
12982
 
11918
12983
  // src/extract/infra/dockerfile.ts
11919
- import path48 from "path";
11920
- import { promises as fs18 } from "fs";
12984
+ import path54 from "path";
12985
+ import { promises as fs24 } from "fs";
11921
12986
  import { EdgeType as EdgeType18, Provenance as Provenance19, confidenceForExtracted as confidenceForExtracted16 } from "@neat.is/types";
11922
12987
  function readDockerfile(content) {
11923
12988
  let image = null;
@@ -11947,15 +13012,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
11947
13012
  let nodesAdded = 0;
11948
13013
  let edgesAdded = 0;
11949
13014
  for (const service of services) {
11950
- const dockerfilePath = path48.join(service.dir, "Dockerfile");
13015
+ const dockerfilePath = path54.join(service.dir, "Dockerfile");
11951
13016
  if (!await exists(dockerfilePath)) continue;
11952
13017
  let content;
11953
13018
  try {
11954
- content = await fs18.readFile(dockerfilePath, "utf8");
13019
+ content = await fs24.readFile(dockerfilePath, "utf8");
11955
13020
  } catch (err) {
11956
13021
  recordExtractionError(
11957
13022
  "infra dockerfile",
11958
- path48.relative(scanPath, dockerfilePath),
13023
+ path54.relative(scanPath, dockerfilePath),
11959
13024
  err
11960
13025
  );
11961
13026
  continue;
@@ -11967,8 +13032,8 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
11967
13032
  graph.addNode(node.id, node);
11968
13033
  nodesAdded++;
11969
13034
  }
11970
- const relDockerfile = toPosix(path48.relative(service.dir, dockerfilePath));
11971
- const evidenceFile = toPosix(path48.relative(scanPath, dockerfilePath));
13035
+ const relDockerfile = toPosix(path54.relative(service.dir, dockerfilePath));
13036
+ const evidenceFile = toPosix(path54.relative(scanPath, dockerfilePath));
11972
13037
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
11973
13038
  graph,
11974
13039
  service.pkg.name,
@@ -12019,23 +13084,23 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
12019
13084
  }
12020
13085
 
12021
13086
  // src/extract/infra/terraform.ts
12022
- import { promises as fs19 } from "fs";
12023
- import path49 from "path";
13087
+ import { promises as fs25 } from "fs";
13088
+ import path55 from "path";
12024
13089
  import { EdgeType as EdgeType19, Provenance as Provenance20, confidenceForExtracted as confidenceForExtracted17 } from "@neat.is/types";
12025
13090
  var RESOURCE_RE = /resource\s+"(aws_[A-Za-z0-9_]+)"\s+"([A-Za-z0-9_-]+)"/g;
12026
13091
  var REFERENCE_RE = /(?<![\w.])(aws_[A-Za-z0-9_]+)\.([A-Za-z0-9_-]+)/g;
12027
13092
  async function walkTfFiles(start, depth = 0, max = 5) {
12028
13093
  if (depth > max) return [];
12029
13094
  const out = [];
12030
- const entries = await fs19.readdir(start, { withFileTypes: true }).catch(() => []);
13095
+ const entries = await fs25.readdir(start, { withFileTypes: true }).catch(() => []);
12031
13096
  for (const entry of entries) {
12032
13097
  if (entry.isDirectory()) {
12033
13098
  if (IGNORED_DIRS.has(entry.name) || entry.name === ".terraform") continue;
12034
- const child = path49.join(start, entry.name);
13099
+ const child = path55.join(start, entry.name);
12035
13100
  if (await isPythonVenvDir(child)) continue;
12036
13101
  out.push(...await walkTfFiles(child, depth + 1, max));
12037
13102
  } else if (entry.isFile() && entry.name.endsWith(".tf")) {
12038
- out.push(path49.join(start, entry.name));
13103
+ out.push(path55.join(start, entry.name));
12039
13104
  }
12040
13105
  }
12041
13106
  return out;
@@ -12066,8 +13131,8 @@ async function addTerraformResources(graph, scanPath) {
12066
13131
  let edgesAdded = 0;
12067
13132
  const files = await walkTfFiles(scanPath);
12068
13133
  for (const file of files) {
12069
- const content = await fs19.readFile(file, "utf8");
12070
- const evidenceFile = toPosix(path49.relative(scanPath, file));
13134
+ const content = await fs25.readFile(file, "utf8");
13135
+ const evidenceFile = toPosix(path55.relative(scanPath, file));
12071
13136
  const resources = [];
12072
13137
  const byKey = /* @__PURE__ */ new Map();
12073
13138
  RESOURCE_RE.lastIndex = 0;
@@ -12123,8 +13188,8 @@ async function addTerraformResources(graph, scanPath) {
12123
13188
  }
12124
13189
 
12125
13190
  // src/extract/infra/k8s.ts
12126
- import { promises as fs20 } from "fs";
12127
- import path50 from "path";
13191
+ import { promises as fs26 } from "fs";
13192
+ import path56 from "path";
12128
13193
  import { parseAllDocuments as parseAllDocuments2 } from "yaml";
12129
13194
  var K8S_KIND_TO_INFRA_KIND = {
12130
13195
  Service: "k8s-service",
@@ -12138,15 +13203,15 @@ var K8S_KIND_TO_INFRA_KIND = {
12138
13203
  async function walkYamlFiles2(start, depth = 0, max = 5) {
12139
13204
  if (depth > max) return [];
12140
13205
  const out = [];
12141
- const entries = await fs20.readdir(start, { withFileTypes: true }).catch(() => []);
13206
+ const entries = await fs26.readdir(start, { withFileTypes: true }).catch(() => []);
12142
13207
  for (const entry of entries) {
12143
13208
  if (entry.isDirectory()) {
12144
13209
  if (IGNORED_DIRS.has(entry.name)) continue;
12145
- const child = path50.join(start, entry.name);
13210
+ const child = path56.join(start, entry.name);
12146
13211
  if (await isPythonVenvDir(child)) continue;
12147
13212
  out.push(...await walkYamlFiles2(child, depth + 1, max));
12148
- } else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(path50.extname(entry.name))) {
12149
- out.push(path50.join(start, entry.name));
13213
+ } else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(path56.extname(entry.name))) {
13214
+ out.push(path56.join(start, entry.name));
12150
13215
  }
12151
13216
  }
12152
13217
  return out;
@@ -12155,7 +13220,7 @@ async function addK8sResources(graph, scanPath) {
12155
13220
  let nodesAdded = 0;
12156
13221
  const files = await walkYamlFiles2(scanPath);
12157
13222
  for (const file of files) {
12158
- const content = await fs20.readFile(file, "utf8");
13223
+ const content = await fs26.readFile(file, "utf8");
12159
13224
  let docs;
12160
13225
  try {
12161
13226
  docs = parseAllDocuments2(content).map((d) => d.toJSON());
@@ -12178,17 +13243,17 @@ async function addK8sResources(graph, scanPath) {
12178
13243
  }
12179
13244
 
12180
13245
  // src/extract/infra/cloudflare.ts
12181
- import { promises as fs21 } from "fs";
12182
- import path51 from "path";
12183
- 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";
12184
13249
  import { EdgeType as EdgeType20, Provenance as Provenance21, confidenceForExtracted as confidenceForExtracted18 } from "@neat.is/types";
12185
13250
  var WRANGLER_FILENAMES = ["wrangler.toml", "wrangler.jsonc", "wrangler.json"];
12186
13251
  async function readWranglerConfig(dir) {
12187
13252
  for (const filename of WRANGLER_FILENAMES) {
12188
- const abs = path51.join(dir, filename);
13253
+ const abs = path57.join(dir, filename);
12189
13254
  if (!await exists(abs)) continue;
12190
- const raw = await fs21.readFile(abs, "utf8");
12191
- 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));
12192
13257
  return { config, relFile: filename, raw };
12193
13258
  }
12194
13259
  return null;
@@ -12248,11 +13313,11 @@ async function addCloudflareWorkers(graph, services, scanPath) {
12248
13313
  try {
12249
13314
  read = await readWranglerConfig(service.dir);
12250
13315
  } catch (err) {
12251
- recordExtractionError("infra cloudflare", path51.relative(scanPath, service.dir), err);
13316
+ recordExtractionError("infra cloudflare", path57.relative(scanPath, service.dir), err);
12252
13317
  continue;
12253
13318
  }
12254
13319
  if (!read || !read.config.name) continue;
12255
- const evidenceFile = toPosix(path51.relative(scanPath, path51.join(service.dir, read.relFile)));
13320
+ const evidenceFile = toPosix(path57.relative(scanPath, path57.join(service.dir, read.relFile)));
12256
13321
  discovered.push({ service, config: read.config, relFile: read.relFile, raw: read.raw, evidenceFile });
12257
13322
  }
12258
13323
  for (const worker of discovered) {
@@ -12264,7 +13329,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
12264
13329
  }
12265
13330
  let anchorId = service.node.id;
12266
13331
  if (config.main) {
12267
- const entryRelPath = toPosix(path51.normalize(config.main));
13332
+ const entryRelPath = toPosix(path57.normalize(config.main));
12268
13333
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
12269
13334
  graph,
12270
13335
  service.pkg.name,
@@ -12410,24 +13475,24 @@ async function addCloudflareWorkers(graph, services, scanPath) {
12410
13475
  }
12411
13476
 
12412
13477
  // src/extract/infra/vercel.ts
12413
- import { promises as fs22 } from "fs";
12414
- import path52 from "path";
13478
+ import { promises as fs28 } from "fs";
13479
+ import path58 from "path";
12415
13480
  import { EdgeType as EdgeType21 } from "@neat.is/types";
12416
13481
  var VERCEL_CONFIG_FILENAMES = ["vercel.json", "vercel.jsonc"];
12417
13482
  async function readVercelConfig(dir) {
12418
13483
  for (const filename of VERCEL_CONFIG_FILENAMES) {
12419
- const abs = path52.join(dir, filename);
13484
+ const abs = path58.join(dir, filename);
12420
13485
  if (!await exists(abs)) continue;
12421
- const raw = await fs22.readFile(abs, "utf8");
13486
+ const raw = await fs28.readFile(abs, "utf8");
12422
13487
  const config = JSON.parse(maskCommentsInSource(raw));
12423
13488
  return { config, relFile: filename, raw };
12424
13489
  }
12425
13490
  return null;
12426
13491
  }
12427
13492
  async function readLinkedProjectName(dir) {
12428
- const abs = path52.join(dir, ".vercel", "project.json");
13493
+ const abs = path58.join(dir, ".vercel", "project.json");
12429
13494
  if (!await exists(abs)) return void 0;
12430
- const parsed = JSON.parse(await fs22.readFile(abs, "utf8"));
13495
+ const parsed = JSON.parse(await fs28.readFile(abs, "utf8"));
12431
13496
  return typeof parsed.projectName === "string" ? parsed.projectName : void 0;
12432
13497
  }
12433
13498
  function routeSource(route) {
@@ -12443,7 +13508,7 @@ async function addVercelServices(graph, services, scanPath) {
12443
13508
  read = await readVercelConfig(service.dir);
12444
13509
  projectName = await readLinkedProjectName(service.dir);
12445
13510
  } catch (err) {
12446
- recordExtractionError("infra vercel", path52.relative(scanPath, service.dir), err);
13511
+ recordExtractionError("infra vercel", path58.relative(scanPath, service.dir), err);
12447
13512
  continue;
12448
13513
  }
12449
13514
  if (!read && !projectName) continue;
@@ -12459,7 +13524,7 @@ async function addVercelServices(graph, services, scanPath) {
12459
13524
  const anchorId = service.node.id;
12460
13525
  if (!read) continue;
12461
13526
  const { config, relFile, raw } = read;
12462
- const evidenceFile = toPosix(path52.relative(scanPath, path52.join(service.dir, relFile)));
13527
+ const evidenceFile = toPosix(path58.relative(scanPath, path58.join(service.dir, relFile)));
12463
13528
  const add = (edgeType, kind, name) => {
12464
13529
  if (!name) return;
12465
13530
  const result = emitPlatformResourceEdge(
@@ -12487,17 +13552,17 @@ async function addVercelServices(graph, services, scanPath) {
12487
13552
  }
12488
13553
 
12489
13554
  // src/extract/infra/railway.ts
12490
- import { promises as fs23 } from "fs";
12491
- import path53 from "path";
12492
- 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";
12493
13558
  import { EdgeType as EdgeType22 } from "@neat.is/types";
12494
13559
  var RAILWAY_FILENAMES = ["railway.toml", "railway.json", "railway.jsonc"];
12495
13560
  async function readRailwayConfig(dir) {
12496
13561
  for (const filename of RAILWAY_FILENAMES) {
12497
- const abs = path53.join(dir, filename);
13562
+ const abs = path59.join(dir, filename);
12498
13563
  if (!await exists(abs)) continue;
12499
- const raw = await fs23.readFile(abs, "utf8");
12500
- 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));
12501
13566
  return { config, relFile: filename, raw };
12502
13567
  }
12503
13568
  return null;
@@ -12510,7 +13575,7 @@ async function addRailwayServices(graph, services, scanPath) {
12510
13575
  try {
12511
13576
  read = await readRailwayConfig(service.dir);
12512
13577
  } catch (err) {
12513
- recordExtractionError("infra railway", path53.relative(scanPath, service.dir), err);
13578
+ recordExtractionError("infra railway", path59.relative(scanPath, service.dir), err);
12514
13579
  continue;
12515
13580
  }
12516
13581
  if (!read) continue;
@@ -12520,7 +13585,7 @@ async function addRailwayServices(graph, services, scanPath) {
12520
13585
  }
12521
13586
  const anchorId = service.node.id;
12522
13587
  const { config, relFile, raw } = read;
12523
- const evidenceFile = toPosix(path53.relative(scanPath, path53.join(service.dir, relFile)));
13588
+ const evidenceFile = toPosix(path59.relative(scanPath, path59.join(service.dir, relFile)));
12524
13589
  const add = (edgeType, kind, name) => {
12525
13590
  if (!name) return;
12526
13591
  const result = emitPlatformResourceEdge(
@@ -12544,16 +13609,16 @@ async function addRailwayServices(graph, services, scanPath) {
12544
13609
  }
12545
13610
 
12546
13611
  // src/extract/infra/supabase.ts
12547
- import { promises as fs24 } from "fs";
12548
- import path54 from "path";
12549
- 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";
12550
13615
  import { EdgeType as EdgeType23 } from "@neat.is/types";
12551
13616
  async function readSupabaseConfig(dir) {
12552
- const relFile = path54.join("supabase", "config.toml");
12553
- const abs = path54.join(dir, relFile);
13617
+ const relFile = path60.join("supabase", "config.toml");
13618
+ const abs = path60.join(dir, relFile);
12554
13619
  if (!await exists(abs)) return null;
12555
- const raw = await fs24.readFile(abs, "utf8");
12556
- const config = parseToml4(raw);
13620
+ const raw = await fs30.readFile(abs, "utf8");
13621
+ const config = parseToml5(raw);
12557
13622
  return { config, relFile, raw };
12558
13623
  }
12559
13624
  async function addSupabaseProjects(graph, services, scanPath) {
@@ -12564,7 +13629,7 @@ async function addSupabaseProjects(graph, services, scanPath) {
12564
13629
  try {
12565
13630
  read = await readSupabaseConfig(service.dir);
12566
13631
  } catch (err) {
12567
- recordExtractionError("infra supabase", path54.relative(scanPath, service.dir), err);
13632
+ recordExtractionError("infra supabase", path60.relative(scanPath, service.dir), err);
12568
13633
  continue;
12569
13634
  }
12570
13635
  if (!read) continue;
@@ -12579,7 +13644,7 @@ async function addSupabaseProjects(graph, services, scanPath) {
12579
13644
  });
12580
13645
  }
12581
13646
  const anchorId = service.node.id;
12582
- const evidenceFile = toPosix(path54.relative(scanPath, path54.join(service.dir, relFile)));
13647
+ const evidenceFile = toPosix(path60.relative(scanPath, path60.join(service.dir, relFile)));
12583
13648
  const add = (edgeType, kind, name) => {
12584
13649
  if (!name) return;
12585
13650
  const result = emitPlatformResourceEdge(
@@ -12620,12 +13685,12 @@ async function addInfra(graph, scanPath, services) {
12620
13685
  }
12621
13686
 
12622
13687
  // src/extract/zod-shapes.ts
12623
- import path55 from "path";
13688
+ import path61 from "path";
12624
13689
  import Parser16 from "tree-sitter";
12625
13690
  import JavaScript8 from "tree-sitter-javascript";
12626
13691
  import {
12627
13692
  EdgeType as EdgeType24,
12628
- NodeType as NodeType21,
13693
+ NodeType as NodeType27,
12629
13694
  Provenance as Provenance22,
12630
13695
  confidenceForExtracted as confidenceForExtracted19,
12631
13696
  extractedEdgeId as extractedEdgeId11,
@@ -12722,7 +13787,7 @@ function topLevelSchemas(root) {
12722
13787
  }
12723
13788
  function zodShapesFromFile(file, serviceDir) {
12724
13789
  if (!ZOD_IMPORT_RE.test(file.content)) return [];
12725
- const tree = parseSource3(parserForExt4(path55.extname(file.path)), file.content);
13790
+ const tree = parseSource3(parserForExt4(path61.extname(file.path)), file.content);
12726
13791
  const out = [];
12727
13792
  const seen = /* @__PURE__ */ new Set();
12728
13793
  for (const { name, call } of topLevelSchemas(tree.rootNode)) {
@@ -12740,7 +13805,7 @@ function zodShapesFromFile(file, serviceDir) {
12740
13805
  name,
12741
13806
  fields,
12742
13807
  evidence: {
12743
- file: path55.relative(serviceDir, file.path),
13808
+ file: path61.relative(serviceDir, file.path),
12744
13809
  line,
12745
13810
  snippet: snippet(file.content, line)
12746
13811
  }
@@ -12757,7 +13822,7 @@ async function addZodShapes(graph, services) {
12757
13822
  ...service.pkg.devDependencies ?? {}
12758
13823
  };
12759
13824
  if (deps["zod"] === void 0) continue;
12760
- const files = await loadSourceFiles(service.dir);
13825
+ const files = await loadSourceFiles(service.dir, service.excludeDirs);
12761
13826
  for (const file of files) {
12762
13827
  if (isTestPath(file.path)) continue;
12763
13828
  let shapes;
@@ -12771,7 +13836,7 @@ async function addZodShapes(graph, services) {
12771
13836
  if (!graph.hasNode(shape.infraId)) {
12772
13837
  const node = {
12773
13838
  id: shape.infraId,
12774
- type: NodeType21.InfraNode,
13839
+ type: NodeType27.InfraNode,
12775
13840
  name: shape.name,
12776
13841
  provider: "self",
12777
13842
  kind: "zod-schema"
@@ -12781,7 +13846,7 @@ async function addZodShapes(graph, services) {
12781
13846
  }
12782
13847
  if (shape.fields.length > 0) {
12783
13848
  const node = graph.getNodeAttributes(shape.infraId);
12784
- if (node.type === NodeType21.InfraNode) {
13849
+ if (node.type === NodeType27.InfraNode) {
12785
13850
  graph.replaceNodeAttributes(shape.infraId, {
12786
13851
  ...node,
12787
13852
  columns: foldColumns(
@@ -12823,7 +13888,7 @@ async function addZodShapes(graph, services) {
12823
13888
  }
12824
13889
 
12825
13890
  // src/extract/firestore-rules.ts
12826
- import { NodeType as NodeType22 } from "@neat.is/types";
13891
+ import { NodeType as NodeType28 } from "@neat.is/types";
12827
13892
  var FIRESTORE_COLLECTION_KIND = "firestore-collection";
12828
13893
  var WRITE_METHODS = /* @__PURE__ */ new Set(["write", "create", "update"]);
12829
13894
  function stripComments(src) {
@@ -12963,7 +14028,7 @@ async function addFirestoreRules(graph, services) {
12963
14028
  if (guards.size === 0) return { nodesAdded: 0, edgesAdded: 0 };
12964
14029
  graph.forEachNode((id, attrs) => {
12965
14030
  const node = attrs;
12966
- if (node.type !== NodeType22.InfraNode) return;
14031
+ if (node.type !== NodeType28.InfraNode) return;
12967
14032
  if (node.kind !== FIRESTORE_COLLECTION_KIND) return;
12968
14033
  const fields = guards.get(collectionKeyFromName(node.name));
12969
14034
  if (!fields || fields.size === 0) return;
@@ -12976,16 +14041,16 @@ async function addFirestoreRules(graph, services) {
12976
14041
  }
12977
14042
 
12978
14043
  // src/extract/index.ts
12979
- import path57 from "path";
14044
+ import path63 from "path";
12980
14045
 
12981
14046
  // src/extract/retire.ts
12982
14047
  import { existsSync as existsSync2 } from "fs";
12983
- import path56 from "path";
12984
- import { NodeType as NodeType23, Provenance as Provenance23 } from "@neat.is/types";
14048
+ import path62 from "path";
14049
+ import { NodeType as NodeType29, Provenance as Provenance23 } from "@neat.is/types";
12985
14050
  function dropOrphanedFileNodes(graph) {
12986
14051
  const orphans = [];
12987
14052
  graph.forEachNode((id, attrs) => {
12988
- if (attrs.type !== NodeType23.FileNode) return;
14053
+ if (attrs.type !== NodeType29.FileNode) return;
12989
14054
  if (graph.inboundEdges(id).length === 0 && graph.outboundEdges(id).length === 0) {
12990
14055
  orphans.push(id);
12991
14056
  }
@@ -13014,11 +14079,11 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
13014
14079
  if (edge.provenance !== Provenance23.EXTRACTED) return;
13015
14080
  const evidenceFile = edge.evidence?.file;
13016
14081
  if (!evidenceFile) return;
13017
- if (path56.isAbsolute(evidenceFile)) {
14082
+ if (path62.isAbsolute(evidenceFile)) {
13018
14083
  if (!existsSync2(evidenceFile)) toDrop.push(id);
13019
14084
  return;
13020
14085
  }
13021
- const found = bases.some((base) => existsSync2(path56.join(base, evidenceFile)));
14086
+ const found = bases.some((base) => existsSync2(path62.join(base, evidenceFile)));
13022
14087
  if (!found) toDrop.push(id);
13023
14088
  });
13024
14089
  for (const id of toDrop) graph.dropEdge(id);
@@ -13075,7 +14140,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
13075
14140
  }
13076
14141
  const droppedEntries = drainDroppedExtracted();
13077
14142
  if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
13078
- const rejectedPath = path57.join(path57.dirname(opts.errorsPath), "rejected.ndjson");
14143
+ const rejectedPath = path63.join(path63.dirname(opts.errorsPath), "rejected.ndjson");
13079
14144
  try {
13080
14145
  await writeRejectedExtracted(droppedEntries, rejectedPath);
13081
14146
  } catch (err) {
@@ -13112,11 +14177,11 @@ import {
13112
14177
  databaseId as databaseId3,
13113
14178
  DivergenceResultSchema,
13114
14179
  EdgeType as EdgeType25,
13115
- NodeType as NodeType24,
14180
+ NodeType as NodeType30,
13116
14181
  parseEdgeId,
13117
14182
  parseFileId,
13118
14183
  Provenance as Provenance24,
13119
- serviceId as serviceId7
14184
+ serviceId as serviceId13
13120
14185
  } from "@neat.is/types";
13121
14186
  function bucketKey(source, target, type) {
13122
14187
  return `${type}|${source}|${target}`;
@@ -13126,8 +14191,8 @@ function bucketSourceFor(graph, edge) {
13126
14191
  const parsed = parseFileId(edge.source);
13127
14192
  if (!parsed || !graph.hasNode(edge.target)) return edge.source;
13128
14193
  const target = graph.getNodeAttributes(edge.target);
13129
- if (target.type !== NodeType24.DatabaseNode) return edge.source;
13130
- return serviceId7(parsed.service);
14194
+ if (target.type !== NodeType30.DatabaseNode) return edge.source;
14195
+ return serviceId13(parsed.service);
13131
14196
  }
13132
14197
  function bucketEdges(graph) {
13133
14198
  const buckets2 = /* @__PURE__ */ new Map();
@@ -13158,22 +14223,22 @@ function bucketEdges(graph) {
13158
14223
  function nodeIsFrontier(graph, nodeId) {
13159
14224
  if (!graph.hasNode(nodeId)) return false;
13160
14225
  const attrs = graph.getNodeAttributes(nodeId);
13161
- return attrs.type === NodeType24.FrontierNode;
14226
+ return attrs.type === NodeType30.FrontierNode;
13162
14227
  }
13163
14228
  function nodeIsWebsocketChannel(graph, nodeId) {
13164
14229
  if (!graph.hasNode(nodeId)) return false;
13165
14230
  const attrs = graph.getNodeAttributes(nodeId);
13166
- return attrs.type === NodeType24.WebSocketChannelNode;
14231
+ return attrs.type === NodeType30.WebSocketChannelNode;
13167
14232
  }
13168
14233
  function nodeIsServerAction(graph, nodeId) {
13169
14234
  if (!graph.hasNode(nodeId)) return false;
13170
14235
  const attrs = graph.getNodeAttributes(nodeId);
13171
- return attrs.type === NodeType24.ServerActionNode;
14236
+ return attrs.type === NodeType30.ServerActionNode;
13172
14237
  }
13173
14238
  function nodeIsSymbol(graph, nodeId) {
13174
14239
  if (!graph.hasNode(nodeId)) return false;
13175
14240
  const attrs = graph.getNodeAttributes(nodeId);
13176
- return attrs.type === NodeType24.SymbolNode;
14241
+ return attrs.type === NodeType30.SymbolNode;
13177
14242
  }
13178
14243
  function clampConfidence(n) {
13179
14244
  if (!Number.isFinite(n)) return 0;
@@ -13258,7 +14323,7 @@ function detectHostMismatch(graph, svcId, svc) {
13258
14323
  if (edge.type !== EdgeType25.CONNECTS_TO) continue;
13259
14324
  if (edge.provenance !== Provenance24.OBSERVED) continue;
13260
14325
  const target = graph.getNodeAttributes(edge.target);
13261
- if (target.type !== NodeType24.DatabaseNode) continue;
14326
+ if (target.type !== NodeType30.DatabaseNode) continue;
13262
14327
  const observedHost = target.host?.trim();
13263
14328
  if (!observedHost) continue;
13264
14329
  if (observedHost === declaredHost) continue;
@@ -13283,7 +14348,7 @@ function detectCompatDivergences(graph, svcId, svc) {
13283
14348
  if (edge.type !== EdgeType25.CONNECTS_TO) continue;
13284
14349
  if (edge.provenance !== Provenance24.OBSERVED) continue;
13285
14350
  const target = graph.getNodeAttributes(edge.target);
13286
- if (target.type !== NodeType24.DatabaseNode) continue;
14351
+ if (target.type !== NodeType30.DatabaseNode) continue;
13287
14352
  for (const pair of compatPairs()) {
13288
14353
  if (pair.engine !== target.engine) continue;
13289
14354
  const declared = deps[pair.driver];
@@ -13399,13 +14464,13 @@ function computeDivergences(graph, opts = {}) {
13399
14464
  }
13400
14465
  graph.forEachNode((nodeId, attrs) => {
13401
14466
  const n = attrs;
13402
- if (n.type === NodeType24.ServiceNode) {
14467
+ if (n.type === NodeType30.ServiceNode) {
13403
14468
  const svc = n;
13404
14469
  for (const d of detectHostMismatch(graph, nodeId, svc)) all.push(d);
13405
14470
  for (const d of detectCompatDivergences(graph, nodeId, svc)) all.push(d);
13406
14471
  return;
13407
14472
  }
13408
- if (n.type === NodeType24.InfraNode && n.kind === "sql-table") {
14473
+ if (n.type === NodeType30.InfraNode && n.kind === "sql-table") {
13409
14474
  for (const d of detectColumnDrift(n)) all.push(d);
13410
14475
  }
13411
14476
  });
@@ -13449,9 +14514,9 @@ function computeDivergences(graph, opts = {}) {
13449
14514
  }
13450
14515
 
13451
14516
  // src/persist.ts
13452
- import { promises as fs25 } from "fs";
13453
- import path58 from "path";
13454
- import { NodeType as NodeType25, Provenance as Provenance25, observedEdgeId as observedEdgeId2 } from "@neat.is/types";
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";
13455
14520
  var SCHEMA_VERSION = 7;
13456
14521
  function migrateV1ToV2(payload) {
13457
14522
  const nodes = payload.graph.nodes;
@@ -13475,7 +14540,7 @@ function migrateV5ToV6(payload) {
13475
14540
  if (Array.isArray(nodes)) {
13476
14541
  for (const node of nodes) {
13477
14542
  const attrs = node.attributes;
13478
- if (!attrs || attrs.type !== NodeType25.InfraNode) continue;
14543
+ if (!attrs || attrs.type !== NodeType31.InfraNode) continue;
13479
14544
  if (attrs.kind !== "sql-table" && attrs.kind !== "supabase-table") continue;
13480
14545
  if (!Array.isArray(attrs.columns)) attrs.columns = [];
13481
14546
  }
@@ -13505,7 +14570,7 @@ function migrateV2ToV3(payload) {
13505
14570
  return { ...payload, schemaVersion: 3 };
13506
14571
  }
13507
14572
  async function ensureDir(filePath) {
13508
- await fs25.mkdir(path58.dirname(filePath), { recursive: true });
14573
+ await fs31.mkdir(path64.dirname(filePath), { recursive: true });
13509
14574
  }
13510
14575
  async function saveGraphToDisk(graph, outPath) {
13511
14576
  await ensureDir(outPath);
@@ -13515,13 +14580,13 @@ async function saveGraphToDisk(graph, outPath) {
13515
14580
  graph: graph.export()
13516
14581
  };
13517
14582
  const tmp = `${outPath}.tmp`;
13518
- await fs25.writeFile(tmp, JSON.stringify(payload), "utf8");
13519
- await fs25.rename(tmp, outPath);
14583
+ await fs31.writeFile(tmp, JSON.stringify(payload), "utf8");
14584
+ await fs31.rename(tmp, outPath);
13520
14585
  }
13521
14586
  async function loadGraphFromDisk(graph, outPath) {
13522
14587
  let raw;
13523
14588
  try {
13524
- raw = await fs25.readFile(outPath, "utf8");
14589
+ raw = await fs31.readFile(outPath, "utf8");
13525
14590
  } catch (err) {
13526
14591
  if (err.code === "ENOENT") return;
13527
14592
  throw err;
@@ -13594,7 +14659,7 @@ function startPersistLoop(graph, outPath, opts = {}) {
13594
14659
  }
13595
14660
 
13596
14661
  // src/diff.ts
13597
- import { promises as fs26 } from "fs";
14662
+ import { promises as fs32 } from "fs";
13598
14663
  async function loadSnapshotForDiff(target) {
13599
14664
  if (/^https?:\/\//i.test(target)) {
13600
14665
  const res = await fetch(target);
@@ -13603,7 +14668,7 @@ async function loadSnapshotForDiff(target) {
13603
14668
  }
13604
14669
  return await res.json();
13605
14670
  }
13606
- const raw = await fs26.readFile(target, "utf8");
14671
+ const raw = await fs32.readFile(target, "utf8");
13607
14672
  return JSON.parse(raw);
13608
14673
  }
13609
14674
  function indexEntries(entries) {
@@ -13670,23 +14735,23 @@ function canonicalJson(value) {
13670
14735
  }
13671
14736
 
13672
14737
  // src/projects.ts
13673
- import path59 from "path";
14738
+ import path65 from "path";
13674
14739
  function pathsForProject(project, baseDir) {
13675
14740
  if (project === DEFAULT_PROJECT) {
13676
14741
  return {
13677
- snapshotPath: path59.join(baseDir, "graph.json"),
13678
- errorsPath: path59.join(baseDir, "errors.ndjson"),
13679
- staleEventsPath: path59.join(baseDir, "stale-events.ndjson"),
13680
- embeddingsCachePath: path59.join(baseDir, "embeddings.json"),
13681
- policyViolationsPath: path59.join(baseDir, "policy-violations.ndjson")
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")
13682
14747
  };
13683
14748
  }
13684
14749
  return {
13685
- snapshotPath: path59.join(baseDir, `${project}.json`),
13686
- errorsPath: path59.join(baseDir, `errors.${project}.ndjson`),
13687
- staleEventsPath: path59.join(baseDir, `stale-events.${project}.ndjson`),
13688
- embeddingsCachePath: path59.join(baseDir, `embeddings.${project}.json`),
13689
- policyViolationsPath: path59.join(baseDir, `policy-violations.${project}.ndjson`)
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`)
13690
14755
  };
13691
14756
  }
13692
14757
  var Projects = class {
@@ -13725,9 +14790,9 @@ function parseExtraProjects(raw) {
13725
14790
  }
13726
14791
 
13727
14792
  // src/registry.ts
13728
- import { promises as fs27 } from "fs";
14793
+ import { promises as fs33 } from "fs";
13729
14794
  import os2 from "os";
13730
- import path60 from "path";
14795
+ import path66 from "path";
13731
14796
  import {
13732
14797
  RegistryFileSchema
13733
14798
  } from "@neat.is/types";
@@ -13735,20 +14800,20 @@ var LOCK_TIMEOUT_MS = 5e3;
13735
14800
  var LOCK_RETRY_MS = 50;
13736
14801
  function neatHome() {
13737
14802
  const override = process.env.NEAT_HOME;
13738
- if (override && override.length > 0) return path60.resolve(override);
13739
- return path60.join(os2.homedir(), ".neat");
14803
+ if (override && override.length > 0) return path66.resolve(override);
14804
+ return path66.join(os2.homedir(), ".neat");
13740
14805
  }
13741
14806
  function registryPath() {
13742
- return path60.join(neatHome(), "projects.json");
14807
+ return path66.join(neatHome(), "projects.json");
13743
14808
  }
13744
14809
  function registryLockPath() {
13745
- return path60.join(neatHome(), "projects.json.lock");
14810
+ return path66.join(neatHome(), "projects.json.lock");
13746
14811
  }
13747
14812
  function daemonPidPath() {
13748
- return path60.join(neatHome(), "neatd.pid");
14813
+ return path66.join(neatHome(), "neatd.pid");
13749
14814
  }
13750
14815
  function daemonsDir() {
13751
- return path60.join(neatHome(), "daemons");
14816
+ return path66.join(neatHome(), "daemons");
13752
14817
  }
13753
14818
  function isFiniteInt(v) {
13754
14819
  return typeof v === "number" && Number.isFinite(v);
@@ -13781,7 +14846,7 @@ async function discoverDaemons(probe = defaultDiscoveryProbe) {
13781
14846
  const dir = daemonsDir();
13782
14847
  let names;
13783
14848
  try {
13784
- names = await fs27.readdir(dir);
14849
+ names = await fs33.readdir(dir);
13785
14850
  } catch (err) {
13786
14851
  if (err.code === "ENOENT") return [];
13787
14852
  throw err;
@@ -13789,10 +14854,10 @@ async function discoverDaemons(probe = defaultDiscoveryProbe) {
13789
14854
  const out = [];
13790
14855
  for (const name of names) {
13791
14856
  if (!name.endsWith(".json")) continue;
13792
- const file = path60.join(dir, name);
14857
+ const file = path66.join(dir, name);
13793
14858
  let raw;
13794
14859
  try {
13795
- raw = await fs27.readFile(file, "utf8");
14860
+ raw = await fs33.readFile(file, "utf8");
13796
14861
  } catch {
13797
14862
  continue;
13798
14863
  }
@@ -13805,7 +14870,7 @@ async function discoverDaemons(probe = defaultDiscoveryProbe) {
13805
14870
  return out;
13806
14871
  }
13807
14872
  async function removeDaemonRecord(source) {
13808
- await fs27.unlink(source).catch(() => {
14873
+ await fs33.unlink(source).catch(() => {
13809
14874
  });
13810
14875
  }
13811
14876
  async function listMachineProjects(probe = defaultDiscoveryProbe) {
@@ -13862,7 +14927,7 @@ function isPidAliveDefault(pid) {
13862
14927
  }
13863
14928
  async function readPidFile(file) {
13864
14929
  try {
13865
- const raw = await fs27.readFile(file, "utf8");
14930
+ const raw = await fs33.readFile(file, "utf8");
13866
14931
  const pid = Number.parseInt(raw.trim(), 10);
13867
14932
  return Number.isInteger(pid) && pid > 0 ? pid : void 0;
13868
14933
  } catch {
@@ -13910,32 +14975,32 @@ function lockHolderMessage(holder, lockPath, timeoutMs) {
13910
14975
  }
13911
14976
  }
13912
14977
  async function normalizeProjectPath(input) {
13913
- const resolved = path60.resolve(input);
14978
+ const resolved = path66.resolve(input);
13914
14979
  try {
13915
- return await fs27.realpath(resolved);
14980
+ return await fs33.realpath(resolved);
13916
14981
  } catch {
13917
14982
  return resolved;
13918
14983
  }
13919
14984
  }
13920
14985
  async function writeAtomically(target, contents) {
13921
- await fs27.mkdir(path60.dirname(target), { recursive: true });
14986
+ await fs33.mkdir(path66.dirname(target), { recursive: true });
13922
14987
  const tmp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
13923
- const fd = await fs27.open(tmp, "w");
14988
+ const fd = await fs33.open(tmp, "w");
13924
14989
  try {
13925
14990
  await fd.writeFile(contents, "utf8");
13926
14991
  await fd.sync();
13927
14992
  } finally {
13928
14993
  await fd.close();
13929
14994
  }
13930
- await fs27.rename(tmp, target);
14995
+ await fs33.rename(tmp, target);
13931
14996
  }
13932
14997
  async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaultLockHolderProbe) {
13933
14998
  const deadline = Date.now() + timeoutMs;
13934
- await fs27.mkdir(path60.dirname(lockPath), { recursive: true });
14999
+ await fs33.mkdir(path66.dirname(lockPath), { recursive: true });
13935
15000
  let probedHolder = false;
13936
15001
  while (true) {
13937
15002
  try {
13938
- const fd = await fs27.open(lockPath, "wx");
15003
+ const fd = await fs33.open(lockPath, "wx");
13939
15004
  try {
13940
15005
  await fd.writeFile(`${process.pid}
13941
15006
  `, "utf8");
@@ -13960,7 +15025,7 @@ async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaul
13960
15025
  }
13961
15026
  }
13962
15027
  async function releaseLock(lockPath) {
13963
- await fs27.unlink(lockPath).catch(() => {
15028
+ await fs33.unlink(lockPath).catch(() => {
13964
15029
  });
13965
15030
  }
13966
15031
  async function withLock(fn) {
@@ -13976,7 +15041,7 @@ async function readRegistry() {
13976
15041
  const file = registryPath();
13977
15042
  let raw;
13978
15043
  try {
13979
- raw = await fs27.readFile(file, "utf8");
15044
+ raw = await fs33.readFile(file, "utf8");
13980
15045
  } catch (err) {
13981
15046
  if (err.code === "ENOENT") {
13982
15047
  return { version: 1, projects: [] };
@@ -14081,7 +15146,7 @@ function pruneTtlMs() {
14081
15146
  }
14082
15147
  async function statPathStatus(p) {
14083
15148
  try {
14084
- const stat = await fs27.stat(p);
15149
+ const stat = await fs33.stat(p);
14085
15150
  return stat.isDirectory() ? "present" : "unknown";
14086
15151
  } catch (err) {
14087
15152
  return err.code === "ENOENT" ? "gone" : "unknown";
@@ -14126,14 +15191,14 @@ import cors from "@fastify/cors";
14126
15191
  import { DivergenceTypeSchema, PoliciesCheckBodySchema, PolicySeveritySchema } from "@neat.is/types";
14127
15192
 
14128
15193
  // src/extend/index.ts
14129
- import { promises as fs29 } from "fs";
14130
- import path62 from "path";
15194
+ import { promises as fs35 } from "fs";
15195
+ import path68 from "path";
14131
15196
  import os3 from "os";
14132
15197
  import { resolve as registryResolve, list as registryList } from "@neat.is/instrumentation-registry";
14133
15198
 
14134
15199
  // src/installers/package-manager.ts
14135
- import { promises as fs28 } from "fs";
14136
- import path61 from "path";
15200
+ import { promises as fs34 } from "fs";
15201
+ import path67 from "path";
14137
15202
  import { spawn } from "child_process";
14138
15203
  var LOCKFILE_PRIORITY = [
14139
15204
  { lockfile: "bun.lockb", pm: "bun", args: ["install", "--no-summary"] },
@@ -14148,29 +15213,29 @@ var LOCKFILE_PRIORITY = [
14148
15213
  var NPM_FALLBACK_ARGS = ["install", "--no-audit", "--no-fund", "--prefer-offline"];
14149
15214
  async function exists2(p) {
14150
15215
  try {
14151
- await fs28.access(p);
15216
+ await fs34.access(p);
14152
15217
  return true;
14153
15218
  } catch {
14154
15219
  return false;
14155
15220
  }
14156
15221
  }
14157
15222
  async function detectPackageManager(serviceDir) {
14158
- let dir = path61.resolve(serviceDir);
15223
+ let dir = path67.resolve(serviceDir);
14159
15224
  const stops = /* @__PURE__ */ new Set();
14160
15225
  for (let i = 0; i < 64; i++) {
14161
15226
  if (stops.has(dir)) break;
14162
15227
  stops.add(dir);
14163
15228
  for (const candidate of LOCKFILE_PRIORITY) {
14164
- const lockPath = path61.join(dir, candidate.lockfile);
15229
+ const lockPath = path67.join(dir, candidate.lockfile);
14165
15230
  if (await exists2(lockPath)) {
14166
15231
  return { pm: candidate.pm, cwd: dir, args: [...candidate.args] };
14167
15232
  }
14168
15233
  }
14169
- const parent = path61.dirname(dir);
15234
+ const parent = path67.dirname(dir);
14170
15235
  if (parent === dir) break;
14171
15236
  dir = parent;
14172
15237
  }
14173
- return { pm: "npm", cwd: path61.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
15238
+ return { pm: "npm", cwd: path67.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
14174
15239
  }
14175
15240
  async function runPackageManagerInstall(cmd) {
14176
15241
  return new Promise((resolve) => {
@@ -14212,15 +15277,15 @@ ${err.message}`
14212
15277
  // src/extend/index.ts
14213
15278
  async function fileExists2(p) {
14214
15279
  try {
14215
- await fs29.access(p);
15280
+ await fs35.access(p);
14216
15281
  return true;
14217
15282
  } catch {
14218
15283
  return false;
14219
15284
  }
14220
15285
  }
14221
15286
  async function readPackageJson(scanPath) {
14222
- const pkgPath = path62.join(scanPath, "package.json");
14223
- const raw = await fs29.readFile(pkgPath, "utf8");
15287
+ const pkgPath = path68.join(scanPath, "package.json");
15288
+ const raw = await fs35.readFile(pkgPath, "utf8");
14224
15289
  return JSON.parse(raw);
14225
15290
  }
14226
15291
  var HOOK_WALK_SKIP_DIRS = /* @__PURE__ */ new Set([
@@ -14234,15 +15299,15 @@ var HOOK_WALK_SKIP_DIRS = /* @__PURE__ */ new Set([
14234
15299
  async function findHookFiles(scanPath) {
14235
15300
  const found = [];
14236
15301
  const walk9 = async (dir) => {
14237
- const entries = await fs29.readdir(dir, { withFileTypes: true }).catch(() => []);
15302
+ const entries = await fs35.readdir(dir, { withFileTypes: true }).catch(() => []);
14238
15303
  for (const entry of entries) {
14239
15304
  if (entry.isDirectory()) {
14240
15305
  if (entry.name.startsWith(".") || HOOK_WALK_SKIP_DIRS.has(entry.name)) continue;
14241
- await walk9(path62.join(dir, entry.name));
15306
+ await walk9(path68.join(dir, entry.name));
14242
15307
  } else if (entry.isFile()) {
14243
15308
  if ((entry.name.startsWith("instrumentation") || entry.name.startsWith("otel-init")) && /\.(ts|js|cjs|mjs)$/.test(entry.name)) {
14244
- const rel = path62.relative(scanPath, path62.join(dir, entry.name));
14245
- found.push(rel.split(path62.sep).join("/"));
15309
+ const rel = path68.relative(scanPath, path68.join(dir, entry.name));
15310
+ found.push(rel.split(path68.sep).join("/"));
14246
15311
  }
14247
15312
  }
14248
15313
  }
@@ -14253,7 +15318,7 @@ async function findHookFiles(scanPath) {
14253
15318
  async function pickPrimaryHookFile(scanPath, hookFiles, snippet2) {
14254
15319
  let fallback = null;
14255
15320
  for (const file of hookFiles) {
14256
- const content = await fs29.readFile(path62.join(scanPath, file), "utf8");
15321
+ const content = await fs35.readFile(path68.join(scanPath, file), "utf8");
14257
15322
  const patched = splicedContent(content, snippet2);
14258
15323
  if (patched !== null) return { file, content, patched };
14259
15324
  if (fallback === null) fallback = { file, content };
@@ -14261,12 +15326,12 @@ async function pickPrimaryHookFile(scanPath, hookFiles, snippet2) {
14261
15326
  return { file: fallback.file, content: fallback.content, patched: null };
14262
15327
  }
14263
15328
  function extendLogPath() {
14264
- return process.env.NEAT_EXTEND_LOG ?? path62.join(os3.homedir(), ".neat", "extend-log.ndjson");
15329
+ return process.env.NEAT_EXTEND_LOG ?? path68.join(os3.homedir(), ".neat", "extend-log.ndjson");
14265
15330
  }
14266
15331
  async function appendExtendLog(entry) {
14267
15332
  const logPath = extendLogPath();
14268
- await fs29.mkdir(path62.dirname(logPath), { recursive: true });
14269
- await fs29.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");
14270
15335
  }
14271
15336
  function splicedContent(fileContent, snippet2) {
14272
15337
  if (fileContent.includes("__INSTRUMENTATION_BLOCK__")) {
@@ -14324,7 +15389,7 @@ function lookupInstrumentation(library, installedVersion) {
14324
15389
  }
14325
15390
  async function describeProjectInstrumentation(ctx) {
14326
15391
  const hookFiles = await findHookFiles(ctx.scanPath);
14327
- const envNeat = await fileExists2(path62.join(ctx.scanPath, ".env.neat"));
15392
+ const envNeat = await fileExists2(path68.join(ctx.scanPath, ".env.neat"));
14328
15393
  const registryInstrPackages = new Set(
14329
15394
  registryList().map((e) => e.instrumentation_package).filter((p) => !!p)
14330
15395
  );
@@ -14346,7 +15411,7 @@ async function applyExtension(ctx, args, options) {
14346
15411
  );
14347
15412
  }
14348
15413
  for (const file of hookFiles) {
14349
- const content = await fs29.readFile(path62.join(ctx.scanPath, file), "utf8");
15414
+ const content = await fs35.readFile(path68.join(ctx.scanPath, file), "utf8");
14350
15415
  if (content.includes(args.registration_snippet)) {
14351
15416
  return { library: args.library, filesTouched: [], depsAdded: [], installOutput: "", alreadyApplied: true };
14352
15417
  }
@@ -14358,18 +15423,18 @@ async function applyExtension(ctx, args, options) {
14358
15423
  );
14359
15424
  }
14360
15425
  const primaryFile = primary.file;
14361
- const primaryPath = path62.join(ctx.scanPath, primaryFile);
15426
+ const primaryPath = path68.join(ctx.scanPath, primaryFile);
14362
15427
  const filesTouched = [];
14363
15428
  const depsAdded = [];
14364
- const pkgPath = path62.join(ctx.scanPath, "package.json");
15429
+ const pkgPath = path68.join(ctx.scanPath, "package.json");
14365
15430
  const pkg = await readPackageJson(ctx.scanPath);
14366
15431
  if (!(pkg.dependencies ?? {})[args.instrumentation_package]) {
14367
15432
  pkg.dependencies = { ...pkg.dependencies ?? {}, [args.instrumentation_package]: args.version };
14368
- await fs29.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
15433
+ await fs35.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
14369
15434
  filesTouched.push("package.json");
14370
15435
  depsAdded.push(`${args.instrumentation_package}@${args.version}`);
14371
15436
  }
14372
- await fs29.writeFile(primaryPath, primary.patched, "utf8");
15437
+ await fs35.writeFile(primaryPath, primary.patched, "utf8");
14373
15438
  filesTouched.push(primaryFile);
14374
15439
  const cmd = await detectPackageManager(ctx.scanPath);
14375
15440
  const installer = options?.runInstall ?? runPackageManagerInstall;
@@ -14400,7 +15465,7 @@ async function dryRunExtension(ctx, args) {
14400
15465
  };
14401
15466
  }
14402
15467
  for (const file of hookFiles) {
14403
- const content = await fs29.readFile(path62.join(ctx.scanPath, file), "utf8");
15468
+ const content = await fs35.readFile(path68.join(ctx.scanPath, file), "utf8");
14404
15469
  if (content.includes(args.registration_snippet)) {
14405
15470
  return {
14406
15471
  library: args.library,
@@ -14435,28 +15500,28 @@ async function rollbackExtension(ctx, args) {
14435
15500
  if (!await fileExists2(logPath)) {
14436
15501
  return { undone: false, message: "no apply found for library" };
14437
15502
  }
14438
- const raw = await fs29.readFile(logPath, "utf8");
15503
+ const raw = await fs35.readFile(logPath, "utf8");
14439
15504
  const entries = raw.trim().split("\n").filter(Boolean).map((line) => JSON.parse(line));
14440
15505
  const match = [...entries].reverse().find((e) => e.project === ctx.project && e.library === args.library);
14441
15506
  if (!match) {
14442
15507
  return { undone: false, message: "no apply found for library" };
14443
15508
  }
14444
- const pkgPath = path62.join(ctx.scanPath, "package.json");
15509
+ const pkgPath = path68.join(ctx.scanPath, "package.json");
14445
15510
  if (await fileExists2(pkgPath)) {
14446
15511
  const pkg = await readPackageJson(ctx.scanPath);
14447
15512
  if (pkg.dependencies?.[match.instrumentation_package]) {
14448
15513
  const { [match.instrumentation_package]: _removed, ...rest } = pkg.dependencies;
14449
15514
  pkg.dependencies = rest;
14450
- await fs29.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
15515
+ await fs35.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
14451
15516
  }
14452
15517
  }
14453
15518
  const hookFiles = await findHookFiles(ctx.scanPath);
14454
15519
  for (const file of hookFiles) {
14455
- const filePath = path62.join(ctx.scanPath, file);
14456
- const content = await fs29.readFile(filePath, "utf8");
15520
+ const filePath = path68.join(ctx.scanPath, file);
15521
+ const content = await fs35.readFile(filePath, "utf8");
14457
15522
  if (content.includes(match.registration_snippet)) {
14458
15523
  const filtered = content.split("\n").filter((line) => !line.includes(match.registration_snippet)).join("\n");
14459
- await fs29.writeFile(filePath, filtered, "utf8");
15524
+ await fs35.writeFile(filePath, filtered, "utf8");
14460
15525
  break;
14461
15526
  }
14462
15527
  }
@@ -14567,8 +15632,8 @@ data: ${JSON.stringify(envelope.payload)}
14567
15632
 
14568
15633
  // src/connectors-config.ts
14569
15634
  import os4 from "os";
14570
- import path63 from "path";
14571
- import { promises as fs30 } from "fs";
15635
+ import path69 from "path";
15636
+ import { promises as fs36 } from "fs";
14572
15637
  var CONNECTORS_CONFIG_VERSION = 1;
14573
15638
  var EnvRefUnsetError = class extends Error {
14574
15639
  ref;
@@ -14582,17 +15647,17 @@ var EnvRefUnsetError = class extends Error {
14582
15647
  };
14583
15648
  function neatHome2() {
14584
15649
  const override = process.env.NEAT_HOME;
14585
- if (override && override.length > 0) return path63.resolve(override);
14586
- return path63.join(os4.homedir(), ".neat");
15650
+ if (override && override.length > 0) return path69.resolve(override);
15651
+ return path69.join(os4.homedir(), ".neat");
14587
15652
  }
14588
15653
  function connectorsConfigPath(home = neatHome2()) {
14589
- return path63.join(home, "connectors.json");
15654
+ return path69.join(home, "connectors.json");
14590
15655
  }
14591
15656
  var MODE_MASK_LOOSER_THAN_0600 = 63;
14592
15657
  async function warnIfModeLooserThan0600(file) {
14593
15658
  if (process.platform === "win32") return;
14594
15659
  try {
14595
- const stat = await fs30.stat(file);
15660
+ const stat = await fs36.stat(file);
14596
15661
  if ((stat.mode & MODE_MASK_LOOSER_THAN_0600) !== 0) {
14597
15662
  const mode = (stat.mode & 511).toString(8).padStart(3, "0");
14598
15663
  console.warn(
@@ -14606,7 +15671,7 @@ async function readConnectorsConfig(home = neatHome2()) {
14606
15671
  const file = connectorsConfigPath(home);
14607
15672
  let raw;
14608
15673
  try {
14609
- raw = await fs30.readFile(file, "utf8");
15674
+ raw = await fs36.readFile(file, "utf8");
14610
15675
  } catch (err) {
14611
15676
  if (err.code === "ENOENT") {
14612
15677
  return { version: CONNECTORS_CONFIG_VERSION, connectors: [] };
@@ -14717,7 +15782,7 @@ function connectorMatchesProject(entry, project) {
14717
15782
  var CONNECTORS_LOCK_TIMEOUT_MS = 5e3;
14718
15783
  var CONNECTORS_LOCK_RETRY_MS = 50;
14719
15784
  function connectorsConfigLockPath(home = neatHome2()) {
14720
- return path63.join(home, "connectors.json.lock");
15785
+ return path69.join(home, "connectors.json.lock");
14721
15786
  }
14722
15787
  function isEnvRef(value) {
14723
15788
  return value.length > 1 && value.startsWith("$");
@@ -14730,9 +15795,9 @@ function redactCredentialRef(ref) {
14730
15795
  return out;
14731
15796
  }
14732
15797
  async function writeConfigAtomically0600(file, contents) {
14733
- await fs30.mkdir(path63.dirname(file), { recursive: true });
15798
+ await fs36.mkdir(path69.dirname(file), { recursive: true });
14734
15799
  const tmp = `${file}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
14735
- const fd = await fs30.open(tmp, "w", 384);
15800
+ const fd = await fs36.open(tmp, "w", 384);
14736
15801
  try {
14737
15802
  await fd.writeFile(contents, "utf8");
14738
15803
  await fd.chmod(384);
@@ -14740,14 +15805,14 @@ async function writeConfigAtomically0600(file, contents) {
14740
15805
  } finally {
14741
15806
  await fd.close();
14742
15807
  }
14743
- await fs30.rename(tmp, file);
15808
+ await fs36.rename(tmp, file);
14744
15809
  }
14745
15810
  async function acquireConnectorsLock(lockPath, timeoutMs = CONNECTORS_LOCK_TIMEOUT_MS) {
14746
15811
  const deadline = Date.now() + timeoutMs;
14747
- await fs30.mkdir(path63.dirname(lockPath), { recursive: true });
15812
+ await fs36.mkdir(path69.dirname(lockPath), { recursive: true });
14748
15813
  for (; ; ) {
14749
15814
  try {
14750
- const fd = await fs30.open(lockPath, "wx");
15815
+ const fd = await fs36.open(lockPath, "wx");
14751
15816
  try {
14752
15817
  await fd.writeFile(`${process.pid}
14753
15818
  `, "utf8");
@@ -14767,7 +15832,7 @@ async function acquireConnectorsLock(lockPath, timeoutMs = CONNECTORS_LOCK_TIMEO
14767
15832
  }
14768
15833
  }
14769
15834
  async function releaseConnectorsLock(lockPath) {
14770
- await fs30.unlink(lockPath).catch(() => {
15835
+ await fs36.unlink(lockPath).catch(() => {
14771
15836
  });
14772
15837
  }
14773
15838
  async function withConnectorsLock(home, fn) {
@@ -14892,7 +15957,7 @@ function getConnectorStatus(id, now = Date.now(), thresholdMs = CONNECTOR_STALE_
14892
15957
  }
14893
15958
 
14894
15959
  // src/connectors/index.ts
14895
- import { NodeType as NodeType26, 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";
14896
15961
  var NO_ENV = "unknown";
14897
15962
  function staticCallSiteFor(graph, serviceName, targetNodeId) {
14898
15963
  if (!graph.hasNode(targetNodeId)) return void 0;
@@ -14911,7 +15976,7 @@ function staticCallSiteFor(graph, serviceName, targetNodeId) {
14911
15976
  function routeCallSiteFor(graph, targetNodeId) {
14912
15977
  if (!graph.hasNode(targetNodeId)) return void 0;
14913
15978
  const attrs = graph.getNodeAttributes(targetNodeId);
14914
- if (attrs.type !== NodeType26.RouteNode || !attrs.path) return void 0;
15979
+ if (attrs.type !== NodeType32.RouteNode || !attrs.path) return void 0;
14915
15980
  const site = { relPath: attrs.path };
14916
15981
  if (attrs.line !== void 0) site.line = attrs.line;
14917
15982
  return site;
@@ -15396,10 +16461,10 @@ var SUPABASE_RPC_TARGET_KIND = "supabase-rpc";
15396
16461
  // src/connectors/supabase/map.ts
15397
16462
  var REST_RPC_PATH_RE = /^\/rest\/v1\/rpc\/([^/?]+)/;
15398
16463
  var REST_TABLE_PATH_RE = /^\/rest\/v1\/([^/?]+)/;
15399
- function targetFromRestPath(path64) {
15400
- const rpcMatch = REST_RPC_PATH_RE.exec(path64);
16464
+ function targetFromRestPath(path70) {
16465
+ const rpcMatch = REST_RPC_PATH_RE.exec(path70);
15401
16466
  if (rpcMatch) return { targetKind: SUPABASE_RPC_TARGET_KIND, name: rpcMatch[1] };
15402
- const tableMatch = REST_TABLE_PATH_RE.exec(path64);
16467
+ const tableMatch = REST_TABLE_PATH_RE.exec(path70);
15403
16468
  if (tableMatch) return { targetKind: SUPABASE_TABLE_TARGET_KIND, name: tableMatch[1] };
15404
16469
  return null;
15405
16470
  }
@@ -15616,7 +16681,7 @@ function createSupabaseConnector(graph, config, deps = {}) {
15616
16681
  }
15617
16682
 
15618
16683
  // src/connectors/railway/index.ts
15619
- import { EdgeType as EdgeType27, NodeType as NodeType27, serviceId as serviceId8 } from "@neat.is/types";
16684
+ import { EdgeType as EdgeType27, NodeType as NodeType33, serviceId as serviceId14 } from "@neat.is/types";
15620
16685
 
15621
16686
  // src/connectors/railway/client.ts
15622
16687
  var DEFAULT_RAILWAY_API_URL = "https://backboard.railway.com/graphql/v2";
@@ -15766,7 +16831,7 @@ function buildRailwayRouteIndex(graph, serviceName) {
15766
16831
  const out = [];
15767
16832
  graph.forEachNode((_id, attrs) => {
15768
16833
  const node = attrs;
15769
- if (node.type !== NodeType27.RouteNode) return;
16834
+ if (node.type !== NodeType33.RouteNode) return;
15770
16835
  const route = attrs;
15771
16836
  if (route.service !== serviceName) return;
15772
16837
  out.push({
@@ -15875,7 +16940,7 @@ function createRailwayResolveTarget(config) {
15875
16940
  if (signal.targetKind === PEER_SERVICE_TARGET_KIND) {
15876
16941
  const peerName = config.serviceNameById[signal.targetName];
15877
16942
  if (!peerName) return null;
15878
- return { targetNodeId: serviceId8(peerName), serviceName, edgeType: EdgeType27.CONNECTS_TO };
16943
+ return { targetNodeId: serviceId14(peerName), serviceName, edgeType: EdgeType27.CONNECTS_TO };
15879
16944
  }
15880
16945
  return null;
15881
16946
  };
@@ -15994,9 +17059,9 @@ function parseFirebaseTargetName(targetName) {
15994
17059
  const secondSep = rest.indexOf(FIELD_SEP);
15995
17060
  if (secondSep === -1) return null;
15996
17061
  const method = rest.slice(0, secondSep);
15997
- const path64 = rest.slice(secondSep + 1);
15998
- if (!resourceName || !method || !path64) return null;
15999
- return { resourceName, method, path: path64 };
17062
+ const path70 = rest.slice(secondSep + 1);
17063
+ if (!resourceName || !method || !path70) return null;
17064
+ return { resourceName, method, path: path70 };
16000
17065
  }
16001
17066
  function resourceNameFor(type, labels) {
16002
17067
  if (!labels) return null;
@@ -16034,14 +17099,14 @@ function mapLogEntryToSignal(entry) {
16034
17099
  if (!req) return null;
16035
17100
  if (typeof req.requestMethod !== "string" || req.requestMethod.length === 0) return null;
16036
17101
  const method = req.requestMethod.toUpperCase();
16037
- const path64 = pathFromRequestUrl(req.requestUrl);
16038
- if (path64 === null) return null;
17102
+ const path70 = pathFromRequestUrl(req.requestUrl);
17103
+ if (path70 === null) return null;
16039
17104
  const timestamp = entry.timestamp;
16040
17105
  if (typeof timestamp !== "string" || timestamp.length === 0) return null;
16041
17106
  const isError = typeof req.status === "number" && req.status >= ERROR_STATUS_THRESHOLD2;
16042
17107
  return {
16043
17108
  targetKind: resourceType,
16044
- targetName: packFirebaseTargetName({ resourceName, method, path: path64 }),
17109
+ targetName: packFirebaseTargetName({ resourceName, method, path: path70 }),
16045
17110
  callCount: 1,
16046
17111
  errorCount: isError ? 1 : 0,
16047
17112
  lastObservedIso: timestamp
@@ -16057,7 +17122,7 @@ function mapLogEntriesToSignals(entries) {
16057
17122
  }
16058
17123
 
16059
17124
  // src/connectors/firebase/resolve.ts
16060
- import { NodeType as NodeType28, EdgeType as EdgeType28 } from "@neat.is/types";
17125
+ import { NodeType as NodeType34, EdgeType as EdgeType28 } from "@neat.is/types";
16061
17126
  function neatServiceNameFor(resourceType, resourceName, serviceMap) {
16062
17127
  switch (resourceType) {
16063
17128
  case "cloud_function":
@@ -16072,7 +17137,7 @@ function routeEntriesFor(graph, serviceName) {
16072
17137
  const entries = [];
16073
17138
  graph.forEachNode((_id, attrs) => {
16074
17139
  const node = attrs;
16075
- if (node.type !== NodeType28.RouteNode) return;
17140
+ if (node.type !== NodeType34.RouteNode) return;
16076
17141
  const route = attrs;
16077
17142
  if (route.service !== serviceName) return;
16078
17143
  entries.push({
@@ -16127,7 +17192,7 @@ function createFirebaseConnector(graph, serviceMap) {
16127
17192
  }
16128
17193
 
16129
17194
  // src/connectors/cloudflare/connector.ts
16130
- import { EdgeType as EdgeType29, NodeType as NodeType29, 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";
16131
17196
 
16132
17197
  // src/connectors/cloudflare/client.ts
16133
17198
  import { randomUUID } from "crypto";
@@ -16238,7 +17303,7 @@ function mapEventToSignal(event) {
16238
17303
  if (Number.isNaN(observedAt.getTime())) return null;
16239
17304
  const statusCode = metadata?.statusCode;
16240
17305
  const isError = typeof statusCode === "number" && statusCode >= ERROR_STATUS_THRESHOLD3;
16241
- const path64 = metadata?.trigger ? parsePathFromTrigger(metadata.trigger) : void 0;
17306
+ const path70 = metadata?.trigger ? parsePathFromTrigger(metadata.trigger) : void 0;
16242
17307
  return {
16243
17308
  targetKind: CLOUDFLARE_TARGET_KIND,
16244
17309
  targetName: scriptName,
@@ -16246,7 +17311,7 @@ function mapEventToSignal(event) {
16246
17311
  errorCount: isError ? 1 : 0,
16247
17312
  lastObservedIso: observedAt.toISOString(),
16248
17313
  method,
16249
- ...path64 ? { path: path64 } : {},
17314
+ ...path70 ? { path: path70 } : {},
16250
17315
  ...typeof statusCode === "number" ? { statusCode } : {},
16251
17316
  ...typeof metadata?.duration === "number" ? { duration: metadata.duration } : {}
16252
17317
  };
@@ -16286,19 +17351,19 @@ function findTaggedWorkerFileNode(graph, workerName) {
16286
17351
  graph.forEachNode((id, attrs) => {
16287
17352
  if (found) return;
16288
17353
  const a = attrs;
16289
- if (a.type === NodeType29.FileNode && a.platform === "cloudflare" && a.platformName === workerName) {
17354
+ if (a.type === NodeType35.FileNode && a.platform === "cloudflare" && a.platformName === workerName) {
16290
17355
  found = id;
16291
17356
  }
16292
17357
  });
16293
17358
  return found;
16294
17359
  }
16295
- function findMatchingRouteNode(graph, serviceName, method, path64) {
16296
- const normalizedPath = normalizePathTemplate(path64);
17360
+ function findMatchingRouteNode(graph, serviceName, method, path70) {
17361
+ const normalizedPath = normalizePathTemplate(path70);
16297
17362
  let found = null;
16298
17363
  graph.forEachNode((id, attrs) => {
16299
17364
  if (found) return;
16300
17365
  const a = attrs;
16301
- if (a.type !== NodeType29.RouteNode || a.service !== serviceName) return;
17366
+ if (a.type !== NodeType35.RouteNode || a.service !== serviceName) return;
16302
17367
  if (!a.pathTemplate || normalizePathTemplate(a.pathTemplate) !== normalizedPath) return;
16303
17368
  const routeMethod = (a.method ?? "").toUpperCase();
16304
17369
  if (routeMethod !== "ALL" && routeMethod !== method) return;
@@ -16310,10 +17375,10 @@ function createCloudflareResolveTarget(config, graph) {
16310
17375
  return (signal) => {
16311
17376
  if (signal.targetKind !== CLOUDFLARE_TARGET_KIND) return null;
16312
17377
  const scriptName = signal.targetName;
16313
- const { method, path: path64 } = signal;
17378
+ const { method, path: path70 } = signal;
16314
17379
  const resolveRouteGrain = (serviceName, wholeFileId) => {
16315
- if (!method || !path64) return wholeFileId;
16316
- return findMatchingRouteNode(graph, serviceName, method, path64) ?? wholeFileId;
17380
+ if (!method || !path70) return wholeFileId;
17381
+ return findMatchingRouteNode(graph, serviceName, method, path70) ?? wholeFileId;
16317
17382
  };
16318
17383
  const mapping = config.workers?.[scriptName];
16319
17384
  if (mapping) {
@@ -16643,9 +17708,9 @@ function parseCloudRunTargetName(targetName) {
16643
17708
  const secondSep = rest.indexOf(FIELD_SEP2);
16644
17709
  if (secondSep === -1) return null;
16645
17710
  const method = rest.slice(0, secondSep);
16646
- const path64 = rest.slice(secondSep + 1);
16647
- if (!serviceName || !method || !path64) return null;
16648
- return { serviceName, method, path: path64 };
17711
+ const path70 = rest.slice(secondSep + 1);
17712
+ if (!serviceName || !method || !path70) return null;
17713
+ return { serviceName, method, path: path70 };
16649
17714
  }
16650
17715
 
16651
17716
  // src/connectors/cloud-run/map.ts
@@ -16674,14 +17739,14 @@ function mapLogEntryToSignal2(entry) {
16674
17739
  if (!req) return null;
16675
17740
  if (typeof req.requestMethod !== "string" || req.requestMethod.length === 0) return null;
16676
17741
  const method = req.requestMethod.toUpperCase();
16677
- const path64 = pathFromRequestUrl2(req.requestUrl);
16678
- if (path64 === null) return null;
17742
+ const path70 = pathFromRequestUrl2(req.requestUrl);
17743
+ if (path70 === null) return null;
16679
17744
  const timestamp = entry.timestamp;
16680
17745
  if (typeof timestamp !== "string" || timestamp.length === 0) return null;
16681
17746
  const isError = typeof req.status === "number" && req.status >= ERROR_STATUS_THRESHOLD4;
16682
17747
  return {
16683
17748
  targetKind: CLOUD_RUN_TARGET_KIND,
16684
- targetName: packCloudRunTargetName({ serviceName, method, path: path64 }),
17749
+ targetName: packCloudRunTargetName({ serviceName, method, path: path70 }),
16685
17750
  callCount: 1,
16686
17751
  errorCount: isError ? 1 : 0,
16687
17752
  lastObservedIso: timestamp
@@ -16697,14 +17762,14 @@ function mapLogEntriesToSignals2(entries) {
16697
17762
  }
16698
17763
 
16699
17764
  // src/connectors/cloud-run/resolve.ts
16700
- import { EdgeType as EdgeType31, NodeType as NodeType30, infraId as infraId23 } from "@neat.is/types";
17765
+ import { EdgeType as EdgeType31, NodeType as NodeType36, infraId as infraId23 } from "@neat.is/types";
16701
17766
  var CLOUD_RUN_SERVICE_INFRA_KIND = "cloud-run-service";
16702
17767
  function findMatchingRouteNode2(graph, serviceName, method, normalizedPath) {
16703
17768
  let found = null;
16704
17769
  graph.forEachNode((_id, attrs) => {
16705
17770
  if (found) return;
16706
17771
  const node = attrs;
16707
- if (node.type !== NodeType30.RouteNode) return;
17772
+ if (node.type !== NodeType36.RouteNode) return;
16708
17773
  const route = attrs;
16709
17774
  if (route.service !== serviceName || !route.pathTemplate) return;
16710
17775
  if (normalizePathTemplate(route.pathTemplate) !== normalizedPath) return;
@@ -16719,14 +17784,14 @@ function createCloudRunResolveTarget(graph, config) {
16719
17784
  if (signal.targetKind !== CLOUD_RUN_TARGET_KIND) return null;
16720
17785
  const identity = parseCloudRunTargetName(signal.targetName);
16721
17786
  if (!identity) return null;
16722
- const { serviceName: gcpServiceName, method, path: path64 } = identity;
17787
+ const { serviceName: gcpServiceName, method, path: path70 } = identity;
16723
17788
  const mappedService = config.serviceMap?.[gcpServiceName];
16724
17789
  if (mappedService) {
16725
17790
  const routeNodeId = findMatchingRouteNode2(
16726
17791
  graph,
16727
17792
  mappedService,
16728
17793
  method,
16729
- normalizePathTemplate(path64)
17794
+ normalizePathTemplate(path70)
16730
17795
  );
16731
17796
  if (routeNodeId) {
16732
17797
  return { targetNodeId: routeNodeId, serviceName: mappedService, edgeType: EdgeType31.CALLS };
@@ -16775,7 +17840,7 @@ function createCloudRunConnector(graph, config = {}) {
16775
17840
  }
16776
17841
 
16777
17842
  // src/connectors/render/index.ts
16778
- import { EdgeType as EdgeType32, NodeType as NodeType31 } from "@neat.is/types";
17843
+ import { EdgeType as EdgeType32, NodeType as NodeType37 } from "@neat.is/types";
16779
17844
 
16780
17845
  // src/connectors/render/types.ts
16781
17846
  function readRenderToken(credentials) {
@@ -16851,7 +17916,7 @@ function buildRenderRouteIndex(graph, serviceName) {
16851
17916
  const out = [];
16852
17917
  graph.forEachNode((_id, attrs) => {
16853
17918
  const node = attrs;
16854
- if (node.type !== NodeType31.RouteNode) return;
17919
+ if (node.type !== NodeType37.RouteNode) return;
16855
17920
  const route = attrs;
16856
17921
  if (route.service !== serviceName) return;
16857
17922
  out.push({
@@ -17335,7 +18400,7 @@ function mapBuildsToSignals(builds, serviceName) {
17335
18400
  }
17336
18401
 
17337
18402
  // src/connectors/eas/resolve.ts
17338
- import { EdgeType as EdgeType34, NodeType as NodeType32, parseFileId as parseFileId3 } from "@neat.is/types";
18403
+ import { EdgeType as EdgeType34, NodeType as NodeType38, parseFileId as parseFileId3 } from "@neat.is/types";
17339
18404
  var NO_ENV2 = "unknown";
17340
18405
  var EAS_JSON_PHASES = /* @__PURE__ */ new Set(["READ_EAS_JSON"]);
17341
18406
  var APP_CONFIG_PHASES = /* @__PURE__ */ new Set([
@@ -17363,7 +18428,7 @@ function findConfigNode(graph, basenames, serviceName) {
17363
18428
  graph.forEachNode((id, attrs) => {
17364
18429
  if (scoped) return;
17365
18430
  const node = attrs;
17366
- if (node.type !== NodeType32.ConfigNode) return;
18431
+ if (node.type !== NodeType38.ConfigNode) return;
17367
18432
  if (typeof node.name !== "string" || !basenames.includes(node.name)) return;
17368
18433
  if (anyMatch === null) anyMatch = id;
17369
18434
  if (configNodeService(graph, id) === serviceName) scoped = id;
@@ -18975,4 +20040,4 @@ export {
18975
20040
  deprovisionConnector,
18976
20041
  buildApi
18977
20042
  };
18978
- //# sourceMappingURL=chunk-ZGZYZ33B.js.map
20043
+ //# sourceMappingURL=chunk-RDLDSIEY.js.map