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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/server.cjs CHANGED
@@ -59,8 +59,8 @@ function mountBearerAuth(app, opts) {
59
59
  ]);
60
60
  const publicRead = opts.publicRead === true;
61
61
  app.addHook("preHandler", (req, reply, done) => {
62
- const path71 = (req.url.split("?")[0] ?? "").replace(/\/+$/, "");
63
- if (exactUnauthPaths.has(path71) || PROJECT_SCOPED_UNAUTH_PATTERN.test(path71)) {
62
+ const path74 = (req.url.split("?")[0] ?? "").replace(/\/+$/, "");
63
+ if (exactUnauthPaths.has(path74) || PROJECT_SCOPED_UNAUTH_PATTERN.test(path74)) {
64
64
  done();
65
65
  return;
66
66
  }
@@ -192,8 +192,8 @@ function reshapeGrpcRequest(req) {
192
192
  };
193
193
  }
194
194
  function resolveProtoRoot() {
195
- const here = import_node_path50.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
196
- return import_node_path50.default.resolve(here, "..", "proto");
195
+ const here = import_node_path53.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
196
+ return import_node_path53.default.resolve(here, "..", "proto");
197
197
  }
198
198
  function loadTraceService() {
199
199
  const protoRoot = resolveProtoRoot();
@@ -261,13 +261,13 @@ async function startOtelGrpcReceiver(opts) {
261
261
  })
262
262
  };
263
263
  }
264
- var import_node_url, import_node_path50, import_node_crypto2, grpc, protoLoader;
264
+ var import_node_url, import_node_path53, import_node_crypto2, grpc, protoLoader;
265
265
  var init_otel_grpc = __esm({
266
266
  "src/otel-grpc.ts"() {
267
267
  "use strict";
268
268
  init_cjs_shims();
269
269
  import_node_url = require("url");
270
- import_node_path50 = __toESM(require("path"), 1);
270
+ import_node_path53 = __toESM(require("path"), 1);
271
271
  import_node_crypto2 = require("crypto");
272
272
  grpc = __toESM(require("@grpc/grpc-js"), 1);
273
273
  protoLoader = __toESM(require("@grpc/proto-loader"), 1);
@@ -413,8 +413,8 @@ function websocketChannelPathOf(attrs) {
413
413
  const v = attrs[key];
414
414
  if (typeof v === "string" && v.length > 0) {
415
415
  const q = v.indexOf("?");
416
- const path71 = q === -1 ? v : v.slice(0, q);
417
- if (path71.length > 0) return path71;
416
+ const path74 = q === -1 ? v : v.slice(0, q);
417
+ if (path74.length > 0) return path74;
418
418
  }
419
419
  }
420
420
  return void 0;
@@ -476,10 +476,10 @@ function parseOtlpRequest(body) {
476
476
  return out;
477
477
  }
478
478
  function loadProtoRoot() {
479
- const here = import_node_path51.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
480
- const protoRoot = import_node_path51.default.resolve(here, "..", "proto");
479
+ const here = import_node_path54.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
480
+ const protoRoot = import_node_path54.default.resolve(here, "..", "proto");
481
481
  const root = new import_protobufjs.default.Root();
482
- root.resolvePath = (_origin, target) => import_node_path51.default.resolve(protoRoot, target);
482
+ root.resolvePath = (_origin, target) => import_node_path54.default.resolve(protoRoot, target);
483
483
  root.loadSync(
484
484
  "opentelemetry/proto/collector/trace/v1/trace_service.proto",
485
485
  { keepCase: true }
@@ -710,12 +710,12 @@ async function buildOtelReceiver(opts) {
710
710
  };
711
711
  return decorated;
712
712
  }
713
- var import_node_path51, import_node_url2, import_fastify, import_protobufjs, ENV_ATTR_CANONICAL, ENV_ATTR_COMPAT, ENV_FALLBACK, exportTraceServiceRequestType, exportTraceServiceResponseType, cachedProtobufResponseBody;
713
+ var import_node_path54, import_node_url2, import_fastify, import_protobufjs, ENV_ATTR_CANONICAL, ENV_ATTR_COMPAT, ENV_FALLBACK, exportTraceServiceRequestType, exportTraceServiceResponseType, cachedProtobufResponseBody;
714
714
  var init_otel = __esm({
715
715
  "src/otel.ts"() {
716
716
  "use strict";
717
717
  init_cjs_shims();
718
- import_node_path51 = __toESM(require("path"), 1);
718
+ import_node_path54 = __toESM(require("path"), 1);
719
719
  import_node_url2 = require("url");
720
720
  import_fastify = __toESM(require("fastify"), 1);
721
721
  import_protobufjs = __toESM(require("protobufjs"), 1);
@@ -731,7 +731,7 @@ var init_otel = __esm({
731
731
 
732
732
  // src/server.ts
733
733
  init_cjs_shims();
734
- var import_node_path70 = __toESM(require("path"), 1);
734
+ var import_node_path73 = __toESM(require("path"), 1);
735
735
 
736
736
  // src/graph.ts
737
737
  init_cjs_shims();
@@ -755,7 +755,7 @@ function getGraph(project = DEFAULT_PROJECT) {
755
755
  init_cjs_shims();
756
756
  var import_fastify2 = __toESM(require("fastify"), 1);
757
757
  var import_cors = __toESM(require("@fastify/cors"), 1);
758
- var import_types88 = require("@neat.is/types");
758
+ var import_types91 = require("@neat.is/types");
759
759
 
760
760
  // src/extend/index.ts
761
761
  init_cjs_shims();
@@ -2015,7 +2015,7 @@ var import_node_fs5 = require("fs");
2015
2015
  var import_node_path5 = __toESM(require("path"), 1);
2016
2016
  var import_yaml = require("yaml");
2017
2017
  var import_types3 = require("@neat.is/types");
2018
- var SERVICE_FILE_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".mjs", ".cjs", ".ts", ".tsx", ".py", ".go", ".rb", ".php", ".cs", ".java"]);
2018
+ 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++"]);
2019
2019
  var CONFIG_FILE_EXTENSIONS = /* @__PURE__ */ new Set([".yaml", ".yml"]);
2020
2020
  var JSON_CONFIG_FILENAMES = /* @__PURE__ */ new Set(["app.json", "app.config.json", "eas.json"]);
2021
2021
  var IGNORED_DIRS = /* @__PURE__ */ new Set([
@@ -2284,7 +2284,8 @@ function buildServiceHostIndex(services) {
2284
2284
  }
2285
2285
  return { knownHosts, hostToNodeId };
2286
2286
  }
2287
- async function walkSourceFiles(dir) {
2287
+ async function walkSourceFiles(dir, excludeDirs = []) {
2288
+ const excluded = new Set(excludeDirs.map((d) => import_node_path7.default.resolve(d)));
2288
2289
  const out = [];
2289
2290
  async function walk9(current) {
2290
2291
  const entries = await import_node_fs7.promises.readdir(current, { withFileTypes: true }).catch(() => []);
@@ -2292,6 +2293,7 @@ async function walkSourceFiles(dir) {
2292
2293
  const full = import_node_path7.default.join(current, entry.name);
2293
2294
  if (entry.isDirectory()) {
2294
2295
  if (IGNORED_DIRS.has(entry.name)) continue;
2296
+ if (excluded.has(import_node_path7.default.resolve(full))) continue;
2295
2297
  if (await isPythonVenvDir(full)) continue;
2296
2298
  await walk9(full);
2297
2299
  } else if (entry.isFile() && SERVICE_FILE_EXTENSIONS.has(import_node_path7.default.extname(entry.name)) && // Skip NEAT's own generated `otel-init.*` bootstrap — extracting it
@@ -2304,8 +2306,8 @@ async function walkSourceFiles(dir) {
2304
2306
  await walk9(dir);
2305
2307
  return out;
2306
2308
  }
2307
- async function loadSourceFiles(dir) {
2308
- const paths = await walkSourceFiles(dir);
2309
+ async function loadSourceFiles(dir, excludeDirs = []) {
2310
+ const paths = await walkSourceFiles(dir, excludeDirs);
2309
2311
  const out = [];
2310
2312
  for (const p of paths) {
2311
2313
  try {
@@ -2342,6 +2344,19 @@ function languageForPath(relPath) {
2342
2344
  return "csharp";
2343
2345
  case ".java":
2344
2346
  return "java";
2347
+ case ".kt":
2348
+ return "kotlin";
2349
+ case ".rs":
2350
+ return "rust";
2351
+ case ".cpp":
2352
+ case ".cc":
2353
+ case ".cxx":
2354
+ case ".c++":
2355
+ case ".hpp":
2356
+ case ".hh":
2357
+ case ".hxx":
2358
+ case ".h++":
2359
+ return "cpp";
2345
2360
  case ".ts":
2346
2361
  case ".tsx":
2347
2362
  return "typescript";
@@ -2675,6 +2690,7 @@ async function resolveGoImport(specifier, modulePath, serviceDir) {
2675
2690
  function emitImportEdge(graph, serviceName, importerFileId, importerRelPath, importeeRelPath, line, snippet2) {
2676
2691
  const importeeFileId = (0, import_types5.fileId)(serviceName, importeeRelPath);
2677
2692
  if (!graph.hasNode(importeeFileId)) return 0;
2693
+ if (importerFileId === importeeFileId) return 0;
2678
2694
  const edgeId = (0, import_types5.extractedEdgeId)(importerFileId, importeeFileId, import_types5.EdgeType.IMPORTS);
2679
2695
  if (graph.hasEdge(edgeId)) return 0;
2680
2696
  const edge = {
@@ -2696,7 +2712,7 @@ async function addImports(graph, services) {
2696
2712
  let edgesAdded = 0;
2697
2713
  for (const service of services) {
2698
2714
  const tsPaths = await loadTsPathConfig(service.dir);
2699
- const files = await loadSourceFiles(service.dir);
2715
+ const files = await loadSourceFiles(service.dir, service.excludeDirs);
2700
2716
  for (const file of files) {
2701
2717
  if (isTestPath(file.path)) continue;
2702
2718
  const relFile = toPosix(import_node_path8.default.relative(service.dir, file.path));
@@ -2878,8 +2894,8 @@ function chiRoutesFromSource(source, parser) {
2878
2894
  chiWalk(tree.rootNode, "", out);
2879
2895
  return out;
2880
2896
  }
2881
- function stripChiRegex(path71) {
2882
- return path71.replace(/\{([^{}:]+):[^{}]*\}/g, "{$1}");
2897
+ function stripChiRegex(path74) {
2898
+ return path74.replace(/\{([^{}:]+):[^{}]*\}/g, "{$1}");
2883
2899
  }
2884
2900
  function chiWalk(node, prefix, out) {
2885
2901
  for (let i = 0; i < node.namedChildCount; i++) {
@@ -3551,9 +3567,9 @@ function rubyRocketRoute(args) {
3551
3567
  if (!pair || pair.type !== "pair") continue;
3552
3568
  const k = pair.childForFieldName("key");
3553
3569
  if (k?.type !== "string") continue;
3554
- const path71 = rubyLiteral(k);
3555
- if (path71 === null) continue;
3556
- return { path: path71, target: rubyLiteral(pair.childForFieldName("value")) };
3570
+ const path74 = rubyLiteral(k);
3571
+ if (path74 === null) continue;
3572
+ return { path: path74, target: rubyLiteral(pair.childForFieldName("value")) };
3557
3573
  }
3558
3574
  return null;
3559
3575
  }
@@ -4238,7 +4254,7 @@ async function addRoutes(graph, services) {
4238
4254
  const hasLaravel = deps["laravel/framework"] !== void 0;
4239
4255
  if (!hasExpress && !hasFastify && !hasHono && !hasNext && !hasNestjs && !hasFastapi && !hasFlask && !hasDjango && !hasGin && !hasEcho && !hasFiber && !hasChi && !isGoService && !hasRails && !hasLaravel)
4240
4256
  continue;
4241
- const files = await loadSourceFiles(service.dir);
4257
+ const files = await loadSourceFiles(service.dir, service.excludeDirs);
4242
4258
  const mountPrefixes = hasExpress ? await expressMountPrefixes(files, service.dir, await loadTsPathConfig(service.dir)) : /* @__PURE__ */ new Map();
4243
4259
  for (const file of files) {
4244
4260
  if (isTestPath(file.path)) continue;
@@ -5596,29 +5612,29 @@ function promoteFrontierNodes(graph, opts = {}) {
5596
5612
  toPromote.push({ frontierId: id, serviceId: target });
5597
5613
  });
5598
5614
  let promoted = 0;
5599
- for (const { frontierId: frontierId2, serviceId: serviceId12 } of toPromote) {
5615
+ for (const { frontierId: frontierId2, serviceId: serviceId15 } of toPromote) {
5600
5616
  if (opts.policies && opts.policies.length > 0 && opts.policyCtx) {
5601
5617
  const gate = canPromoteFrontier(graph, frontierId2, opts.policies, opts.policyCtx);
5602
5618
  if (!gate.allowed) {
5603
5619
  continue;
5604
5620
  }
5605
5621
  }
5606
- rewireFrontierEdges(graph, frontierId2, serviceId12);
5622
+ rewireFrontierEdges(graph, frontierId2, serviceId15);
5607
5623
  graph.dropNode(frontierId2);
5608
5624
  promoted++;
5609
5625
  }
5610
5626
  return promoted;
5611
5627
  }
5612
- function rewireFrontierEdges(graph, frontierId2, serviceId12) {
5628
+ function rewireFrontierEdges(graph, frontierId2, serviceId15) {
5613
5629
  const inbound = [...graph.inboundEdges(frontierId2)];
5614
5630
  const outbound = [...graph.outboundEdges(frontierId2)];
5615
5631
  for (const edgeId of inbound) {
5616
5632
  const edge = graph.getEdgeAttributes(edgeId);
5617
- rebuildEdge(graph, edge, edge.source, serviceId12, edgeId);
5633
+ rebuildEdge(graph, edge, edge.source, serviceId15, edgeId);
5618
5634
  }
5619
5635
  for (const edgeId of outbound) {
5620
5636
  const edge = graph.getEdgeAttributes(edgeId);
5621
- rebuildEdge(graph, edge, serviceId12, edge.target, edgeId);
5637
+ rebuildEdge(graph, edge, serviceId15, edge.target, edgeId);
5622
5638
  }
5623
5639
  }
5624
5640
  function rebuildEdge(graph, edge, newSource, newTarget, oldEdgeId) {
@@ -5937,19 +5953,19 @@ function confidenceFromMix(edges, now = Date.now()) {
5937
5953
  function longestIncomingWalk(graph, start, maxDepth) {
5938
5954
  let best = { path: [start], edges: [] };
5939
5955
  const visited = /* @__PURE__ */ new Set([start]);
5940
- function step(node, path71, edges) {
5941
- if (path71.length > best.path.length) {
5942
- best = { path: [...path71], edges: [...edges] };
5956
+ function step(node, path74, edges) {
5957
+ if (path74.length > best.path.length) {
5958
+ best = { path: [...path74], edges: [...edges] };
5943
5959
  }
5944
- if (path71.length - 1 >= maxDepth) return;
5960
+ if (path74.length - 1 >= maxDepth) return;
5945
5961
  const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
5946
5962
  for (const [srcId, edge] of incoming) {
5947
5963
  if (visited.has(srcId)) continue;
5948
5964
  visited.add(srcId);
5949
- path71.push(srcId);
5965
+ path74.push(srcId);
5950
5966
  edges.push(edge);
5951
- step(srcId, path71, edges);
5952
- path71.pop();
5967
+ step(srcId, path74, edges);
5968
+ path74.pop();
5953
5969
  edges.pop();
5954
5970
  visited.delete(srcId);
5955
5971
  }
@@ -5964,7 +5980,7 @@ function databaseRootCauseShape(graph, origin, walk9) {
5964
5980
  for (const id of walk9.path) {
5965
5981
  const owner = resolveOwningService(graph, id);
5966
5982
  if (!owner) continue;
5967
- const { id: serviceId12, svc } = owner;
5983
+ const { id: serviceId15, svc } = owner;
5968
5984
  const deps = svc.dependencies ?? {};
5969
5985
  for (const pair of candidatePairs) {
5970
5986
  const declared = deps[pair.driver];
@@ -5977,7 +5993,7 @@ function databaseRootCauseShape(graph, origin, walk9) {
5977
5993
  );
5978
5994
  if (!result.compatible) {
5979
5995
  return {
5980
- rootCauseNode: serviceId12,
5996
+ rootCauseNode: serviceId15,
5981
5997
  rootCauseReason: result.reason ?? "incompatible driver",
5982
5998
  ...result.minDriverVersion ? {
5983
5999
  fixRecommendation: `Upgrade ${svc.name} ${pair.driver} driver to >= ${result.minDriverVersion}`
@@ -5992,7 +6008,7 @@ function serviceRootCauseShape(graph, _origin, walk9) {
5992
6008
  for (const id of walk9.path) {
5993
6009
  const owner = resolveOwningService(graph, id);
5994
6010
  if (!owner) continue;
5995
- const { id: serviceId12, svc } = owner;
6011
+ const { id: serviceId15, svc } = owner;
5996
6012
  const deps = svc.dependencies ?? {};
5997
6013
  const serviceNodeEngine = svc.nodeEngine;
5998
6014
  for (const constraint of nodeEngineConstraints()) {
@@ -6001,7 +6017,7 @@ function serviceRootCauseShape(graph, _origin, walk9) {
6001
6017
  const result = checkNodeEngineConstraint(constraint, declared, serviceNodeEngine);
6002
6018
  if (!result.compatible && result.reason) {
6003
6019
  return {
6004
- rootCauseNode: serviceId12,
6020
+ rootCauseNode: serviceId15,
6005
6021
  rootCauseReason: result.reason,
6006
6022
  ...result.requiredNodeVersion ? {
6007
6023
  fixRecommendation: `Bump ${svc.name}'s engines.node to >= ${result.requiredNodeVersion}`
@@ -6016,7 +6032,7 @@ function serviceRootCauseShape(graph, _origin, walk9) {
6016
6032
  const result = checkPackageConflict(conflict, declared, requiredDeclared);
6017
6033
  if (!result.compatible && result.reason) {
6018
6034
  return {
6019
- rootCauseNode: serviceId12,
6035
+ rootCauseNode: serviceId15,
6020
6036
  rootCauseReason: result.reason,
6021
6037
  fixRecommendation: `Upgrade ${svc.name}'s ${conflict.requires.name} to >= ${conflict.requires.minVersion}`
6022
6038
  };
@@ -6117,9 +6133,9 @@ function rootCauseFromIncidents(nodeId, incidents, errorEvent) {
6117
6133
  function isFailingCallEdge(e) {
6118
6134
  return e.type === import_types8.EdgeType.CALLS && (e.signal?.errorCount ?? 0) > 0;
6119
6135
  }
6120
- function callSourcesForService(graph, serviceId12) {
6121
- const ids = [serviceId12];
6122
- for (const edgeId of graph.outboundEdges(serviceId12)) {
6136
+ function callSourcesForService(graph, serviceId15) {
6137
+ const ids = [serviceId15];
6138
+ for (const edgeId of graph.outboundEdges(serviceId15)) {
6123
6139
  const e = graph.getEdgeAttributes(edgeId);
6124
6140
  if (e.type !== import_types8.EdgeType.CONTAINS) continue;
6125
6141
  const tgt = graph.getNodeAttributes(e.target);
@@ -6143,9 +6159,9 @@ function failingCallDominates(e, id, curEdge, curId) {
6143
6159
  }
6144
6160
  return id < curId;
6145
6161
  }
6146
- function dominantFailingCall(graph, serviceId12, visited) {
6162
+ function dominantFailingCall(graph, serviceId15, visited) {
6147
6163
  let best = null;
6148
- for (const src of callSourcesForService(graph, serviceId12)) {
6164
+ for (const src of callSourcesForService(graph, serviceId15)) {
6149
6165
  for (const edgeId of graph.outboundEdges(src)) {
6150
6166
  const e = graph.getEdgeAttributes(edgeId);
6151
6167
  if (!isFailingCallEdge(e)) continue;
@@ -6160,26 +6176,26 @@ function dominantFailingCall(graph, serviceId12, visited) {
6160
6176
  return best;
6161
6177
  }
6162
6178
  function followFailingCallChain(graph, originServiceId, maxDepth) {
6163
- const path71 = [originServiceId];
6179
+ const path74 = [originServiceId];
6164
6180
  const edges = [];
6165
6181
  const visited = /* @__PURE__ */ new Set([originServiceId]);
6166
6182
  let current = originServiceId;
6167
6183
  for (let depth = 0; depth < maxDepth; depth++) {
6168
6184
  const hop = dominantFailingCall(graph, current, visited);
6169
6185
  if (!hop) break;
6170
- path71.push(hop.nextService);
6186
+ path74.push(hop.nextService);
6171
6187
  edges.push(hop.edge);
6172
6188
  visited.add(hop.nextService);
6173
6189
  current = hop.nextService;
6174
6190
  }
6175
6191
  if (edges.length === 0) return null;
6176
- return { path: path71, edges, culprit: current };
6192
+ return { path: path74, edges, culprit: current };
6177
6193
  }
6178
6194
  function crossServiceRootCause(graph, originId, incidents, errorEvent) {
6179
6195
  const chain = followFailingCallChain(graph, originId, ROOT_CAUSE_MAX_DEPTH);
6180
6196
  if (!chain) return null;
6181
6197
  const culprit = chain.culprit;
6182
- const path71 = [...chain.path];
6198
+ const path74 = [...chain.path];
6183
6199
  const edgeProvenances = chain.edges.map((e) => e.provenance);
6184
6200
  const baseConfidence = confidenceFromMix(chain.edges);
6185
6201
  const confidence = Math.max(0, Math.min(1, baseConfidence * INCIDENT_ROOT_CAUSE_CONFIDENCE));
@@ -6187,14 +6203,14 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
6187
6203
  if (loc) {
6188
6204
  let rootCauseNode = culprit;
6189
6205
  if (loc.fileNode) {
6190
- path71.push(loc.fileNode);
6206
+ path74.push(loc.fileNode);
6191
6207
  edgeProvenances.push(import_types8.Provenance.OBSERVED);
6192
6208
  rootCauseNode = loc.fileNode;
6193
6209
  }
6194
6210
  return import_types8.RootCauseResultSchema.parse({
6195
6211
  rootCauseNode,
6196
6212
  rootCauseReason: loc.rootCauseReason,
6197
- traversalPath: path71,
6213
+ traversalPath: path74,
6198
6214
  edgeProvenances,
6199
6215
  confidence,
6200
6216
  ...loc.fixRecommendation ? { fixRecommendation: loc.fixRecommendation } : {}
@@ -6206,7 +6222,7 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
6206
6222
  return import_types8.RootCauseResultSchema.parse({
6207
6223
  rootCauseNode: culprit,
6208
6224
  rootCauseReason: `${culpritName} is failing downstream calls (${errs} observed error${errs === 1 ? "" : "s"})`,
6209
- traversalPath: path71,
6225
+ traversalPath: path74,
6210
6226
  edgeProvenances,
6211
6227
  confidence,
6212
6228
  fixRecommendation: `Inspect ${culpritName}'s failing handler`
@@ -6642,10 +6658,10 @@ function enrichWithNavigation(graph, errorNodeId, legacy, incidents, now) {
6642
6658
  let traversalPath = legacy.traversalPath;
6643
6659
  let edgeProvenances = legacy.edgeProvenances;
6644
6660
  if (top.node !== seedNode) {
6645
- const path71 = findPath(graph, errorNodeId, top.node, "up", ROOT_CAUSE_MAX_DEPTH);
6646
- if (path71) {
6647
- traversalPath = path71.nodes;
6648
- edgeProvenances = path71.edges.map((e) => e.provenance);
6661
+ const path74 = findPath(graph, errorNodeId, top.node, "up", ROOT_CAUSE_MAX_DEPTH);
6662
+ if (path74) {
6663
+ traversalPath = path74.nodes;
6664
+ edgeProvenances = path74.edges.map((e) => e.provenance);
6649
6665
  } else {
6650
6666
  traversalPath = [errorNodeId, top.node];
6651
6667
  edgeProvenances = [top.provenance ?? import_types8.Provenance.OBSERVED];
@@ -7057,11 +7073,11 @@ init_cjs_shims();
7057
7073
 
7058
7074
  // src/extract/services.ts
7059
7075
  init_cjs_shims();
7060
- var import_node_fs17 = require("fs");
7061
- var import_node_path19 = __toESM(require("path"), 1);
7076
+ var import_node_fs20 = require("fs");
7077
+ var import_node_path22 = __toESM(require("path"), 1);
7062
7078
  var import_ignore = __toESM(require("ignore"), 1);
7063
7079
  var import_minimatch2 = require("minimatch");
7064
- var import_types16 = require("@neat.is/types");
7080
+ var import_types19 = require("@neat.is/types");
7065
7081
 
7066
7082
  // src/extract/python.ts
7067
7083
  init_cjs_shims();
@@ -7386,11 +7402,158 @@ async function discoverJavaService(scanPath, dir) {
7386
7402
  return { pkg, dir, node };
7387
7403
  }
7388
7404
 
7389
- // src/extract/dockerfile-service.ts
7405
+ // src/extract/kotlin.ts
7390
7406
  init_cjs_shims();
7391
7407
  var import_node_fs15 = require("fs");
7392
7408
  var import_node_path17 = __toESM(require("path"), 1);
7393
7409
  var import_types15 = require("@neat.is/types");
7410
+ var KOTLIN_SOURCE_EXT = ".kt";
7411
+ var JAVA_SOURCE_EXT = ".java";
7412
+ var SOURCE_PROBE_MAX_DEPTH = 8;
7413
+ async function dominantJvmSourceLanguage(dir) {
7414
+ let kt = 0;
7415
+ let java = 0;
7416
+ async function recurse(current, depth) {
7417
+ if (depth > SOURCE_PROBE_MAX_DEPTH) return;
7418
+ const entries = await import_node_fs15.promises.readdir(current, { withFileTypes: true }).catch(() => []);
7419
+ for (const entry of entries) {
7420
+ if (entry.isDirectory()) {
7421
+ if (IGNORED_DIRS.has(entry.name)) continue;
7422
+ await recurse(import_node_path17.default.join(current, entry.name), depth + 1);
7423
+ continue;
7424
+ }
7425
+ if (!entry.isFile()) continue;
7426
+ const ext = import_node_path17.default.extname(entry.name).toLowerCase();
7427
+ if (ext === KOTLIN_SOURCE_EXT) kt++;
7428
+ else if (ext === JAVA_SOURCE_EXT) java++;
7429
+ }
7430
+ }
7431
+ await recurse(dir, 0);
7432
+ if (kt > 0 && kt >= java) return "kotlin";
7433
+ if (java > 0) return "java";
7434
+ return void 0;
7435
+ }
7436
+ async function readJvmManifest(dir) {
7437
+ const pomPath = import_node_path17.default.join(dir, JAVA_MANIFESTS[0]);
7438
+ const gradlePath = import_node_path17.default.join(dir, JAVA_MANIFESTS[1]);
7439
+ const gradleKtsPath = import_node_path17.default.join(dir, JAVA_MANIFESTS[2]);
7440
+ if (await exists2(pomPath)) {
7441
+ return parsePomXml(await import_node_fs15.promises.readFile(pomPath, "utf8").catch(() => ""));
7442
+ }
7443
+ if (await exists2(gradlePath)) {
7444
+ return parseGradle(await import_node_fs15.promises.readFile(gradlePath, "utf8").catch(() => ""));
7445
+ }
7446
+ if (await exists2(gradleKtsPath)) {
7447
+ return parseGradle(await import_node_fs15.promises.readFile(gradleKtsPath, "utf8").catch(() => ""));
7448
+ }
7449
+ return null;
7450
+ }
7451
+ async function discoverKotlinService(scanPath, dir) {
7452
+ if (!await hasJavaManifest(dir)) return null;
7453
+ if (await dominantJvmSourceLanguage(dir) !== "kotlin") return null;
7454
+ const manifest = await readJvmManifest(dir);
7455
+ if (!manifest) return null;
7456
+ const name = manifest.name?.trim() || import_node_path17.default.basename(dir);
7457
+ const dependencies = manifest.dependencies;
7458
+ const pkg = { name, dependencies };
7459
+ const node = {
7460
+ id: (0, import_types15.serviceId)(name),
7461
+ type: import_types15.NodeType.ServiceNode,
7462
+ name,
7463
+ language: "kotlin",
7464
+ dependencies,
7465
+ repoPath: import_node_path17.default.relative(scanPath, dir)
7466
+ };
7467
+ return { pkg, dir, node };
7468
+ }
7469
+
7470
+ // src/extract/rust.ts
7471
+ init_cjs_shims();
7472
+ var import_node_fs16 = require("fs");
7473
+ var import_node_path18 = __toESM(require("path"), 1);
7474
+ var import_smol_toml2 = require("smol-toml");
7475
+ var import_types16 = require("@neat.is/types");
7476
+ var CARGO_MANIFEST = "Cargo.toml";
7477
+ async function hasRustManifest(dir) {
7478
+ return exists2(import_node_path18.default.join(dir, CARGO_MANIFEST));
7479
+ }
7480
+ function parseCargoToml(source) {
7481
+ const dependencies = {};
7482
+ let name;
7483
+ let parsed;
7484
+ try {
7485
+ parsed = (0, import_smol_toml2.parse)(source);
7486
+ } catch {
7487
+ return { dependencies };
7488
+ }
7489
+ if (typeof parsed.package?.name === "string") name = parsed.package.name;
7490
+ for (const [crate, spec] of Object.entries(parsed.dependencies ?? {})) {
7491
+ if (typeof spec === "string") {
7492
+ dependencies[crate] = spec;
7493
+ } else if (spec && typeof spec === "object") {
7494
+ const version = spec.version;
7495
+ dependencies[crate] = typeof version === "string" ? version : "";
7496
+ } else {
7497
+ dependencies[crate] = "";
7498
+ }
7499
+ }
7500
+ return { name, dependencies };
7501
+ }
7502
+ async function discoverRustService(scanPath, dir) {
7503
+ const cargoPath = import_node_path18.default.join(dir, CARGO_MANIFEST);
7504
+ if (!await exists2(cargoPath)) return null;
7505
+ const manifest = parseCargoToml(await import_node_fs16.promises.readFile(cargoPath, "utf8").catch(() => ""));
7506
+ const name = manifest.name?.trim() || import_node_path18.default.basename(dir);
7507
+ const dependencies = manifest.dependencies;
7508
+ const pkg = { name, dependencies };
7509
+ const node = {
7510
+ id: (0, import_types16.serviceId)(name),
7511
+ type: import_types16.NodeType.ServiceNode,
7512
+ name,
7513
+ language: "rust",
7514
+ dependencies,
7515
+ repoPath: import_node_path18.default.relative(scanPath, dir)
7516
+ };
7517
+ return { pkg, dir, node };
7518
+ }
7519
+
7520
+ // src/extract/cpp.ts
7521
+ init_cjs_shims();
7522
+ var import_node_fs17 = require("fs");
7523
+ var import_node_path19 = __toESM(require("path"), 1);
7524
+ var import_types17 = require("@neat.is/types");
7525
+ var CMAKE_MANIFEST = "CMakeLists.txt";
7526
+ async function hasCppManifest(dir) {
7527
+ return exists2(import_node_path19.default.join(dir, CMAKE_MANIFEST));
7528
+ }
7529
+ function parseCMakeProjectName(source) {
7530
+ const withoutComments = source.replace(/#.*$/gm, "");
7531
+ const m = withoutComments.match(/\bproject\s*\(\s*"?([A-Za-z0-9_.+-]+)"?/i);
7532
+ return m?.[1];
7533
+ }
7534
+ async function discoverCppService(scanPath, dir) {
7535
+ const cmakePath = import_node_path19.default.join(dir, CMAKE_MANIFEST);
7536
+ if (!await exists2(cmakePath)) return null;
7537
+ const source = await import_node_fs17.promises.readFile(cmakePath, "utf8").catch(() => "");
7538
+ const name = parseCMakeProjectName(source)?.trim() || import_node_path19.default.basename(dir);
7539
+ const dependencies = {};
7540
+ const pkg = { name, dependencies };
7541
+ const node = {
7542
+ id: (0, import_types17.serviceId)(name),
7543
+ type: import_types17.NodeType.ServiceNode,
7544
+ name,
7545
+ language: "cpp",
7546
+ dependencies,
7547
+ repoPath: import_node_path19.default.relative(scanPath, dir)
7548
+ };
7549
+ return { pkg, dir, node };
7550
+ }
7551
+
7552
+ // src/extract/dockerfile-service.ts
7553
+ init_cjs_shims();
7554
+ var import_node_fs18 = require("fs");
7555
+ var import_node_path20 = __toESM(require("path"), 1);
7556
+ var import_types18 = require("@neat.is/types");
7394
7557
  function languageForSourceExt(ext) {
7395
7558
  switch (ext) {
7396
7559
  case ".py":
@@ -7405,6 +7568,19 @@ function languageForSourceExt(ext) {
7405
7568
  return "csharp";
7406
7569
  case ".java":
7407
7570
  return "java";
7571
+ case ".kt":
7572
+ return "kotlin";
7573
+ case ".rs":
7574
+ return "rust";
7575
+ case ".cpp":
7576
+ case ".cc":
7577
+ case ".cxx":
7578
+ case ".c++":
7579
+ case ".hpp":
7580
+ case ".hh":
7581
+ case ".hxx":
7582
+ case ".h++":
7583
+ return "cpp";
7408
7584
  case ".ts":
7409
7585
  case ".tsx":
7410
7586
  return "typescript";
@@ -7416,14 +7592,14 @@ function languageForSourceExt(ext) {
7416
7592
  return null;
7417
7593
  }
7418
7594
  }
7419
- var LANGUAGE_PRECEDENCE = ["typescript", "javascript", "python", "go", "ruby", "php", "csharp", "java"];
7595
+ var LANGUAGE_PRECEDENCE = ["typescript", "javascript", "python", "go", "ruby", "php", "csharp", "java", "kotlin", "rust", "cpp"];
7420
7596
  async function inferServiceLanguage(dir) {
7421
- const entries = await import_node_fs15.promises.readdir(dir, { withFileTypes: true }).catch(() => []);
7597
+ const entries = await import_node_fs18.promises.readdir(dir, { withFileTypes: true }).catch(() => []);
7422
7598
  const counts = /* @__PURE__ */ new Map();
7423
7599
  for (const entry of entries) {
7424
7600
  if (!entry.isFile()) continue;
7425
7601
  if (isNeatAuthoredSourceFile(entry.name)) continue;
7426
- const ext = import_node_path17.default.extname(entry.name).toLowerCase();
7602
+ const ext = import_node_path20.default.extname(entry.name).toLowerCase();
7427
7603
  if (!SERVICE_FILE_EXTENSIONS.has(ext)) continue;
7428
7604
  const lang = languageForSourceExt(ext);
7429
7605
  if (!lang) continue;
@@ -7442,46 +7618,50 @@ async function inferServiceLanguage(dir) {
7442
7618
  return best;
7443
7619
  }
7444
7620
  async function hasLanguageManifest(dir) {
7445
- return await exists2(import_node_path17.default.join(dir, "package.json")) || await exists2(import_node_path17.default.join(dir, "pyproject.toml")) || await exists2(import_node_path17.default.join(dir, "requirements.txt")) || await exists2(import_node_path17.default.join(dir, "setup.py")) || await exists2(import_node_path17.default.join(dir, "go.mod")) || await exists2(import_node_path17.default.join(dir, "Gemfile")) || await exists2(import_node_path17.default.join(dir, "composer.json")) || // C#'s marker is a glob (`*.csproj` / `*.sln`), so it reads the directory
7621
+ return await exists2(import_node_path20.default.join(dir, "package.json")) || await exists2(import_node_path20.default.join(dir, "pyproject.toml")) || await exists2(import_node_path20.default.join(dir, "requirements.txt")) || await exists2(import_node_path20.default.join(dir, "setup.py")) || await exists2(import_node_path20.default.join(dir, "go.mod")) || await exists2(import_node_path20.default.join(dir, "Gemfile")) || await exists2(import_node_path20.default.join(dir, "composer.json")) || // C#'s marker is a glob (`*.csproj` / `*.sln`), so it reads the directory
7446
7622
  // rather than probing a fixed filename (ADR-196).
7447
- await hasCsharpProject(dir) || // Java keys on a Maven / Gradle build manifest (ADR-197).
7448
- await hasJavaManifest(dir);
7623
+ await hasCsharpProject(dir) || // Java (and its JVM sibling Kotlin, ADR-199) key on the same Maven / Gradle
7624
+ // build manifest (ADR-197), so this one check stands the Dockerfile fallback
7625
+ // down for both.
7626
+ await hasJavaManifest(dir) || // Rust's marker is a fixed `Cargo.toml` (ADR-201).
7627
+ await hasRustManifest(dir) || // C++'s marker is a fixed `CMakeLists.txt` (ADR-202).
7628
+ await hasCppManifest(dir);
7449
7629
  }
7450
7630
  async function hasDockerfileDeclaredService(dir) {
7451
- if (!await exists2(import_node_path17.default.join(dir, "Dockerfile"))) return false;
7631
+ if (!await exists2(import_node_path20.default.join(dir, "Dockerfile"))) return false;
7452
7632
  return await inferServiceLanguage(dir) !== null;
7453
7633
  }
7454
7634
  async function discoverDockerfileService(scanPath, dir) {
7455
- if (!await exists2(import_node_path17.default.join(dir, "Dockerfile"))) return null;
7635
+ if (!await exists2(import_node_path20.default.join(dir, "Dockerfile"))) return null;
7456
7636
  if (await hasLanguageManifest(dir)) return null;
7457
7637
  const language = await inferServiceLanguage(dir);
7458
7638
  if (!language) return null;
7459
- const name = import_node_path17.default.basename(dir);
7639
+ const name = import_node_path20.default.basename(dir);
7460
7640
  const pkg = { name, dependencies: {} };
7461
7641
  const node = {
7462
- id: (0, import_types15.serviceId)(name),
7463
- type: import_types15.NodeType.ServiceNode,
7642
+ id: (0, import_types18.serviceId)(name),
7643
+ type: import_types18.NodeType.ServiceNode,
7464
7644
  name,
7465
7645
  language,
7466
7646
  dependencies: {},
7467
- repoPath: import_node_path17.default.relative(scanPath, dir)
7647
+ repoPath: import_node_path20.default.relative(scanPath, dir)
7468
7648
  };
7469
7649
  return { pkg, dir, node };
7470
7650
  }
7471
7651
 
7472
7652
  // src/extract/owners.ts
7473
7653
  init_cjs_shims();
7474
- var import_node_fs16 = require("fs");
7475
- var import_node_path18 = __toESM(require("path"), 1);
7654
+ var import_node_fs19 = require("fs");
7655
+ var import_node_path21 = __toESM(require("path"), 1);
7476
7656
  var import_minimatch = require("minimatch");
7477
7657
  async function loadCodeowners(scanPath) {
7478
7658
  const candidates = [
7479
- import_node_path18.default.join(scanPath, "CODEOWNERS"),
7480
- import_node_path18.default.join(scanPath, ".github", "CODEOWNERS")
7659
+ import_node_path21.default.join(scanPath, "CODEOWNERS"),
7660
+ import_node_path21.default.join(scanPath, ".github", "CODEOWNERS")
7481
7661
  ];
7482
7662
  for (const file of candidates) {
7483
7663
  if (await exists2(file)) {
7484
- const raw = await import_node_fs16.promises.readFile(file, "utf8");
7664
+ const raw = await import_node_fs19.promises.readFile(file, "utf8");
7485
7665
  return parseCodeowners(raw);
7486
7666
  }
7487
7667
  }
@@ -7499,7 +7679,7 @@ function parseCodeowners(raw) {
7499
7679
  return { rules };
7500
7680
  }
7501
7681
  function matchOwner(file, repoPath) {
7502
- const normalized = repoPath.split(import_node_path18.default.sep).join("/");
7682
+ const normalized = repoPath.split(import_node_path21.default.sep).join("/");
7503
7683
  for (const rule of file.rules) {
7504
7684
  if (matchesPattern(rule.pattern, normalized)) return rule.owners;
7505
7685
  }
@@ -7515,7 +7695,7 @@ function matchesPattern(rawPattern, repoPath) {
7515
7695
  return false;
7516
7696
  }
7517
7697
  async function readPackageJsonAuthor(serviceDir) {
7518
- const pkgPath = import_node_path18.default.join(serviceDir, "package.json");
7698
+ const pkgPath = import_node_path21.default.join(serviceDir, "package.json");
7519
7699
  if (!await exists2(pkgPath)) return null;
7520
7700
  try {
7521
7701
  const pkg = await readJson(pkgPath);
@@ -7552,36 +7732,36 @@ function workspaceGlobs(pkg) {
7552
7732
  return null;
7553
7733
  }
7554
7734
  async function hasPythonManifest(dir) {
7555
- return await exists2(import_node_path19.default.join(dir, "pyproject.toml")) || await exists2(import_node_path19.default.join(dir, "requirements.txt")) || await exists2(import_node_path19.default.join(dir, "setup.py"));
7735
+ return await exists2(import_node_path22.default.join(dir, "pyproject.toml")) || await exists2(import_node_path22.default.join(dir, "requirements.txt")) || await exists2(import_node_path22.default.join(dir, "setup.py"));
7556
7736
  }
7557
7737
  async function hasGoManifest(dir) {
7558
- return exists2(import_node_path19.default.join(dir, "go.mod"));
7738
+ return exists2(import_node_path22.default.join(dir, "go.mod"));
7559
7739
  }
7560
7740
  async function hasRubyManifest(dir) {
7561
- return exists2(import_node_path19.default.join(dir, "Gemfile"));
7741
+ return exists2(import_node_path22.default.join(dir, "Gemfile"));
7562
7742
  }
7563
7743
  async function hasPhpManifest(dir) {
7564
- return exists2(import_node_path19.default.join(dir, "composer.json"));
7744
+ return exists2(import_node_path22.default.join(dir, "composer.json"));
7565
7745
  }
7566
7746
  async function hasCsharpManifest(dir) {
7567
7747
  return hasCsharpProject(dir);
7568
7748
  }
7569
7749
  async function loadGitignore(scanPath) {
7570
- const gitignorePath = import_node_path19.default.join(scanPath, ".gitignore");
7750
+ const gitignorePath = import_node_path22.default.join(scanPath, ".gitignore");
7571
7751
  if (!await exists2(gitignorePath)) return null;
7572
- const raw = await import_node_fs17.promises.readFile(gitignorePath, "utf8");
7752
+ const raw = await import_node_fs20.promises.readFile(gitignorePath, "utf8");
7573
7753
  return (0, import_ignore.default)().add(raw);
7574
7754
  }
7575
7755
  async function walkDirs(start, scanPath, options, visit) {
7576
7756
  async function recurse(current, depth) {
7577
7757
  if (depth > options.maxDepth) return;
7578
- const entries = await import_node_fs17.promises.readdir(current, { withFileTypes: true }).catch(() => []);
7758
+ const entries = await import_node_fs20.promises.readdir(current, { withFileTypes: true }).catch(() => []);
7579
7759
  for (const entry of entries) {
7580
7760
  if (!entry.isDirectory()) continue;
7581
7761
  if (IGNORED_DIRS.has(entry.name)) continue;
7582
- const child = import_node_path19.default.join(current, entry.name);
7762
+ const child = import_node_path22.default.join(current, entry.name);
7583
7763
  if (options.ig) {
7584
- const rel = import_node_path19.default.relative(scanPath, child).split(import_node_path19.default.sep).join("/");
7764
+ const rel = import_node_path22.default.relative(scanPath, child).split(import_node_path22.default.sep).join("/");
7585
7765
  if (rel && options.ig.ignores(rel + "/")) continue;
7586
7766
  }
7587
7767
  if (await isPythonVenvDir(child)) continue;
@@ -7597,8 +7777,8 @@ async function expandWorkspaceGlobs(scanPath, globs) {
7597
7777
  for (const raw of globs) {
7598
7778
  const pattern = raw.replace(/^\.\//, "");
7599
7779
  if (!pattern.includes("*")) {
7600
- const candidate = import_node_path19.default.join(scanPath, pattern);
7601
- if (await exists2(import_node_path19.default.join(candidate, "package.json"))) found.add(candidate);
7780
+ const candidate = import_node_path22.default.join(scanPath, pattern);
7781
+ if (await exists2(import_node_path22.default.join(candidate, "package.json"))) found.add(candidate);
7602
7782
  continue;
7603
7783
  }
7604
7784
  const segments = pattern.split("/");
@@ -7607,13 +7787,13 @@ async function expandWorkspaceGlobs(scanPath, globs) {
7607
7787
  if (seg.includes("*")) break;
7608
7788
  staticSegments.push(seg);
7609
7789
  }
7610
- const start = import_node_path19.default.join(scanPath, ...staticSegments);
7790
+ const start = import_node_path22.default.join(scanPath, ...staticSegments);
7611
7791
  if (!await exists2(start)) continue;
7612
7792
  const hasDoubleStar = pattern.includes("**");
7613
7793
  const walkDepth = hasDoubleStar ? scanDepth : Math.max(0, segments.length - staticSegments.length - 1);
7614
7794
  await walkDirs(start, scanPath, { maxDepth: walkDepth, ig: null }, async (dir) => {
7615
- const rel = import_node_path19.default.relative(scanPath, dir).split(import_node_path19.default.sep).join("/");
7616
- if ((0, import_minimatch2.minimatch)(rel, pattern) && await exists2(import_node_path19.default.join(dir, "package.json"))) {
7795
+ const rel = import_node_path22.default.relative(scanPath, dir).split(import_node_path22.default.sep).join("/");
7796
+ if ((0, import_minimatch2.minimatch)(rel, pattern) && await exists2(import_node_path22.default.join(dir, "package.json"))) {
7617
7797
  found.add(dir);
7618
7798
  }
7619
7799
  });
@@ -7636,31 +7816,31 @@ function detectJsFramework(pkg) {
7636
7816
  async function detectJsServiceLanguage(dir, pkg) {
7637
7817
  const deps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
7638
7818
  if (deps["typescript"] !== void 0) return "typescript";
7639
- const entries = await import_node_fs17.promises.readdir(dir).catch(() => []);
7819
+ const entries = await import_node_fs20.promises.readdir(dir).catch(() => []);
7640
7820
  if (entries.some((name) => /^tsconfig(\..+)?\.json$/.test(name))) return "typescript";
7641
7821
  return "javascript";
7642
7822
  }
7643
7823
  async function discoverNodeService(scanPath, dir) {
7644
- const pkgPath = import_node_path19.default.join(dir, "package.json");
7824
+ const pkgPath = import_node_path22.default.join(dir, "package.json");
7645
7825
  if (!await exists2(pkgPath)) return null;
7646
7826
  let pkg;
7647
7827
  try {
7648
7828
  pkg = await readJson(pkgPath);
7649
7829
  } catch (err) {
7650
- recordExtractionError("services", import_node_path19.default.relative(scanPath, pkgPath), err);
7830
+ recordExtractionError("services", import_node_path22.default.relative(scanPath, pkgPath), err);
7651
7831
  return null;
7652
7832
  }
7653
7833
  if (!pkg.name) return null;
7654
7834
  const framework = detectJsFramework(pkg);
7655
7835
  const language = await detectJsServiceLanguage(dir, pkg);
7656
7836
  const node = {
7657
- id: (0, import_types16.serviceId)(pkg.name),
7658
- type: import_types16.NodeType.ServiceNode,
7837
+ id: (0, import_types19.serviceId)(pkg.name),
7838
+ type: import_types19.NodeType.ServiceNode,
7659
7839
  name: pkg.name,
7660
7840
  language,
7661
7841
  version: pkg.version,
7662
7842
  dependencies: pkg.dependencies ?? {},
7663
- repoPath: import_node_path19.default.relative(scanPath, dir),
7843
+ repoPath: import_node_path22.default.relative(scanPath, dir),
7664
7844
  ...pkg.engines?.node ? { nodeEngine: pkg.engines.node } : {},
7665
7845
  ...framework ? { framework } : {}
7666
7846
  };
@@ -7671,18 +7851,18 @@ async function discoverPyService(scanPath, dir) {
7671
7851
  if (!py) return null;
7672
7852
  const pkg = pythonToPackage(py);
7673
7853
  const node = {
7674
- id: (0, import_types16.serviceId)(py.name),
7675
- type: import_types16.NodeType.ServiceNode,
7854
+ id: (0, import_types19.serviceId)(py.name),
7855
+ type: import_types19.NodeType.ServiceNode,
7676
7856
  name: py.name,
7677
7857
  language: "python",
7678
7858
  version: py.version,
7679
7859
  dependencies: py.dependencies,
7680
- repoPath: import_node_path19.default.relative(scanPath, dir)
7860
+ repoPath: import_node_path22.default.relative(scanPath, dir)
7681
7861
  };
7682
7862
  return { pkg, dir, node };
7683
7863
  }
7684
7864
  async function discoverServices(scanPath) {
7685
- const rootPkgPath = import_node_path19.default.join(scanPath, "package.json");
7865
+ const rootPkgPath = import_node_path22.default.join(scanPath, "package.json");
7686
7866
  let rootPkg = null;
7687
7867
  if (await exists2(rootPkgPath)) {
7688
7868
  try {
@@ -7690,7 +7870,7 @@ async function discoverServices(scanPath) {
7690
7870
  } catch (err) {
7691
7871
  recordExtractionError(
7692
7872
  "services workspaces",
7693
- import_node_path19.default.relative(scanPath, rootPkgPath),
7873
+ import_node_path22.default.relative(scanPath, rootPkgPath),
7694
7874
  err
7695
7875
  );
7696
7876
  }
@@ -7702,7 +7882,7 @@ async function discoverServices(scanPath) {
7702
7882
  } else {
7703
7883
  if (rootPkg && rootPkg.name) {
7704
7884
  candidateDirs.push(scanPath);
7705
- } else if (await hasPythonManifest(scanPath) || await hasGoManifest(scanPath) || await hasRubyManifest(scanPath) || await hasPhpManifest(scanPath) || await hasCsharpManifest(scanPath) || await hasJavaManifest(scanPath)) {
7885
+ } 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)) {
7706
7886
  candidateDirs.push(scanPath);
7707
7887
  }
7708
7888
  const ig = await loadGitignore(scanPath);
@@ -7711,9 +7891,9 @@ async function discoverServices(scanPath) {
7711
7891
  scanPath,
7712
7892
  { maxDepth: parseScanDepth(), ig },
7713
7893
  async (dir) => {
7714
- if (await exists2(import_node_path19.default.join(dir, "package.json"))) {
7894
+ if (await exists2(import_node_path22.default.join(dir, "package.json"))) {
7715
7895
  candidateDirs.push(dir);
7716
- } else if (await hasPythonManifest(dir) || await hasGoManifest(dir) || await hasRubyManifest(dir) || await hasPhpManifest(dir) || await hasCsharpManifest(dir) || await hasJavaManifest(dir)) {
7896
+ } 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)) {
7717
7897
  candidateDirs.push(dir);
7718
7898
  } else if (await hasDockerfileDeclaredService(dir)) {
7719
7899
  candidateDirs.push(dir);
@@ -7726,14 +7906,21 @@ async function discoverServices(scanPath) {
7726
7906
  const seenDirs = /* @__PURE__ */ new Set();
7727
7907
  const out = [];
7728
7908
  for (const dir of candidateDirs) {
7729
- const service = await discoverNodeService(scanPath, dir) ?? await discoverPyService(scanPath, dir) ?? await discoverGoService(scanPath, dir) ?? await discoverRubyService(scanPath, dir) ?? await discoverPhpService(scanPath, dir) ?? await discoverCsharpService(scanPath, dir) ?? await discoverJavaService(scanPath, dir) ?? await discoverDockerfileService(scanPath, dir);
7909
+ 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
7910
+ // manifest, so Kotlin claims a JVM-manifest dir whose source is `.kt` (ADR-199)
7911
+ // and a `.java`-dominant one falls through to the Java reader unchanged.
7912
+ await discoverKotlinService(scanPath, dir) ?? await discoverJavaService(scanPath, dir) ?? // Rust keys on a `Cargo.toml`, a manifest no other language shares (ADR-201),
7913
+ // so its order among the distinct-manifest discoverers doesn't matter.
7914
+ await discoverRustService(scanPath, dir) ?? // C++ keys on a `CMakeLists.txt`, likewise a manifest no other language shares
7915
+ // (ADR-202), so it too sits anywhere among the distinct-manifest discoverers.
7916
+ await discoverCppService(scanPath, dir) ?? await discoverDockerfileService(scanPath, dir);
7730
7917
  if (!service) continue;
7731
7918
  if (seenDirs.has(service.dir)) continue;
7732
7919
  seenDirs.add(service.dir);
7733
7920
  const existingDir = seen.get(service.node.name);
7734
7921
  if (existingDir !== void 0) {
7735
- const a = import_node_path19.default.relative(scanPath, existingDir) || ".";
7736
- const b = import_node_path19.default.relative(scanPath, dir) || ".";
7922
+ const a = import_node_path22.default.relative(scanPath, existingDir) || ".";
7923
+ const b = import_node_path22.default.relative(scanPath, dir) || ".";
7737
7924
  console.warn(
7738
7925
  `[neat] duplicate package name "${service.node.name}" \u2014 keeping ${a}, ignoring ${b}`
7739
7926
  );
@@ -7747,6 +7934,17 @@ async function discoverServices(scanPath) {
7747
7934
  const owner = await computeServiceOwner(codeowners, service.node.repoPath, service.dir);
7748
7935
  if (owner !== void 0) service.node.owner = owner;
7749
7936
  }
7937
+ const resolvedDirs = out.map((s) => import_node_path22.default.resolve(s.dir));
7938
+ for (let i = 0; i < out.length; i++) {
7939
+ const self = resolvedDirs[i];
7940
+ const nested = [];
7941
+ for (let j = 0; j < out.length; j++) {
7942
+ if (i === j) continue;
7943
+ const other = resolvedDirs[j];
7944
+ if (other.startsWith(self + import_node_path22.default.sep)) nested.push(other);
7945
+ }
7946
+ out[i].excludeDirs = nested;
7947
+ }
7750
7948
  return out;
7751
7949
  }
7752
7950
  function addServiceNodes(graph, services) {
@@ -7770,20 +7968,20 @@ function addServiceNodes(graph, services) {
7770
7968
 
7771
7969
  // src/extract/aliases.ts
7772
7970
  init_cjs_shims();
7773
- var import_node_path20 = __toESM(require("path"), 1);
7774
- var import_node_fs18 = require("fs");
7971
+ var import_node_path23 = __toESM(require("path"), 1);
7972
+ var import_node_fs21 = require("fs");
7775
7973
  var import_yaml2 = require("yaml");
7776
- var import_types17 = require("@neat.is/types");
7974
+ var import_types20 = require("@neat.is/types");
7777
7975
  var K8S_KINDS_WITH_HOSTNAMES = /* @__PURE__ */ new Set([
7778
7976
  "Service",
7779
7977
  "Deployment",
7780
7978
  "StatefulSet",
7781
7979
  "DaemonSet"
7782
7980
  ]);
7783
- function addAliases(graph, serviceId12, candidates) {
7784
- if (!graph.hasNode(serviceId12)) return;
7785
- const node = graph.getNodeAttributes(serviceId12);
7786
- if (node.type !== import_types17.NodeType.ServiceNode) return;
7981
+ function addAliases(graph, serviceId15, candidates) {
7982
+ if (!graph.hasNode(serviceId15)) return;
7983
+ const node = graph.getNodeAttributes(serviceId15);
7984
+ if (node.type !== import_types20.NodeType.ServiceNode) return;
7787
7985
  const set = new Set(node.aliases ?? []);
7788
7986
  for (const c of candidates) {
7789
7987
  if (!c) continue;
@@ -7792,20 +7990,20 @@ function addAliases(graph, serviceId12, candidates) {
7792
7990
  }
7793
7991
  if (set.size === 0) return;
7794
7992
  const updated = { ...node, aliases: [...set].sort() };
7795
- graph.replaceNodeAttributes(serviceId12, updated);
7993
+ graph.replaceNodeAttributes(serviceId15, updated);
7796
7994
  }
7797
7995
  function indexServicesByName(services) {
7798
7996
  const map = /* @__PURE__ */ new Map();
7799
7997
  for (const s of services) {
7800
7998
  map.set(s.node.name, s.node.id);
7801
- map.set(import_node_path20.default.basename(s.dir), s.node.id);
7999
+ map.set(import_node_path23.default.basename(s.dir), s.node.id);
7802
8000
  }
7803
8001
  return map;
7804
8002
  }
7805
8003
  async function collectComposeAliases(graph, scanPath, serviceIndex) {
7806
8004
  let composePath = null;
7807
8005
  for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
7808
- const abs = import_node_path20.default.join(scanPath, name);
8006
+ const abs = import_node_path23.default.join(scanPath, name);
7809
8007
  if (await exists2(abs)) {
7810
8008
  composePath = abs;
7811
8009
  break;
@@ -7818,19 +8016,19 @@ async function collectComposeAliases(graph, scanPath, serviceIndex) {
7818
8016
  } catch (err) {
7819
8017
  recordExtractionError(
7820
8018
  "aliases compose",
7821
- import_node_path20.default.relative(scanPath, composePath),
8019
+ import_node_path23.default.relative(scanPath, composePath),
7822
8020
  err
7823
8021
  );
7824
8022
  return;
7825
8023
  }
7826
8024
  if (!compose?.services) return;
7827
8025
  for (const [composeName, svc] of Object.entries(compose.services)) {
7828
- const serviceId12 = serviceIndex.get(composeName);
7829
- if (!serviceId12) continue;
8026
+ const serviceId15 = serviceIndex.get(composeName);
8027
+ if (!serviceId15) continue;
7830
8028
  const aliases = /* @__PURE__ */ new Set([composeName]);
7831
8029
  if (svc.container_name) aliases.add(svc.container_name);
7832
8030
  if (svc.hostname) aliases.add(svc.hostname);
7833
- addAliases(graph, serviceId12, aliases);
8031
+ addAliases(graph, serviceId15, aliases);
7834
8032
  }
7835
8033
  }
7836
8034
  var LABEL_KEYS = /* @__PURE__ */ new Set([
@@ -7861,11 +8059,11 @@ function parseDockerfileLabels(content) {
7861
8059
  }
7862
8060
  async function collectDockerfileAliases(graph, services) {
7863
8061
  for (const service of services) {
7864
- const dockerfilePath = import_node_path20.default.join(service.dir, "Dockerfile");
8062
+ const dockerfilePath = import_node_path23.default.join(service.dir, "Dockerfile");
7865
8063
  if (!await exists2(dockerfilePath)) continue;
7866
8064
  let content;
7867
8065
  try {
7868
- content = await import_node_fs18.promises.readFile(dockerfilePath, "utf8");
8066
+ content = await import_node_fs21.promises.readFile(dockerfilePath, "utf8");
7869
8067
  } catch (err) {
7870
8068
  recordExtractionError("aliases dockerfile", dockerfilePath, err);
7871
8069
  continue;
@@ -7877,15 +8075,15 @@ async function collectDockerfileAliases(graph, services) {
7877
8075
  async function walkYamlFiles(start, depth = 0, max = 5) {
7878
8076
  if (depth > max) return [];
7879
8077
  const out = [];
7880
- const entries = await import_node_fs18.promises.readdir(start, { withFileTypes: true }).catch(() => []);
8078
+ const entries = await import_node_fs21.promises.readdir(start, { withFileTypes: true }).catch(() => []);
7881
8079
  for (const entry of entries) {
7882
8080
  if (entry.isDirectory()) {
7883
8081
  if (IGNORED_DIRS.has(entry.name)) continue;
7884
- const child = import_node_path20.default.join(start, entry.name);
8082
+ const child = import_node_path23.default.join(start, entry.name);
7885
8083
  if (await isPythonVenvDir(child)) continue;
7886
8084
  out.push(...await walkYamlFiles(child, depth + 1, max));
7887
- } else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path20.default.extname(entry.name))) {
7888
- out.push(import_node_path20.default.join(start, entry.name));
8085
+ } else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path23.default.extname(entry.name))) {
8086
+ out.push(import_node_path23.default.join(start, entry.name));
7889
8087
  }
7890
8088
  }
7891
8089
  return out;
@@ -7912,7 +8110,7 @@ function k8sServiceTarget(doc, byName) {
7912
8110
  async function collectK8sAliases(graph, scanPath, serviceIndex) {
7913
8111
  const files = await walkYamlFiles(scanPath);
7914
8112
  for (const file of files) {
7915
- const content = await import_node_fs18.promises.readFile(file, "utf8");
8113
+ const content = await import_node_fs21.promises.readFile(file, "utf8");
7916
8114
  let docs;
7917
8115
  try {
7918
8116
  docs = (0, import_yaml2.parseAllDocuments)(content).map((d) => d.toJSON());
@@ -7937,14 +8135,14 @@ async function addServiceAliases(graph, scanPath, services) {
7937
8135
 
7938
8136
  // src/extract/files.ts
7939
8137
  init_cjs_shims();
7940
- var import_node_path21 = __toESM(require("path"), 1);
8138
+ var import_node_path24 = __toESM(require("path"), 1);
7941
8139
  async function addFiles(graph, services) {
7942
8140
  let nodesAdded = 0;
7943
8141
  let edgesAdded = 0;
7944
8142
  for (const service of services) {
7945
- const filePaths = await walkSourceFiles(service.dir);
8143
+ const filePaths = await walkSourceFiles(service.dir, service.excludeDirs);
7946
8144
  for (const filePath of filePaths) {
7947
- const relPath = toPosix(import_node_path21.default.relative(service.dir, filePath));
8145
+ const relPath = toPosix(import_node_path24.default.relative(service.dir, filePath));
7948
8146
  const { nodesAdded: n, edgesAdded: e } = ensureFileNode(
7949
8147
  graph,
7950
8148
  service.pkg.name,
@@ -7960,7 +8158,7 @@ async function addFiles(graph, services) {
7960
8158
 
7961
8159
  // src/extract/symbols.ts
7962
8160
  init_cjs_shims();
7963
- var import_node_path22 = __toESM(require("path"), 1);
8161
+ var import_node_path25 = __toESM(require("path"), 1);
7964
8162
  var import_tree_sitter3 = __toESM(require("tree-sitter"), 1);
7965
8163
  var import_tree_sitter_javascript3 = __toESM(require("tree-sitter-javascript"), 1);
7966
8164
  var import_tree_sitter_typescript = __toESM(require("tree-sitter-typescript"), 1);
@@ -7970,7 +8168,10 @@ var import_tree_sitter_ruby2 = __toESM(require("tree-sitter-ruby"), 1);
7970
8168
  var import_tree_sitter_php2 = __toESM(require("tree-sitter-php"), 1);
7971
8169
  var import_tree_sitter_c_sharp = __toESM(require("tree-sitter-c-sharp"), 1);
7972
8170
  var import_tree_sitter_java = __toESM(require("tree-sitter-java"), 1);
7973
- var import_types18 = require("@neat.is/types");
8171
+ var import_tree_sitter_kotlin = __toESM(require("tree-sitter-kotlin"), 1);
8172
+ var import_tree_sitter_rust = __toESM(require("tree-sitter-rust"), 1);
8173
+ var import_tree_sitter_cpp = __toESM(require("tree-sitter-cpp"), 1);
8174
+ var import_types21 = require("@neat.is/types");
7974
8175
  var PARSE_CHUNK3 = 16384;
7975
8176
  var GRAMMAR_BY_EXT = {
7976
8177
  ".ts": import_tree_sitter_typescript.default.typescript,
@@ -7987,7 +8188,25 @@ var SYMBOL_GRAMMAR_BY_EXT = {
7987
8188
  ".rb": import_tree_sitter_ruby2.default,
7988
8189
  ".php": import_tree_sitter_php2.default.php_only,
7989
8190
  ".cs": import_tree_sitter_c_sharp.default,
7990
- ".java": import_tree_sitter_java.default
8191
+ ".java": import_tree_sitter_java.default,
8192
+ ".kt": import_tree_sitter_kotlin.default,
8193
+ ".rs": import_tree_sitter_rust.default,
8194
+ // C++ (ADR-202) — only the UNAMBIGUOUS extensions. `.cpp` / `.cc` / `.cxx` /
8195
+ // `.c++` are implementation files; `.hpp` / `.hh` / `.hxx` / `.h++` are C++-only
8196
+ // headers. `.h` and `.c` are deliberately absent: they are shared with C (a
8197
+ // language NEAT does not add), and this map is global — the symbol grain
8198
+ // dispatches on extension, not on the owning service's language — so claiming
8199
+ // `.h` would parse every pure-C header in any repo with the C++ grammar and
8200
+ // mislabel it. The otel-demo `currency` target's executable code lives in a
8201
+ // `.cpp`, so nothing is lost.
8202
+ ".cpp": import_tree_sitter_cpp.default,
8203
+ ".cc": import_tree_sitter_cpp.default,
8204
+ ".cxx": import_tree_sitter_cpp.default,
8205
+ ".c++": import_tree_sitter_cpp.default,
8206
+ ".hpp": import_tree_sitter_cpp.default,
8207
+ ".hh": import_tree_sitter_cpp.default,
8208
+ ".hxx": import_tree_sitter_cpp.default,
8209
+ ".h++": import_tree_sitter_cpp.default
7991
8210
  };
7992
8211
  function parseSource3(parser, source) {
7993
8212
  return parser.parse(
@@ -8400,6 +8619,237 @@ function collectJavaSymbolDefs(root) {
8400
8619
  walkChildren(root, void 0);
8401
8620
  return out;
8402
8621
  }
8622
+ function collectKotlinSymbolDefs(root) {
8623
+ const out = [];
8624
+ const push = (kind, qualname, node) => {
8625
+ out.push({
8626
+ kind,
8627
+ qualname,
8628
+ startLine: node.startPosition.row + 1,
8629
+ endLine: node.endPosition.row + 1
8630
+ });
8631
+ };
8632
+ const join = (prefix, name) => prefix ? `${prefix}.${name}` : name;
8633
+ const firstChildOfType = (node, types) => {
8634
+ for (let i = 0; i < node.namedChildCount; i++) {
8635
+ const child = node.namedChild(i);
8636
+ if (child && types.includes(child.type)) return child;
8637
+ }
8638
+ return void 0;
8639
+ };
8640
+ const nameOf = (node, ...types) => firstChildOfType(node, types)?.text;
8641
+ const bodyOf = (node) => firstChildOfType(node, ["class_body", "enum_class_body"]);
8642
+ let pkg;
8643
+ for (let i = 0; i < root.namedChildCount; i++) {
8644
+ const child = root.namedChild(i);
8645
+ if (child?.type === "package_header") {
8646
+ pkg = child.namedChild(0)?.text;
8647
+ break;
8648
+ }
8649
+ }
8650
+ const walkChildren = (container, classCtx) => {
8651
+ for (let i = 0; i < container.namedChildCount; i++) {
8652
+ const child = container.namedChild(i);
8653
+ if (child) visit(child, classCtx);
8654
+ }
8655
+ };
8656
+ const visit = (node, classCtx) => {
8657
+ switch (node.type) {
8658
+ case "class_declaration":
8659
+ case "object_declaration": {
8660
+ const name = nameOf(node, "type_identifier");
8661
+ const full = name ? join(classCtx ?? pkg, name) : classCtx;
8662
+ if (name) push("class", full, node);
8663
+ const body = bodyOf(node);
8664
+ if (body) walkChildren(body, full);
8665
+ return;
8666
+ }
8667
+ case "companion_object": {
8668
+ const name = nameOf(node, "type_identifier");
8669
+ const full = name ? join(classCtx ?? pkg, name) : classCtx ?? pkg;
8670
+ const body = bodyOf(node);
8671
+ if (body) walkChildren(body, full);
8672
+ return;
8673
+ }
8674
+ case "function_declaration": {
8675
+ const name = nameOf(node, "simple_identifier");
8676
+ if (name) push(classCtx ? "method" : "function", join(classCtx ?? pkg, name), node);
8677
+ return;
8678
+ }
8679
+ case "secondary_constructor": {
8680
+ if (classCtx) {
8681
+ const typeName = classCtx.slice(classCtx.lastIndexOf(".") + 1);
8682
+ push("constructor", join(classCtx, typeName), node);
8683
+ }
8684
+ return;
8685
+ }
8686
+ }
8687
+ walkChildren(node, classCtx);
8688
+ };
8689
+ walkChildren(root, void 0);
8690
+ return out;
8691
+ }
8692
+ function collectRustSymbolDefs(root) {
8693
+ const out = [];
8694
+ const push = (kind, qualname, node) => {
8695
+ out.push({
8696
+ kind,
8697
+ qualname,
8698
+ startLine: node.startPosition.row + 1,
8699
+ endLine: node.endPosition.row + 1
8700
+ });
8701
+ };
8702
+ const join = (prefix, name) => prefix ? `${prefix}::${name}` : name;
8703
+ const walkTypeBody = (body, typeCtx) => {
8704
+ for (let i = 0; i < body.namedChildCount; i++) {
8705
+ const child = body.namedChild(i);
8706
+ if (!child) continue;
8707
+ if (child.type === "function_item" || child.type === "function_signature_item") {
8708
+ const name = child.childForFieldName("name")?.text;
8709
+ if (name) push("method", join(typeCtx, name), child);
8710
+ }
8711
+ }
8712
+ };
8713
+ const walkChildren = (container, modCtx) => {
8714
+ for (let i = 0; i < container.namedChildCount; i++) {
8715
+ const child = container.namedChild(i);
8716
+ if (child) visit(child, modCtx);
8717
+ }
8718
+ };
8719
+ const visit = (node, modCtx) => {
8720
+ switch (node.type) {
8721
+ case "mod_item": {
8722
+ const name = node.childForFieldName("name")?.text;
8723
+ const full = name ? join(modCtx, name) : modCtx;
8724
+ const body = node.childForFieldName("body");
8725
+ if (body) walkChildren(body, full);
8726
+ return;
8727
+ }
8728
+ case "struct_item":
8729
+ case "enum_item": {
8730
+ const name = node.childForFieldName("name")?.text;
8731
+ if (name) push("class", join(modCtx, name), node);
8732
+ return;
8733
+ }
8734
+ case "trait_item": {
8735
+ const name = node.childForFieldName("name")?.text;
8736
+ const full = name ? join(modCtx, name) : modCtx;
8737
+ if (name) push("class", full, node);
8738
+ const body = node.childForFieldName("body");
8739
+ if (body && full) walkTypeBody(body, full);
8740
+ return;
8741
+ }
8742
+ case "impl_item": {
8743
+ const typeName = node.childForFieldName("type")?.text;
8744
+ const full = typeName ? join(modCtx, typeName) : modCtx;
8745
+ const body = node.childForFieldName("body");
8746
+ if (body && full) walkTypeBody(body, full);
8747
+ return;
8748
+ }
8749
+ case "function_item": {
8750
+ const name = node.childForFieldName("name")?.text;
8751
+ if (name) push("function", join(modCtx, name), node);
8752
+ return;
8753
+ }
8754
+ }
8755
+ walkChildren(node, modCtx);
8756
+ };
8757
+ walkChildren(root, void 0);
8758
+ return out;
8759
+ }
8760
+ function collectCppSymbolDefs(root) {
8761
+ const out = [];
8762
+ const push = (kind, qualname, node) => {
8763
+ out.push({
8764
+ kind,
8765
+ qualname,
8766
+ startLine: node.startPosition.row + 1,
8767
+ endLine: node.endPosition.row + 1
8768
+ });
8769
+ };
8770
+ const join = (prefix, name) => prefix ? `${prefix}::${name}` : name;
8771
+ const DECLARATOR_WRAPPERS = /* @__PURE__ */ new Set([
8772
+ "function_declarator",
8773
+ "pointer_declarator",
8774
+ "reference_declarator",
8775
+ "parenthesized_declarator"
8776
+ ]);
8777
+ const functionNameNode = (node) => {
8778
+ let decl = node.childForFieldName("declarator");
8779
+ for (let guard = 0; decl && guard < 8; guard++) {
8780
+ if (decl.type === "function_declarator") {
8781
+ return decl.childForFieldName("declarator") ?? void 0;
8782
+ }
8783
+ let next;
8784
+ for (let i = 0; i < decl.namedChildCount; i++) {
8785
+ const child = decl.namedChild(i);
8786
+ if (child && DECLARATOR_WRAPPERS.has(child.type)) {
8787
+ next = child;
8788
+ break;
8789
+ }
8790
+ }
8791
+ decl = next;
8792
+ }
8793
+ return void 0;
8794
+ };
8795
+ const classify = (nameText, prefix, enclosingClass) => {
8796
+ const qualname = join(prefix, nameText);
8797
+ let className;
8798
+ let memberName;
8799
+ const idx = nameText.lastIndexOf("::");
8800
+ if (idx >= 0) {
8801
+ memberName = nameText.slice(idx + 2);
8802
+ const before = nameText.slice(0, idx);
8803
+ const j = before.lastIndexOf("::");
8804
+ className = j >= 0 ? before.slice(j + 2) : before;
8805
+ } else {
8806
+ memberName = nameText;
8807
+ className = enclosingClass;
8808
+ }
8809
+ let kind;
8810
+ if (className && memberName === className) kind = "constructor";
8811
+ else if (className) kind = "method";
8812
+ else kind = "function";
8813
+ return { kind, qualname };
8814
+ };
8815
+ const walkChildren = (container, prefix, enclosingClass) => {
8816
+ for (let i = 0; i < container.namedChildCount; i++) {
8817
+ const child = container.namedChild(i);
8818
+ if (child) visit(child, prefix, enclosingClass);
8819
+ }
8820
+ };
8821
+ const visit = (node, prefix, enclosingClass) => {
8822
+ switch (node.type) {
8823
+ case "namespace_definition": {
8824
+ const name = node.childForFieldName("name")?.text;
8825
+ const newPrefix = name ? join(prefix, name) : prefix;
8826
+ const body = node.childForFieldName("body");
8827
+ if (body) walkChildren(body, newPrefix, void 0);
8828
+ return;
8829
+ }
8830
+ case "class_specifier":
8831
+ case "struct_specifier": {
8832
+ const name = node.childForFieldName("name")?.text;
8833
+ const full = name ? join(prefix, name) : prefix;
8834
+ if (name) push("class", full, node);
8835
+ const body = node.childForFieldName("body");
8836
+ if (body && name) walkChildren(body, full, name);
8837
+ return;
8838
+ }
8839
+ case "function_definition": {
8840
+ const nameNode = functionNameNode(node);
8841
+ if (nameNode) {
8842
+ const { kind, qualname } = classify(nameNode.text, prefix, enclosingClass);
8843
+ push(kind, qualname, node);
8844
+ }
8845
+ return;
8846
+ }
8847
+ }
8848
+ walkChildren(node, prefix, enclosingClass);
8849
+ };
8850
+ walkChildren(root, void 0, void 0);
8851
+ return out;
8852
+ }
8403
8853
  function collectSymbolDefsForExt(ext, root) {
8404
8854
  switch (ext) {
8405
8855
  case ".py":
@@ -8414,6 +8864,19 @@ function collectSymbolDefsForExt(ext, root) {
8414
8864
  return collectCsharpSymbolDefs(root);
8415
8865
  case ".java":
8416
8866
  return collectJavaSymbolDefs(root);
8867
+ case ".kt":
8868
+ return collectKotlinSymbolDefs(root);
8869
+ case ".rs":
8870
+ return collectRustSymbolDefs(root);
8871
+ case ".cpp":
8872
+ case ".cc":
8873
+ case ".cxx":
8874
+ case ".c++":
8875
+ case ".hpp":
8876
+ case ".hh":
8877
+ case ".hxx":
8878
+ case ".h++":
8879
+ return collectCppSymbolDefs(root);
8417
8880
  default:
8418
8881
  return collectSymbolDefs(root);
8419
8882
  }
@@ -8445,12 +8908,12 @@ async function addSymbols(graph, services) {
8445
8908
  let nodesAdded = 0;
8446
8909
  let edgesAdded = 0;
8447
8910
  for (const service of services) {
8448
- const files = await loadSourceFiles(service.dir);
8911
+ const files = await loadSourceFiles(service.dir, service.excludeDirs);
8449
8912
  for (const file of files) {
8450
- const ext = import_node_path22.default.extname(file.path);
8913
+ const ext = import_node_path25.default.extname(file.path);
8451
8914
  const parser = parserForExt5(ext);
8452
8915
  if (!parser) continue;
8453
- const relPath = toPosix(import_node_path22.default.relative(service.dir, file.path));
8916
+ const relPath = toPosix(import_node_path25.default.relative(service.dir, file.path));
8454
8917
  let defs;
8455
8918
  try {
8456
8919
  const tree = parseSource3(parser, file.content);
@@ -8469,11 +8932,11 @@ async function addSymbols(graph, services) {
8469
8932
  nodesAdded += fn;
8470
8933
  edgesAdded += fe;
8471
8934
  for (const { def, disambiguator } of disambiguate(defs)) {
8472
- const sid = (0, import_types18.symbolId)(service.pkg.name, relPath, def.qualname, disambiguator);
8935
+ const sid = (0, import_types21.symbolId)(service.pkg.name, relPath, def.qualname, disambiguator);
8473
8936
  if (!graph.hasNode(sid)) {
8474
8937
  const node = {
8475
8938
  id: sid,
8476
- type: import_types18.NodeType.SymbolNode,
8939
+ type: import_types21.NodeType.SymbolNode,
8477
8940
  kind: def.kind,
8478
8941
  qualname: def.qualname,
8479
8942
  span: { startLine: def.startLine, endLine: def.endLine },
@@ -8484,15 +8947,15 @@ async function addSymbols(graph, services) {
8484
8947
  graph.addNode(sid, node);
8485
8948
  nodesAdded++;
8486
8949
  }
8487
- const containsId = (0, import_types18.extractedEdgeId)(fileNodeId, sid, import_types18.EdgeType.CONTAINS);
8950
+ const containsId = (0, import_types21.extractedEdgeId)(fileNodeId, sid, import_types21.EdgeType.CONTAINS);
8488
8951
  if (!graph.hasEdge(containsId)) {
8489
8952
  const edge = {
8490
8953
  id: containsId,
8491
8954
  source: fileNodeId,
8492
8955
  target: sid,
8493
- type: import_types18.EdgeType.CONTAINS,
8494
- provenance: import_types18.Provenance.EXTRACTED,
8495
- confidence: (0, import_types18.confidenceForExtracted)("structural"),
8956
+ type: import_types21.EdgeType.CONTAINS,
8957
+ provenance: import_types21.Provenance.EXTRACTED,
8958
+ confidence: (0, import_types21.confidenceForExtracted)("structural"),
8496
8959
  evidence: {
8497
8960
  file: relPath,
8498
8961
  line: def.startLine,
@@ -8510,9 +8973,9 @@ async function addSymbols(graph, services) {
8510
8973
 
8511
8974
  // src/extract/symbol-edges.ts
8512
8975
  init_cjs_shims();
8513
- var import_node_path23 = __toESM(require("path"), 1);
8976
+ var import_node_path26 = __toESM(require("path"), 1);
8514
8977
  var import_tree_sitter4 = __toESM(require("tree-sitter"), 1);
8515
- var import_types19 = require("@neat.is/types");
8978
+ var import_types22 = require("@neat.is/types");
8516
8979
  function extendsInfo(classHeritage) {
8517
8980
  for (let i = 0; i < classHeritage.namedChildCount; i++) {
8518
8981
  const child = classHeritage.namedChild(i);
@@ -8615,12 +9078,12 @@ async function addSymbolEdges(graph, services) {
8615
9078
  for (const service of services) {
8616
9079
  const serviceName = service.pkg.name;
8617
9080
  const tsPaths = await loadTsPathConfig(service.dir);
8618
- const files = await loadSourceFiles(service.dir);
9081
+ const files = await loadSourceFiles(service.dir, service.excludeDirs);
8619
9082
  for (const file of files) {
8620
- const parser = parserForExt5(import_node_path23.default.extname(file.path));
9083
+ const parser = parserForExt5(import_node_path26.default.extname(file.path));
8621
9084
  if (!parser) continue;
8622
- const relPath = toPosix(import_node_path23.default.relative(service.dir, file.path));
8623
- const fileDir = import_node_path23.default.dirname(file.path);
9085
+ const relPath = toPosix(import_node_path26.default.relative(service.dir, file.path));
9086
+ const fileDir = import_node_path26.default.dirname(file.path);
8624
9087
  let root;
8625
9088
  try {
8626
9089
  root = parseSource3(parser, file.content).rootNode;
@@ -8630,7 +9093,7 @@ async function addSymbolEdges(graph, services) {
8630
9093
  }
8631
9094
  const disambiguated = disambiguate(collectSymbolDefs(root));
8632
9095
  const locals = disambiguated.map(({ def, disambiguator }) => ({
8633
- sid: (0, import_types19.symbolId)(serviceName, relPath, def.qualname, disambiguator),
9096
+ sid: (0, import_types22.symbolId)(serviceName, relPath, def.qualname, disambiguator),
8634
9097
  qualname: def.qualname,
8635
9098
  kind: def.kind,
8636
9099
  startLine: def.startLine,
@@ -8659,10 +9122,10 @@ async function addSymbolEdges(graph, services) {
8659
9122
  if (local) return local.kind === wantKind ? local.sid : null;
8660
9123
  const imp = resolvedImports.get(name);
8661
9124
  if (imp) {
8662
- const candidate = (0, import_types19.symbolId)(serviceName, imp.targetRelPath, imp.importedName);
9125
+ const candidate = (0, import_types22.symbolId)(serviceName, imp.targetRelPath, imp.importedName);
8663
9126
  if (graph.hasNode(candidate)) {
8664
9127
  const node = graph.getNodeAttributes(candidate);
8665
- if (node.type === import_types19.NodeType.SymbolNode && node.kind === wantKind) return candidate;
9128
+ if (node.type === import_types22.NodeType.SymbolNode && node.kind === wantKind) return candidate;
8666
9129
  }
8667
9130
  }
8668
9131
  return null;
@@ -8689,7 +9152,7 @@ async function addSymbolEdges(graph, services) {
8689
9152
  sourceSid: self.sid,
8690
9153
  targetName: ext.name,
8691
9154
  wantKind: "class",
8692
- edgeType: import_types19.EdgeType.INHERITS,
9155
+ edgeType: import_types22.EdgeType.INHERITS,
8693
9156
  line: ext.line
8694
9157
  });
8695
9158
  }
@@ -8698,7 +9161,7 @@ async function addSymbolEdges(graph, services) {
8698
9161
  sourceSid: self.sid,
8699
9162
  targetName: impl.name,
8700
9163
  wantKind: "class",
8701
- edgeType: import_types19.EdgeType.IMPLEMENTS,
9164
+ edgeType: import_types22.EdgeType.IMPLEMENTS,
8702
9165
  line: impl.line
8703
9166
  });
8704
9167
  }
@@ -8714,7 +9177,7 @@ async function addSymbolEdges(graph, services) {
8714
9177
  sourceSid: caller.sid,
8715
9178
  targetName: fn.text,
8716
9179
  wantKind: "function",
8717
- edgeType: import_types19.EdgeType.CALLS,
9180
+ edgeType: import_types22.EdgeType.CALLS,
8718
9181
  line
8719
9182
  });
8720
9183
  }
@@ -8730,15 +9193,15 @@ async function addSymbolEdges(graph, services) {
8730
9193
  const targetSid = resolveTarget(req.targetName, req.wantKind);
8731
9194
  if (!targetSid) continue;
8732
9195
  if (targetSid === req.sourceSid) continue;
8733
- const edgeId = (0, import_types19.extractedEdgeId)(req.sourceSid, targetSid, req.edgeType);
9196
+ const edgeId = (0, import_types22.extractedEdgeId)(req.sourceSid, targetSid, req.edgeType);
8734
9197
  if (graph.hasEdge(edgeId)) continue;
8735
9198
  const edge = {
8736
9199
  id: edgeId,
8737
9200
  source: req.sourceSid,
8738
9201
  target: targetSid,
8739
9202
  type: req.edgeType,
8740
- provenance: import_types19.Provenance.EXTRACTED,
8741
- confidence: (0, import_types19.confidenceForExtracted)("structural"),
9203
+ provenance: import_types22.Provenance.EXTRACTED,
9204
+ confidence: (0, import_types22.confidenceForExtracted)("structural"),
8742
9205
  evidence: {
8743
9206
  file: relPath,
8744
9207
  line: req.line,
@@ -8755,9 +9218,9 @@ async function addSymbolEdges(graph, services) {
8755
9218
 
8756
9219
  // src/extract/actions.ts
8757
9220
  init_cjs_shims();
8758
- var import_node_path24 = __toESM(require("path"), 1);
9221
+ var import_node_path27 = __toESM(require("path"), 1);
8759
9222
  var import_tree_sitter5 = __toESM(require("tree-sitter"), 1);
8760
- var import_types20 = require("@neat.is/types");
9223
+ var import_types23 = require("@neat.is/types");
8761
9224
  function stringLiteralText2(node) {
8762
9225
  for (let i = 0; i < node.childCount; i++) {
8763
9226
  const child = node.child(i);
@@ -8899,12 +9362,12 @@ async function addServerActions(graph, services) {
8899
9362
  };
8900
9363
  if (deps["next"] === void 0) continue;
8901
9364
  const tsPaths = await loadTsPathConfig(service.dir);
8902
- const files = await loadSourceFiles(service.dir);
9365
+ const files = await loadSourceFiles(service.dir, service.excludeDirs);
8903
9366
  for (const file of files) {
8904
9367
  if (isTestPath(file.path)) continue;
8905
- const parser = parserForExt5(import_node_path24.default.extname(file.path));
9368
+ const parser = parserForExt5(import_node_path27.default.extname(file.path));
8906
9369
  if (!parser) continue;
8907
- const relPath = toPosix(import_node_path24.default.relative(service.dir, file.path));
9370
+ const relPath = toPosix(import_node_path27.default.relative(service.dir, file.path));
8908
9371
  let root;
8909
9372
  try {
8910
9373
  root = parseSource3(parser, file.content).rootNode;
@@ -8927,11 +9390,11 @@ async function addServerActions(graph, services) {
8927
9390
  nodesAdded += fn;
8928
9391
  edgesAdded += fe;
8929
9392
  for (const action of actions) {
8930
- const aid = (0, import_types20.serverActionId)(service.pkg.name, relPath, action.exportName);
9393
+ const aid = (0, import_types23.serverActionId)(service.pkg.name, relPath, action.exportName);
8931
9394
  if (!graph.hasNode(aid)) {
8932
9395
  const node = {
8933
9396
  id: aid,
8934
- type: import_types20.NodeType.ServerActionNode,
9397
+ type: import_types23.NodeType.ServerActionNode,
8935
9398
  name: action.exportName,
8936
9399
  service: service.pkg.name,
8937
9400
  module: relPath,
@@ -8943,15 +9406,15 @@ async function addServerActions(graph, services) {
8943
9406
  graph.addNode(aid, node);
8944
9407
  nodesAdded++;
8945
9408
  }
8946
- const containsId = (0, import_types20.extractedEdgeId)(fileNodeId, aid, import_types20.EdgeType.CONTAINS);
9409
+ const containsId = (0, import_types23.extractedEdgeId)(fileNodeId, aid, import_types23.EdgeType.CONTAINS);
8947
9410
  if (!graph.hasEdge(containsId)) {
8948
9411
  const edge = {
8949
9412
  id: containsId,
8950
9413
  source: fileNodeId,
8951
9414
  target: aid,
8952
- type: import_types20.EdgeType.CONTAINS,
8953
- provenance: import_types20.Provenance.EXTRACTED,
8954
- confidence: (0, import_types20.confidenceForExtracted)("structural"),
9415
+ type: import_types23.EdgeType.CONTAINS,
9416
+ provenance: import_types23.Provenance.EXTRACTED,
9417
+ confidence: (0, import_types23.confidenceForExtracted)("structural"),
8955
9418
  evidence: {
8956
9419
  file: relPath,
8957
9420
  line: action.line,
@@ -8965,10 +9428,10 @@ async function addServerActions(graph, services) {
8965
9428
  }
8966
9429
  for (const file of files) {
8967
9430
  if (isTestPath(file.path)) continue;
8968
- const parser = parserForExt5(import_node_path24.default.extname(file.path));
9431
+ const parser = parserForExt5(import_node_path27.default.extname(file.path));
8969
9432
  if (!parser) continue;
8970
- const relPath = toPosix(import_node_path24.default.relative(service.dir, file.path));
8971
- const fileDir = import_node_path24.default.dirname(file.path);
9433
+ const relPath = toPosix(import_node_path27.default.relative(service.dir, file.path));
9434
+ const fileDir = import_node_path27.default.dirname(file.path);
8972
9435
  let root;
8973
9436
  try {
8974
9437
  root = parseSource3(parser, file.content).rootNode;
@@ -8981,7 +9444,7 @@ async function addServerActions(graph, services) {
8981
9444
  for (const [local, binding] of bindings) {
8982
9445
  const target = await resolveJsImport(binding.specifier, fileDir, service.dir, tsPaths);
8983
9446
  if (!target) continue;
8984
- const aid = (0, import_types20.serverActionId)(service.pkg.name, target, binding.importedName);
9447
+ const aid = (0, import_types23.serverActionId)(service.pkg.name, target, binding.importedName);
8985
9448
  if (graph.hasNode(aid)) actionBindings.set(local, aid);
8986
9449
  }
8987
9450
  if (actionBindings.size === 0) continue;
@@ -8996,15 +9459,15 @@ async function addServerActions(graph, services) {
8996
9459
  nodesAdded += ensured.nodesAdded;
8997
9460
  edgesAdded += ensured.edgesAdded;
8998
9461
  }
8999
- const callsId = (0, import_types20.extractedEdgeId)(clientFileId, aid, import_types20.EdgeType.CALLS);
9462
+ const callsId = (0, import_types23.extractedEdgeId)(clientFileId, aid, import_types23.EdgeType.CALLS);
9000
9463
  if (graph.hasEdge(callsId)) continue;
9001
9464
  const edge = {
9002
9465
  id: callsId,
9003
9466
  source: clientFileId,
9004
9467
  target: aid,
9005
- type: import_types20.EdgeType.CALLS,
9006
- provenance: import_types20.Provenance.EXTRACTED,
9007
- confidence: (0, import_types20.confidenceForExtracted)("structural"),
9468
+ type: import_types23.EdgeType.CALLS,
9469
+ provenance: import_types23.Provenance.EXTRACTED,
9470
+ confidence: (0, import_types23.confidenceForExtracted)("structural"),
9008
9471
  evidence: {
9009
9472
  file: relPath,
9010
9473
  line,
@@ -9021,14 +9484,14 @@ async function addServerActions(graph, services) {
9021
9484
 
9022
9485
  // src/extract/databases/index.ts
9023
9486
  init_cjs_shims();
9024
- var import_node_path32 = __toESM(require("path"), 1);
9025
- var import_types21 = require("@neat.is/types");
9487
+ var import_node_path35 = __toESM(require("path"), 1);
9488
+ var import_types24 = require("@neat.is/types");
9026
9489
 
9027
9490
  // src/extract/databases/db-config-yaml.ts
9028
9491
  init_cjs_shims();
9029
- var import_node_path25 = __toESM(require("path"), 1);
9492
+ var import_node_path28 = __toESM(require("path"), 1);
9030
9493
  async function parse(serviceDir) {
9031
- const yamlPath = import_node_path25.default.join(serviceDir, "db-config.yaml");
9494
+ const yamlPath = import_node_path28.default.join(serviceDir, "db-config.yaml");
9032
9495
  if (!await exists2(yamlPath)) return [];
9033
9496
  const raw = await readYaml(yamlPath);
9034
9497
  return [
@@ -9046,13 +9509,13 @@ var dbConfigYamlParser = { name: "db-config.yaml", parse };
9046
9509
 
9047
9510
  // src/extract/databases/dotenv.ts
9048
9511
  init_cjs_shims();
9049
- var import_node_fs20 = require("fs");
9050
- var import_node_path27 = __toESM(require("path"), 1);
9512
+ var import_node_fs23 = require("fs");
9513
+ var import_node_path30 = __toESM(require("path"), 1);
9051
9514
 
9052
9515
  // src/extract/databases/shared.ts
9053
9516
  init_cjs_shims();
9054
- var import_node_fs19 = require("fs");
9055
- var import_node_path26 = __toESM(require("path"), 1);
9517
+ var import_node_fs22 = require("fs");
9518
+ var import_node_path29 = __toESM(require("path"), 1);
9056
9519
  function schemeToEngine(scheme) {
9057
9520
  const s = scheme.toLowerCase().split("+")[0];
9058
9521
  switch (s) {
@@ -9091,18 +9554,18 @@ function parseConnectionString(url) {
9091
9554
  }
9092
9555
  async function readIfExists(filePath) {
9093
9556
  try {
9094
- return await import_node_fs19.promises.readFile(filePath, "utf8");
9557
+ return await import_node_fs22.promises.readFile(filePath, "utf8");
9095
9558
  } catch {
9096
9559
  return null;
9097
9560
  }
9098
9561
  }
9099
9562
  async function resolveEnvVar(serviceDir, name) {
9100
- const entries = await import_node_fs19.promises.readdir(serviceDir, { withFileTypes: true }).catch(() => []);
9563
+ const entries = await import_node_fs22.promises.readdir(serviceDir, { withFileTypes: true }).catch(() => []);
9101
9564
  const envNames = entries.filter((e) => e.isFile() && (e.name === ".env" || e.name.startsWith(".env."))).map((e) => e.name);
9102
9565
  const rank = (n) => n === ".env.local" ? 0 : n === ".env" ? 1 : 2;
9103
9566
  envNames.sort((a, b) => rank(a) - rank(b) || a.localeCompare(b));
9104
9567
  for (const fileName of envNames) {
9105
- const content = await readIfExists(import_node_path26.default.join(serviceDir, fileName));
9568
+ const content = await readIfExists(import_node_path29.default.join(serviceDir, fileName));
9106
9569
  if (!content) continue;
9107
9570
  for (const line of content.split("\n")) {
9108
9571
  const trimmed = line.trim();
@@ -9121,7 +9584,7 @@ async function resolveEnvVar(serviceDir, name) {
9121
9584
  }
9122
9585
  async function findFirst(serviceDir, candidates) {
9123
9586
  for (const rel of candidates) {
9124
- const abs = import_node_path26.default.join(serviceDir, rel);
9587
+ const abs = import_node_path29.default.join(serviceDir, rel);
9125
9588
  const content = await readIfExists(abs);
9126
9589
  if (content !== null) return abs;
9127
9590
  }
@@ -9179,15 +9642,15 @@ function parseDotenvLine(line) {
9179
9642
  return { key, value };
9180
9643
  }
9181
9644
  async function parse2(serviceDir) {
9182
- const entries = await import_node_fs20.promises.readdir(serviceDir, { withFileTypes: true }).catch(() => []);
9645
+ const entries = await import_node_fs23.promises.readdir(serviceDir, { withFileTypes: true }).catch(() => []);
9183
9646
  const configs = [];
9184
9647
  const seen = /* @__PURE__ */ new Set();
9185
9648
  for (const entry of entries) {
9186
9649
  if (!entry.isFile()) continue;
9187
9650
  const match = isConfigFile(entry.name);
9188
9651
  if (!match.match || match.fileType !== "env") continue;
9189
- const filePath = import_node_path27.default.join(serviceDir, entry.name);
9190
- const content = await import_node_fs20.promises.readFile(filePath, "utf8");
9652
+ const filePath = import_node_path30.default.join(serviceDir, entry.name);
9653
+ const content = await import_node_fs23.promises.readFile(filePath, "utf8");
9191
9654
  for (const line of content.split("\n")) {
9192
9655
  const parsed = parseDotenvLine(line);
9193
9656
  if (!parsed) continue;
@@ -9206,9 +9669,9 @@ var dotenvParser = { name: ".env", parse: parse2 };
9206
9669
 
9207
9670
  // src/extract/databases/prisma.ts
9208
9671
  init_cjs_shims();
9209
- var import_node_path28 = __toESM(require("path"), 1);
9672
+ var import_node_path31 = __toESM(require("path"), 1);
9210
9673
  async function parse3(serviceDir) {
9211
- const schemaPath = import_node_path28.default.join(serviceDir, "prisma", "schema.prisma");
9674
+ const schemaPath = import_node_path31.default.join(serviceDir, "prisma", "schema.prisma");
9212
9675
  const content = await readIfExists(schemaPath);
9213
9676
  if (!content) return [];
9214
9677
  const block = content.match(/datasource\s+\w+\s*\{([^}]*)\}/s);
@@ -9366,10 +9829,10 @@ var knexParser = { name: "knex", parse: parse5 };
9366
9829
 
9367
9830
  // src/extract/databases/ormconfig.ts
9368
9831
  init_cjs_shims();
9369
- var import_node_path29 = __toESM(require("path"), 1);
9832
+ var import_node_path32 = __toESM(require("path"), 1);
9370
9833
  async function parse6(serviceDir) {
9371
9834
  for (const candidate of ["ormconfig.json", "ormconfig.yaml", "ormconfig.yml"]) {
9372
- const abs = import_node_path29.default.join(serviceDir, candidate);
9835
+ const abs = import_node_path32.default.join(serviceDir, candidate);
9373
9836
  if (!await exists2(abs)) continue;
9374
9837
  const raw = candidate.endsWith(".json") ? await readJson(abs) : await readYaml(abs);
9375
9838
  const entries = Array.isArray(raw) ? raw : [raw];
@@ -9429,9 +9892,9 @@ var typeormParser = { name: "typeorm", parse: parse7 };
9429
9892
 
9430
9893
  // src/extract/databases/sequelize.ts
9431
9894
  init_cjs_shims();
9432
- var import_node_path30 = __toESM(require("path"), 1);
9895
+ var import_node_path33 = __toESM(require("path"), 1);
9433
9896
  async function parse8(serviceDir) {
9434
- const configPath = import_node_path30.default.join(serviceDir, "config", "config.json");
9897
+ const configPath = import_node_path33.default.join(serviceDir, "config", "config.json");
9435
9898
  if (!await exists2(configPath)) return [];
9436
9899
  const raw = await readJson(configPath);
9437
9900
  const out = [];
@@ -9458,7 +9921,7 @@ var sequelizeParser = { name: "sequelize", parse: parse8 };
9458
9921
 
9459
9922
  // src/extract/databases/docker-compose.ts
9460
9923
  init_cjs_shims();
9461
- var import_node_path31 = __toESM(require("path"), 1);
9924
+ var import_node_path34 = __toESM(require("path"), 1);
9462
9925
  function portFromService(svc) {
9463
9926
  for (const raw of svc.ports ?? []) {
9464
9927
  const str = String(raw);
@@ -9485,7 +9948,7 @@ function databaseFromEnv(svc) {
9485
9948
  }
9486
9949
  async function parse9(serviceDir) {
9487
9950
  for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
9488
- const abs = import_node_path31.default.join(serviceDir, name);
9951
+ const abs = import_node_path34.default.join(serviceDir, name);
9489
9952
  if (!await exists2(abs)) continue;
9490
9953
  const raw = await readYaml(abs);
9491
9954
  if (!raw?.services) return [];
@@ -9526,8 +9989,8 @@ function compatibleDriversFor(engine) {
9526
9989
  }
9527
9990
  function toDatabaseNode(config) {
9528
9991
  return {
9529
- id: (0, import_types21.databaseId)(config.host),
9530
- type: import_types21.NodeType.DatabaseNode,
9992
+ id: (0, import_types24.databaseId)(config.host),
9993
+ type: import_types24.NodeType.DatabaseNode,
9531
9994
  name: config.database || config.host,
9532
9995
  engine: config.engine,
9533
9996
  engineVersion: config.engineVersion,
@@ -9657,7 +10120,7 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
9657
10120
  discoveredVia: mergedDiscoveredVia
9658
10121
  });
9659
10122
  }
9660
- const relConfigFile = toPosix(import_node_path32.default.relative(service.dir, config.sourceFile));
10123
+ const relConfigFile = toPosix(import_node_path35.default.relative(service.dir, config.sourceFile));
9661
10124
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
9662
10125
  graph,
9663
10126
  service.pkg.name,
@@ -9666,14 +10129,14 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
9666
10129
  );
9667
10130
  nodesAdded += fn;
9668
10131
  edgesAdded += fe;
9669
- const evidenceFile = toPosix(import_node_path32.default.relative(scanPath, config.sourceFile));
10132
+ const evidenceFile = toPosix(import_node_path35.default.relative(scanPath, config.sourceFile));
9670
10133
  const edge = {
9671
- id: (0, import_types3.extractedEdgeId)(fileNodeId, dbNode.id, import_types21.EdgeType.CONNECTS_TO),
10134
+ id: (0, import_types3.extractedEdgeId)(fileNodeId, dbNode.id, import_types24.EdgeType.CONNECTS_TO),
9672
10135
  source: fileNodeId,
9673
10136
  target: dbNode.id,
9674
- type: import_types21.EdgeType.CONNECTS_TO,
9675
- provenance: import_types21.Provenance.EXTRACTED,
9676
- confidence: (0, import_types21.confidenceForExtracted)("structural"),
10137
+ type: import_types24.EdgeType.CONNECTS_TO,
10138
+ provenance: import_types24.Provenance.EXTRACTED,
10139
+ confidence: (0, import_types24.confidenceForExtracted)("structural"),
9677
10140
  evidence: { file: evidenceFile }
9678
10141
  };
9679
10142
  if (!graph.hasEdge(edge.id)) {
@@ -9684,26 +10147,26 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
9684
10147
  if (allConfigs.length === 1) {
9685
10148
  const primary = allConfigs[0];
9686
10149
  service.node.dbConnectionTarget = primary.port ? `${primary.host}:${primary.port}` : primary.host;
9687
- const relPath = import_node_path32.default.relative(scanPath, primary.sourceFile);
9688
- const cfgId = (0, import_types21.configId)(relPath);
10150
+ const relPath = import_node_path35.default.relative(scanPath, primary.sourceFile);
10151
+ const cfgId = (0, import_types24.configId)(relPath);
9689
10152
  if (!graph.hasNode(cfgId)) {
9690
10153
  const cfgNode = {
9691
10154
  id: cfgId,
9692
- type: import_types21.NodeType.ConfigNode,
9693
- name: import_node_path32.default.basename(primary.sourceFile),
10155
+ type: import_types24.NodeType.ConfigNode,
10156
+ name: import_node_path35.default.basename(primary.sourceFile),
9694
10157
  path: relPath,
9695
- fileType: isConfigFile(import_node_path32.default.basename(primary.sourceFile)).fileType || "config"
10158
+ fileType: isConfigFile(import_node_path35.default.basename(primary.sourceFile)).fileType || "config"
9696
10159
  };
9697
10160
  graph.addNode(cfgId, cfgNode);
9698
10161
  nodesAdded++;
9699
10162
  }
9700
10163
  const cfgEdge = {
9701
- id: (0, import_types3.extractedEdgeId)(service.node.id, cfgId, import_types21.EdgeType.CONFIGURED_BY),
10164
+ id: (0, import_types3.extractedEdgeId)(service.node.id, cfgId, import_types24.EdgeType.CONFIGURED_BY),
9702
10165
  source: service.node.id,
9703
10166
  target: cfgId,
9704
- type: import_types21.EdgeType.CONFIGURED_BY,
9705
- provenance: import_types21.Provenance.EXTRACTED,
9706
- confidence: (0, import_types21.confidenceForExtracted)("structural"),
10167
+ type: import_types24.EdgeType.CONFIGURED_BY,
10168
+ provenance: import_types24.Provenance.EXTRACTED,
10169
+ confidence: (0, import_types24.confidenceForExtracted)("structural"),
9707
10170
  evidence: { file: toPosix(relPath) }
9708
10171
  };
9709
10172
  if (!graph.hasEdge(cfgEdge.id)) {
@@ -9733,17 +10196,19 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
9733
10196
 
9734
10197
  // src/extract/configs.ts
9735
10198
  init_cjs_shims();
9736
- var import_node_fs21 = require("fs");
9737
- var import_node_path33 = __toESM(require("path"), 1);
9738
- var import_types22 = require("@neat.is/types");
9739
- async function walkConfigFiles(dir) {
10199
+ var import_node_fs24 = require("fs");
10200
+ var import_node_path36 = __toESM(require("path"), 1);
10201
+ var import_types25 = require("@neat.is/types");
10202
+ async function walkConfigFiles(dir, excludeDirs = []) {
10203
+ const excluded = new Set(excludeDirs.map((d) => import_node_path36.default.resolve(d)));
9740
10204
  const out = [];
9741
10205
  async function walk9(current) {
9742
- const entries = await import_node_fs21.promises.readdir(current, { withFileTypes: true });
10206
+ const entries = await import_node_fs24.promises.readdir(current, { withFileTypes: true });
9743
10207
  for (const entry of entries) {
9744
- const full = import_node_path33.default.join(current, entry.name);
10208
+ const full = import_node_path36.default.join(current, entry.name);
9745
10209
  if (entry.isDirectory()) {
9746
10210
  if (IGNORED_DIRS.has(entry.name)) continue;
10211
+ if (excluded.has(import_node_path36.default.resolve(full))) continue;
9747
10212
  if (await isPythonVenvDir(full)) continue;
9748
10213
  await walk9(full);
9749
10214
  } else if (entry.isFile() && isConfigFile(entry.name).match) {
@@ -9758,21 +10223,21 @@ async function addConfigNodes(graph, services, scanPath) {
9758
10223
  let nodesAdded = 0;
9759
10224
  let edgesAdded = 0;
9760
10225
  for (const service of services) {
9761
- const configFiles = await walkConfigFiles(service.dir);
10226
+ const configFiles = await walkConfigFiles(service.dir, service.excludeDirs);
9762
10227
  for (const file of configFiles) {
9763
- const relPath = import_node_path33.default.relative(scanPath, file);
10228
+ const relPath = import_node_path36.default.relative(scanPath, file);
9764
10229
  const node = {
9765
- id: (0, import_types22.configId)(relPath),
9766
- type: import_types22.NodeType.ConfigNode,
9767
- name: import_node_path33.default.basename(file),
10230
+ id: (0, import_types25.configId)(relPath),
10231
+ type: import_types25.NodeType.ConfigNode,
10232
+ name: import_node_path36.default.basename(file),
9768
10233
  path: relPath,
9769
- fileType: isConfigFile(import_node_path33.default.basename(file)).fileType
10234
+ fileType: isConfigFile(import_node_path36.default.basename(file)).fileType
9770
10235
  };
9771
10236
  if (!graph.hasNode(node.id)) {
9772
10237
  graph.addNode(node.id, node);
9773
10238
  nodesAdded++;
9774
10239
  }
9775
- const relToService = toPosix(import_node_path33.default.relative(service.dir, file));
10240
+ const relToService = toPosix(import_node_path36.default.relative(service.dir, file));
9776
10241
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
9777
10242
  graph,
9778
10243
  service.pkg.name,
@@ -9782,13 +10247,13 @@ async function addConfigNodes(graph, services, scanPath) {
9782
10247
  nodesAdded += fn;
9783
10248
  edgesAdded += fe;
9784
10249
  const edge = {
9785
- id: (0, import_types3.extractedEdgeId)(fileNodeId, node.id, import_types22.EdgeType.CONFIGURED_BY),
10250
+ id: (0, import_types3.extractedEdgeId)(fileNodeId, node.id, import_types25.EdgeType.CONFIGURED_BY),
9786
10251
  source: fileNodeId,
9787
10252
  target: node.id,
9788
- type: import_types22.EdgeType.CONFIGURED_BY,
9789
- provenance: import_types22.Provenance.EXTRACTED,
9790
- confidence: (0, import_types22.confidenceForExtracted)("structural"),
9791
- evidence: { file: relPath.split(import_node_path33.default.sep).join("/") }
10253
+ type: import_types25.EdgeType.CONFIGURED_BY,
10254
+ provenance: import_types25.Provenance.EXTRACTED,
10255
+ confidence: (0, import_types25.confidenceForExtracted)("structural"),
10256
+ evidence: { file: relPath.split(import_node_path36.default.sep).join("/") }
9792
10257
  };
9793
10258
  if (!graph.hasEdge(edge.id)) {
9794
10259
  graph.addEdgeWithKey(edge.id, edge.source, edge.target, edge);
@@ -9801,9 +10266,9 @@ async function addConfigNodes(graph, services, scanPath) {
9801
10266
 
9802
10267
  // src/extract/proto.ts
9803
10268
  init_cjs_shims();
9804
- var import_node_fs22 = require("fs");
9805
- var import_node_path34 = __toESM(require("path"), 1);
9806
- var import_types23 = require("@neat.is/types");
10269
+ var import_node_fs25 = require("fs");
10270
+ var import_node_path37 = __toESM(require("path"), 1);
10271
+ var import_types26 = require("@neat.is/types");
9807
10272
  var PROTO_EXTENSION = ".proto";
9808
10273
  function packageOf(content) {
9809
10274
  const m = content.match(/^\s*package\s+([A-Za-z_][A-Za-z0-9_.]*)\s*;/m);
@@ -9839,17 +10304,19 @@ function grpcMethodsFromProto(content, fqPackage) {
9839
10304
  }
9840
10305
  return out;
9841
10306
  }
9842
- async function walkProtoFiles(dir) {
10307
+ async function walkProtoFiles(dir, excludeDirs = []) {
10308
+ const excluded = new Set(excludeDirs.map((d) => import_node_path37.default.resolve(d)));
9843
10309
  const out = [];
9844
10310
  async function walk9(current) {
9845
- const entries = await import_node_fs22.promises.readdir(current, { withFileTypes: true }).catch(() => []);
10311
+ const entries = await import_node_fs25.promises.readdir(current, { withFileTypes: true }).catch(() => []);
9846
10312
  for (const entry of entries) {
9847
- const full = import_node_path34.default.join(current, entry.name);
10313
+ const full = import_node_path37.default.join(current, entry.name);
9848
10314
  if (entry.isDirectory()) {
9849
10315
  if (IGNORED_DIRS.has(entry.name)) continue;
10316
+ if (excluded.has(import_node_path37.default.resolve(full))) continue;
9850
10317
  if (await isPythonVenvDir(full)) continue;
9851
10318
  await walk9(full);
9852
- } else if (entry.isFile() && import_node_path34.default.extname(entry.name) === PROTO_EXTENSION) {
10319
+ } else if (entry.isFile() && import_node_path37.default.extname(entry.name) === PROTO_EXTENSION) {
9853
10320
  out.push(full);
9854
10321
  }
9855
10322
  }
@@ -9861,13 +10328,13 @@ async function addGrpcMethods(graph, services) {
9861
10328
  let nodesAdded = 0;
9862
10329
  let edgesAdded = 0;
9863
10330
  for (const service of services) {
9864
- const protoPaths = await walkProtoFiles(service.dir);
10331
+ const protoPaths = await walkProtoFiles(service.dir, service.excludeDirs);
9865
10332
  for (const protoPath of protoPaths) {
9866
10333
  if (isTestPath(protoPath)) continue;
9867
- const relFile = toPosix(import_node_path34.default.relative(service.dir, protoPath));
10334
+ const relFile = toPosix(import_node_path37.default.relative(service.dir, protoPath));
9868
10335
  let content;
9869
10336
  try {
9870
- content = await import_node_fs22.promises.readFile(protoPath, "utf8");
10337
+ content = await import_node_fs25.promises.readFile(protoPath, "utf8");
9871
10338
  } catch (err) {
9872
10339
  recordExtractionError("proto extraction", protoPath, err);
9873
10340
  continue;
@@ -9881,11 +10348,11 @@ async function addGrpcMethods(graph, services) {
9881
10348
  }
9882
10349
  if (methods.length === 0) continue;
9883
10350
  for (const method of methods) {
9884
- const mid = (0, import_types23.grpcMethodId)(method.rpcService, method.rpcMethod);
10351
+ const mid = (0, import_types26.grpcMethodId)(method.rpcService, method.rpcMethod);
9885
10352
  if (!graph.hasNode(mid)) {
9886
10353
  const node = {
9887
10354
  id: mid,
9888
- type: import_types23.NodeType.GrpcMethodNode,
10355
+ type: import_types26.NodeType.GrpcMethodNode,
9889
10356
  name: `${method.rpcService}/${method.rpcMethod}`,
9890
10357
  rpcService: method.rpcService,
9891
10358
  rpcMethod: method.rpcMethod,
@@ -9896,15 +10363,15 @@ async function addGrpcMethods(graph, services) {
9896
10363
  graph.addNode(mid, node);
9897
10364
  nodesAdded++;
9898
10365
  }
9899
- const containsId = (0, import_types23.extractedEdgeId)(service.node.id, mid, import_types23.EdgeType.CONTAINS);
10366
+ const containsId = (0, import_types26.extractedEdgeId)(service.node.id, mid, import_types26.EdgeType.CONTAINS);
9900
10367
  if (!graph.hasEdge(containsId)) {
9901
10368
  const edge = {
9902
10369
  id: containsId,
9903
10370
  source: service.node.id,
9904
10371
  target: mid,
9905
- type: import_types23.EdgeType.CONTAINS,
9906
- provenance: import_types23.Provenance.EXTRACTED,
9907
- confidence: (0, import_types23.confidenceForExtracted)("structural"),
10372
+ type: import_types26.EdgeType.CONTAINS,
10373
+ provenance: import_types26.Provenance.EXTRACTED,
10374
+ confidence: (0, import_types26.confidenceForExtracted)("structural"),
9908
10375
  evidence: {
9909
10376
  file: relFile,
9910
10377
  line: method.line,
@@ -9922,16 +10389,16 @@ async function addGrpcMethods(graph, services) {
9922
10389
 
9923
10390
  // src/extract/calls/index.ts
9924
10391
  init_cjs_shims();
9925
- var import_types41 = require("@neat.is/types");
10392
+ var import_types44 = require("@neat.is/types");
9926
10393
 
9927
10394
  // src/extract/calls/http.ts
9928
10395
  init_cjs_shims();
9929
- var import_node_path35 = __toESM(require("path"), 1);
10396
+ var import_node_path38 = __toESM(require("path"), 1);
9930
10397
  var import_tree_sitter6 = __toESM(require("tree-sitter"), 1);
9931
10398
  var import_tree_sitter_javascript4 = __toESM(require("tree-sitter-javascript"), 1);
9932
10399
  var import_tree_sitter_typescript2 = __toESM(require("tree-sitter-typescript"), 1);
9933
10400
  var import_tree_sitter_python4 = __toESM(require("tree-sitter-python"), 1);
9934
- var import_types24 = require("@neat.is/types");
10401
+ var import_types27 = require("@neat.is/types");
9935
10402
  var STRING_LITERAL_NODE_TYPES = /* @__PURE__ */ new Set(["string_fragment", "string_content"]);
9936
10403
  var JSX_EXTERNAL_LINK_TAGS = /* @__PURE__ */ new Set(["a", "Link", "NavLink", "ExternalLink", "Anchor"]);
9937
10404
  function isInsideJsxExternalLink(node) {
@@ -10005,11 +10472,11 @@ async function addHttpCallEdges(graph, services) {
10005
10472
  let nodesAdded = 0;
10006
10473
  let edgesAdded = 0;
10007
10474
  for (const service of services) {
10008
- const files = await loadSourceFiles(service.dir);
10475
+ const files = await loadSourceFiles(service.dir, service.excludeDirs);
10009
10476
  const seen = /* @__PURE__ */ new Set();
10010
10477
  for (const file of files) {
10011
10478
  if (isTestPath(file.path)) continue;
10012
- const parser = parserForExt(import_node_path35.default.extname(file.path), parserCache);
10479
+ const parser = parserForExt(import_node_path38.default.extname(file.path), parserCache);
10013
10480
  let sites;
10014
10481
  try {
10015
10482
  sites = callsFromSource(file.content, parser, knownHosts);
@@ -10018,14 +10485,14 @@ async function addHttpCallEdges(graph, services) {
10018
10485
  continue;
10019
10486
  }
10020
10487
  if (sites.length === 0) continue;
10021
- const relFile = toPosix(import_node_path35.default.relative(service.dir, file.path));
10488
+ const relFile = toPosix(import_node_path38.default.relative(service.dir, file.path));
10022
10489
  for (const site of sites) {
10023
10490
  const targetId = hostToNodeId.get(site.host);
10024
10491
  if (!targetId || targetId === service.node.id) continue;
10025
10492
  const dedupKey = `${relFile}|${targetId}`;
10026
10493
  if (seen.has(dedupKey)) continue;
10027
10494
  seen.add(dedupKey);
10028
- const confidence = (0, import_types24.confidenceForExtracted)("url-literal-service-target");
10495
+ const confidence = (0, import_types27.confidenceForExtracted)("url-literal-service-target");
10029
10496
  const ev = {
10030
10497
  file: relFile,
10031
10498
  line: site.line,
@@ -10039,25 +10506,25 @@ async function addHttpCallEdges(graph, services) {
10039
10506
  );
10040
10507
  nodesAdded += n;
10041
10508
  edgesAdded += e;
10042
- if (!(0, import_types24.passesExtractedFloor)(confidence)) {
10509
+ if (!(0, import_types27.passesExtractedFloor)(confidence)) {
10043
10510
  noteExtractedDropped({
10044
10511
  source: fileNodeId,
10045
10512
  target: targetId,
10046
- type: import_types24.EdgeType.CALLS,
10513
+ type: import_types27.EdgeType.CALLS,
10047
10514
  confidence,
10048
10515
  confidenceKind: "url-literal-service-target",
10049
10516
  evidence: ev
10050
10517
  });
10051
10518
  continue;
10052
10519
  }
10053
- const edgeId = (0, import_types3.extractedEdgeId)(fileNodeId, targetId, import_types24.EdgeType.CALLS);
10520
+ const edgeId = (0, import_types3.extractedEdgeId)(fileNodeId, targetId, import_types27.EdgeType.CALLS);
10054
10521
  if (!graph.hasEdge(edgeId)) {
10055
10522
  const edge = {
10056
10523
  id: edgeId,
10057
10524
  source: fileNodeId,
10058
10525
  target: targetId,
10059
- type: import_types24.EdgeType.CALLS,
10060
- provenance: import_types24.Provenance.EXTRACTED,
10526
+ type: import_types27.EdgeType.CALLS,
10527
+ provenance: import_types27.Provenance.EXTRACTED,
10061
10528
  confidence,
10062
10529
  evidence: ev
10063
10530
  };
@@ -10072,10 +10539,10 @@ async function addHttpCallEdges(graph, services) {
10072
10539
 
10073
10540
  // src/extract/calls/route-match.ts
10074
10541
  init_cjs_shims();
10075
- var import_node_path36 = __toESM(require("path"), 1);
10542
+ var import_node_path39 = __toESM(require("path"), 1);
10076
10543
  var import_tree_sitter7 = __toESM(require("tree-sitter"), 1);
10077
10544
  var import_tree_sitter_javascript5 = __toESM(require("tree-sitter-javascript"), 1);
10078
- var import_types25 = require("@neat.is/types");
10545
+ var import_types28 = require("@neat.is/types");
10079
10546
  var PARSE_CHUNK5 = 16384;
10080
10547
  function parseSource5(parser, source) {
10081
10548
  return parser.parse(
@@ -10240,9 +10707,9 @@ function buildRouteIndex(graph) {
10240
10707
  const index = /* @__PURE__ */ new Map();
10241
10708
  graph.forEachNode((_id, attrs) => {
10242
10709
  const node = attrs;
10243
- if (node.type !== import_types25.NodeType.RouteNode) return;
10710
+ if (node.type !== import_types28.NodeType.RouteNode) return;
10244
10711
  const route = attrs;
10245
- const owner = (0, import_types25.serviceId)(route.service);
10712
+ const owner = (0, import_types28.serviceId)(route.service);
10246
10713
  const entry = {
10247
10714
  method: route.method.toUpperCase(),
10248
10715
  normalizedPath: normalizePathTemplate(route.pathTemplate),
@@ -10267,11 +10734,11 @@ async function addRouteCallEdges(graph, services) {
10267
10734
  let nodesAdded = 0;
10268
10735
  let edgesAdded = 0;
10269
10736
  for (const service of services) {
10270
- const files = await loadSourceFiles(service.dir);
10737
+ const files = await loadSourceFiles(service.dir, service.excludeDirs);
10271
10738
  const seen = /* @__PURE__ */ new Set();
10272
10739
  for (const file of files) {
10273
10740
  if (isTestPath(file.path)) continue;
10274
- if (!JS_CLIENT_EXTENSIONS.has(import_node_path36.default.extname(file.path))) continue;
10741
+ if (!JS_CLIENT_EXTENSIONS.has(import_node_path39.default.extname(file.path))) continue;
10275
10742
  let sites;
10276
10743
  try {
10277
10744
  sites = clientCallSitesFromSource(file.content, jsParser, knownHosts);
@@ -10280,7 +10747,7 @@ async function addRouteCallEdges(graph, services) {
10280
10747
  continue;
10281
10748
  }
10282
10749
  if (sites.length === 0) continue;
10283
- const relFile = toPosix(import_node_path36.default.relative(service.dir, file.path));
10750
+ const relFile = toPosix(import_node_path39.default.relative(service.dir, file.path));
10284
10751
  for (const site of sites) {
10285
10752
  const serverServiceId = hostToNodeId.get(site.host);
10286
10753
  if (!serverServiceId || serverServiceId === service.node.id) continue;
@@ -10300,7 +10767,7 @@ async function addRouteCallEdges(graph, services) {
10300
10767
  );
10301
10768
  nodesAdded += n;
10302
10769
  edgesAdded += e;
10303
- const confidence = (0, import_types25.confidenceForExtracted)("verified-call-site");
10770
+ const confidence = (0, import_types28.confidenceForExtracted)("verified-call-site");
10304
10771
  const ev = {
10305
10772
  file: relFile,
10306
10773
  line: site.line,
@@ -10308,25 +10775,25 @@ async function addRouteCallEdges(graph, services) {
10308
10775
  method: site.method ?? match.method,
10309
10776
  pathTemplate: site.pathTemplate
10310
10777
  };
10311
- if (!(0, import_types25.passesExtractedFloor)(confidence)) {
10778
+ if (!(0, import_types28.passesExtractedFloor)(confidence)) {
10312
10779
  noteExtractedDropped({
10313
10780
  source: fileNodeId,
10314
10781
  target: match.routeNodeId,
10315
- type: import_types25.EdgeType.CALLS,
10782
+ type: import_types28.EdgeType.CALLS,
10316
10783
  confidence,
10317
10784
  confidenceKind: "verified-call-site",
10318
10785
  evidence: ev
10319
10786
  });
10320
10787
  continue;
10321
10788
  }
10322
- const edgeId = (0, import_types3.extractedEdgeId)(fileNodeId, match.routeNodeId, import_types25.EdgeType.CALLS);
10789
+ const edgeId = (0, import_types3.extractedEdgeId)(fileNodeId, match.routeNodeId, import_types28.EdgeType.CALLS);
10323
10790
  if (!graph.hasEdge(edgeId)) {
10324
10791
  const edge = {
10325
10792
  id: edgeId,
10326
10793
  source: fileNodeId,
10327
10794
  target: match.routeNodeId,
10328
- type: import_types25.EdgeType.CALLS,
10329
- provenance: import_types25.Provenance.EXTRACTED,
10795
+ type: import_types28.EdgeType.CALLS,
10796
+ provenance: import_types28.Provenance.EXTRACTED,
10330
10797
  confidence,
10331
10798
  evidence: ev
10332
10799
  };
@@ -10341,8 +10808,8 @@ async function addRouteCallEdges(graph, services) {
10341
10808
 
10342
10809
  // src/extract/calls/kafka.ts
10343
10810
  init_cjs_shims();
10344
- var import_node_path37 = __toESM(require("path"), 1);
10345
- var import_types26 = require("@neat.is/types");
10811
+ var import_node_path40 = __toESM(require("path"), 1);
10812
+ var import_types29 = require("@neat.is/types");
10346
10813
  var PRODUCER_TOPIC_RE = /(?:producer|kafkaProducer)[\s\S]{0,40}?\.send\s*\(\s*\{[\s\S]{0,200}?topic\s*:\s*['"`]([^'"`]+)['"`]/g;
10347
10814
  var CONSUMER_TOPIC_RE = /(?:consumer|kafkaConsumer)[\s\S]{0,40}?\.(?:subscribe|run)\s*\(\s*\{[\s\S]{0,200}?topic[s]?\s*:\s*(?:\[\s*)?['"`]([^'"`]+)['"`]/g;
10348
10815
  function findAll(re, text) {
@@ -10363,7 +10830,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
10363
10830
  seen.add(key);
10364
10831
  const line = lineOf(file.content, topic);
10365
10832
  out.push({
10366
- infraId: (0, import_types26.infraId)("kafka-topic", topic),
10833
+ infraId: (0, import_types29.infraId)("kafka-topic", topic),
10367
10834
  name: topic,
10368
10835
  kind: "kafka-topic",
10369
10836
  edgeType,
@@ -10372,7 +10839,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
10372
10839
  // tier (ADR-066).
10373
10840
  confidenceKind: "verified-call-site",
10374
10841
  evidence: {
10375
- file: import_node_path37.default.relative(serviceDir, file.path),
10842
+ file: import_node_path40.default.relative(serviceDir, file.path),
10376
10843
  line,
10377
10844
  snippet: snippet(file.content, line)
10378
10845
  }
@@ -10385,8 +10852,8 @@ function kafkaEndpointsFromFile(file, serviceDir) {
10385
10852
 
10386
10853
  // src/extract/calls/redis.ts
10387
10854
  init_cjs_shims();
10388
- var import_node_path38 = __toESM(require("path"), 1);
10389
- var import_types27 = require("@neat.is/types");
10855
+ var import_node_path41 = __toESM(require("path"), 1);
10856
+ var import_types30 = require("@neat.is/types");
10390
10857
  var REDIS_URL_RE = /redis(?:s)?:\/\/(?:[^@'"`\s]+@)?([^:/'"`\s]+)(?::(\d+))?/g;
10391
10858
  function redisEndpointsFromFile(file, serviceDir) {
10392
10859
  const out = [];
@@ -10399,7 +10866,7 @@ function redisEndpointsFromFile(file, serviceDir) {
10399
10866
  seen.add(host);
10400
10867
  const line = lineOf(file.content, host);
10401
10868
  out.push({
10402
- infraId: (0, import_types27.infraId)("redis", host),
10869
+ infraId: (0, import_types30.infraId)("redis", host),
10403
10870
  name: host,
10404
10871
  kind: "redis",
10405
10872
  edgeType: "CALLS",
@@ -10408,7 +10875,7 @@ function redisEndpointsFromFile(file, serviceDir) {
10408
10875
  // support tier (ADR-066).
10409
10876
  confidenceKind: "url-with-structural-support",
10410
10877
  evidence: {
10411
- file: import_node_path38.default.relative(serviceDir, file.path),
10878
+ file: import_node_path41.default.relative(serviceDir, file.path),
10412
10879
  line,
10413
10880
  snippet: snippet(file.content, line)
10414
10881
  }
@@ -10419,8 +10886,8 @@ function redisEndpointsFromFile(file, serviceDir) {
10419
10886
 
10420
10887
  // src/extract/calls/aws.ts
10421
10888
  init_cjs_shims();
10422
- var import_node_path39 = __toESM(require("path"), 1);
10423
- var import_types28 = require("@neat.is/types");
10889
+ var import_node_path42 = __toESM(require("path"), 1);
10890
+ var import_types31 = require("@neat.is/types");
10424
10891
  var S3_BUCKET_RE = /Bucket\s*:\s*['"`]([^'"`]+)['"`]/g;
10425
10892
  var DYNAMO_TABLE_RE = /TableName\s*:\s*['"`]([^'"`]+)['"`]/g;
10426
10893
  function hasMarker(text, markers) {
@@ -10444,7 +10911,7 @@ function awsEndpointsFromFile(file, serviceDir) {
10444
10911
  seen.add(key);
10445
10912
  const line = lineOf(file.content, name);
10446
10913
  out.push({
10447
- infraId: (0, import_types28.infraId)(kind, name),
10914
+ infraId: (0, import_types31.infraId)(kind, name),
10448
10915
  name,
10449
10916
  kind,
10450
10917
  edgeType: "CALLS",
@@ -10453,7 +10920,7 @@ function awsEndpointsFromFile(file, serviceDir) {
10453
10920
  // (ADR-066).
10454
10921
  confidenceKind: "verified-call-site",
10455
10922
  evidence: {
10456
- file: import_node_path39.default.relative(serviceDir, file.path),
10923
+ file: import_node_path42.default.relative(serviceDir, file.path),
10457
10924
  line,
10458
10925
  snippet: snippet(file.content, line)
10459
10926
  }
@@ -10478,8 +10945,8 @@ function awsEndpointsFromFile(file, serviceDir) {
10478
10945
 
10479
10946
  // src/extract/calls/grpc.ts
10480
10947
  init_cjs_shims();
10481
- var import_node_path40 = __toESM(require("path"), 1);
10482
- var import_types29 = require("@neat.is/types");
10948
+ var import_node_path43 = __toESM(require("path"), 1);
10949
+ var import_types32 = require("@neat.is/types");
10483
10950
  var GRPC_CLIENT_RE = /new\s+([A-Z][A-Za-z0-9_]*)Client\s*\(\s*['"`]?([^,'"`)]+)?/g;
10484
10951
  var AWS_SDK_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@aws-sdk\/client-([a-z0-9-]+)['"`]/g;
10485
10952
  var GRPC_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@grpc\/grpc-js['"`]|_grpc_pb['"`]/;
@@ -10528,7 +10995,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
10528
10995
  const { kind } = classified;
10529
10996
  const line = lineOf(file.content, m[0]);
10530
10997
  out.push({
10531
- infraId: (0, import_types29.infraId)(kind, name),
10998
+ infraId: (0, import_types32.infraId)(kind, name),
10532
10999
  name,
10533
11000
  kind,
10534
11001
  edgeType: "CALLS",
@@ -10537,7 +11004,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
10537
11004
  // tier (ADR-066).
10538
11005
  confidenceKind: "verified-call-site",
10539
11006
  evidence: {
10540
- file: import_node_path40.default.relative(serviceDir, file.path),
11007
+ file: import_node_path43.default.relative(serviceDir, file.path),
10541
11008
  line,
10542
11009
  snippet: snippet(file.content, line)
10543
11010
  }
@@ -10548,8 +11015,8 @@ function grpcEndpointsFromFile(file, serviceDir) {
10548
11015
 
10549
11016
  // src/extract/calls/supabase.ts
10550
11017
  init_cjs_shims();
10551
- var import_node_path41 = __toESM(require("path"), 1);
10552
- var import_types30 = require("@neat.is/types");
11018
+ var import_node_path44 = __toESM(require("path"), 1);
11019
+ var import_types33 = require("@neat.is/types");
10553
11020
  var SUPABASE_JS_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@supabase\/supabase-js['"`]/;
10554
11021
  var SUPABASE_SSR_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@supabase\/ssr['"`]/;
10555
11022
  var SUPABASE_CLIENT_RE = /\b(createClient|createServerClient|createBrowserClient)\s*\(\s*(?:['"`]([^'"`]*)['"`])?/g;
@@ -10597,7 +11064,7 @@ function supabaseEndpointsFromFile(file, serviceDir) {
10597
11064
  seen.add(name);
10598
11065
  const line = lineOf(file.content, m[0]);
10599
11066
  out.push({
10600
- infraId: (0, import_types30.infraId)("supabase", name),
11067
+ infraId: (0, import_types33.infraId)("supabase", name),
10601
11068
  name,
10602
11069
  kind: "supabase",
10603
11070
  edgeType: "CALLS",
@@ -10607,7 +11074,7 @@ function supabaseEndpointsFromFile(file, serviceDir) {
10607
11074
  // tier (ADR-066), the same grade aws.ts / grpc.ts emit at.
10608
11075
  confidenceKind: "verified-call-site",
10609
11076
  evidence: {
10610
- file: import_node_path41.default.relative(serviceDir, file.path),
11077
+ file: import_node_path44.default.relative(serviceDir, file.path),
10611
11078
  line,
10612
11079
  snippet: snippet(file.content, line)
10613
11080
  }
@@ -10628,13 +11095,13 @@ function supabaseEndpointsFromFile(file, serviceDir) {
10628
11095
  seen.add(key);
10629
11096
  const line = lineOf(file.content, am[0]);
10630
11097
  out.push({
10631
- infraId: (0, import_types30.infraId)(kind, resource),
11098
+ infraId: (0, import_types33.infraId)(kind, resource),
10632
11099
  name: resource,
10633
11100
  kind,
10634
11101
  edgeType: "CALLS",
10635
11102
  confidenceKind: "verified-call-site",
10636
11103
  evidence: {
10637
- file: import_node_path41.default.relative(serviceDir, file.path),
11104
+ file: import_node_path44.default.relative(serviceDir, file.path),
10638
11105
  line,
10639
11106
  snippet: snippet(file.content, line)
10640
11107
  }
@@ -10646,10 +11113,10 @@ function supabaseEndpointsFromFile(file, serviceDir) {
10646
11113
 
10647
11114
  // src/extract/calls/firestore.ts
10648
11115
  init_cjs_shims();
10649
- var import_node_path42 = __toESM(require("path"), 1);
11116
+ var import_node_path45 = __toESM(require("path"), 1);
10650
11117
  var import_tree_sitter8 = __toESM(require("tree-sitter"), 1);
10651
11118
  var import_tree_sitter_javascript6 = __toESM(require("tree-sitter-javascript"), 1);
10652
- var import_types31 = require("@neat.is/types");
11119
+ var import_types34 = require("@neat.is/types");
10653
11120
  var FIRESTORE_CLIENT_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])firebase\/firestore['"`]/;
10654
11121
  var FIRESTORE_ADMIN_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])firebase-admin(?:\/firestore)?['"`]/;
10655
11122
  function parserForExt2(ext) {
@@ -10817,7 +11284,7 @@ function firestoreEndpointsFromFile(file, serviceDir) {
10817
11284
  const hasAdmin = FIRESTORE_ADMIN_IMPORT_RE.test(file.content);
10818
11285
  if (!hasClient && !hasAdmin) return [];
10819
11286
  const fileSdk = hasClient && !hasAdmin ? "client" : hasAdmin && !hasClient ? "admin" : null;
10820
- const tree = parseSource3(parserForExt2(import_node_path42.default.extname(file.path)), file.content);
11287
+ const tree = parseSource3(parserForExt2(import_node_path45.default.extname(file.path)), file.content);
10821
11288
  const clientVars = firestoreClientVars(tree.rootNode);
10822
11289
  const collLine = /* @__PURE__ */ new Map();
10823
11290
  const writes = /* @__PURE__ */ new Map();
@@ -10910,7 +11377,7 @@ function firestoreEndpointsFromFile(file, serviceDir) {
10910
11377
  }
10911
11378
  }
10912
11379
  out.push({
10913
- infraId: (0, import_types31.infraId)("firestore-collection", collPath),
11380
+ infraId: (0, import_types34.infraId)("firestore-collection", collPath),
10914
11381
  name: collPath,
10915
11382
  kind: "firestore-collection",
10916
11383
  edgeType: "CALLS",
@@ -10921,7 +11388,7 @@ function firestoreEndpointsFromFile(file, serviceDir) {
10921
11388
  ...columnSet.size > 0 ? { columns: [...columnSet] } : {},
10922
11389
  ...sdkWrites ? { sdkWrites } : {},
10923
11390
  evidence: {
10924
- file: import_node_path42.default.relative(serviceDir, file.path),
11391
+ file: import_node_path45.default.relative(serviceDir, file.path),
10925
11392
  line,
10926
11393
  snippet: snippet(file.content, line)
10927
11394
  }
@@ -10932,8 +11399,8 @@ function firestoreEndpointsFromFile(file, serviceDir) {
10932
11399
 
10933
11400
  // src/extract/calls/mongoose.ts
10934
11401
  init_cjs_shims();
10935
- var import_node_path43 = __toESM(require("path"), 1);
10936
- var import_types32 = require("@neat.is/types");
11402
+ var import_node_path46 = __toESM(require("path"), 1);
11403
+ var import_types35 = require("@neat.is/types");
10937
11404
  var MONGOOSE_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])mongoose['"`]/;
10938
11405
  var MONGODB_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])mongodb['"`]/;
10939
11406
  var PLURALIZE_DISABLED_RE = /\bpluralize\s*\(\s*(?:null|false)\s*\)/;
@@ -11078,12 +11545,12 @@ function collectModelDefs(content, pluralizeOn) {
11078
11545
  function endpoint(r, file, serviceDir, matchText) {
11079
11546
  const line = lineOf(file.content, matchText);
11080
11547
  return {
11081
- infraId: (0, import_types32.infraId)(r.kind, r.name),
11548
+ infraId: (0, import_types35.infraId)(r.kind, r.name),
11082
11549
  name: r.name,
11083
11550
  kind: r.kind,
11084
11551
  edgeType: "CALLS",
11085
11552
  confidenceKind: "verified-call-site",
11086
- evidence: { file: import_node_path43.default.relative(serviceDir, file.path), line, snippet: snippet(file.content, line) }
11553
+ evidence: { file: import_node_path46.default.relative(serviceDir, file.path), line, snippet: snippet(file.content, line) }
11087
11554
  };
11088
11555
  }
11089
11556
  function mongooseEndpointsFromFile(file, serviceDir) {
@@ -11173,7 +11640,7 @@ async function mongooseCrossFileEndpoints(files, serviceDir) {
11173
11640
  const registry = /* @__PURE__ */ new Map();
11174
11641
  for (const f of mongooseFiles) {
11175
11642
  const fx = fileExportsOf(f.content, pluralizeOn);
11176
- if (fx) registry.set(toPosix(import_node_path43.default.relative(serviceDir, f.path)), fx);
11643
+ if (fx) registry.set(toPosix(import_node_path46.default.relative(serviceDir, f.path)), fx);
11177
11644
  }
11178
11645
  if (registry.size === 0) return [];
11179
11646
  const out = [];
@@ -11184,7 +11651,7 @@ async function mongooseCrossFileEndpoints(files, serviceDir) {
11184
11651
  const directColl = /* @__PURE__ */ new Map();
11185
11652
  const nsExports = /* @__PURE__ */ new Map();
11186
11653
  for (const b of bindings) {
11187
- const resolvedRel = await resolveJsImport(b.specifier, import_node_path43.default.dirname(f.path), serviceDir, null);
11654
+ const resolvedRel = await resolveJsImport(b.specifier, import_node_path46.default.dirname(f.path), serviceDir, null);
11188
11655
  if (!resolvedRel) continue;
11189
11656
  const fx = registry.get(resolvedRel);
11190
11657
  if (!fx) continue;
@@ -11228,10 +11695,10 @@ async function mongooseCrossFileEndpoints(files, serviceDir) {
11228
11695
 
11229
11696
  // src/extract/calls/sqlalchemy.ts
11230
11697
  init_cjs_shims();
11231
- var import_node_path44 = __toESM(require("path"), 1);
11698
+ var import_node_path47 = __toESM(require("path"), 1);
11232
11699
  var import_tree_sitter9 = __toESM(require("tree-sitter"), 1);
11233
11700
  var import_tree_sitter_python5 = __toESM(require("tree-sitter-python"), 1);
11234
- var import_types33 = require("@neat.is/types");
11701
+ var import_types36 = require("@neat.is/types");
11235
11702
  var SQLALCHEMY_IMPORT_RE = /(?:from|import)\s+(?:flask_sqlalchemy|sqlalchemy)\b/;
11236
11703
  var PARSE_CHUNK6 = 16384;
11237
11704
  function makePyParser3() {
@@ -11367,7 +11834,7 @@ function sqlalchemyForeignKeys(file, serviceDir) {
11367
11834
  childTable,
11368
11835
  parentTable,
11369
11836
  evidence: {
11370
- file: import_node_path44.default.relative(serviceDir, file.path),
11837
+ file: import_node_path47.default.relative(serviceDir, file.path),
11371
11838
  line,
11372
11839
  snippet: snippet(file.content, line)
11373
11840
  }
@@ -11385,14 +11852,14 @@ function sqlalchemyEndpointsFromFile(file, serviceDir) {
11385
11852
  if (seen.has(name)) return;
11386
11853
  seen.add(name);
11387
11854
  out.push({
11388
- infraId: (0, import_types33.infraId)("sql-table", name),
11855
+ infraId: (0, import_types36.infraId)("sql-table", name),
11389
11856
  name,
11390
11857
  kind: "sql-table",
11391
11858
  edgeType: "CALLS",
11392
11859
  confidenceKind: "verified-call-site",
11393
11860
  ...columns && columns.length > 0 ? { columns } : {},
11394
11861
  evidence: {
11395
- file: import_node_path44.default.relative(serviceDir, file.path),
11862
+ file: import_node_path47.default.relative(serviceDir, file.path),
11396
11863
  line,
11397
11864
  snippet: snippet(file.content, line)
11398
11865
  }
@@ -11493,13 +11960,13 @@ function pythonOrmCrossFileEndpoints(files, serviceDir) {
11493
11960
  if (seen.has(key)) continue;
11494
11961
  seen.add(key);
11495
11962
  out.push({
11496
- infraId: (0, import_types33.infraId)("sql-table", t),
11963
+ infraId: (0, import_types36.infraId)("sql-table", t),
11497
11964
  name: t,
11498
11965
  kind: "sql-table",
11499
11966
  edgeType: "CALLS",
11500
11967
  confidenceKind: "verified-call-site",
11501
11968
  evidence: {
11502
- file: import_node_path44.default.relative(serviceDir, file.path),
11969
+ file: import_node_path47.default.relative(serviceDir, file.path),
11503
11970
  line,
11504
11971
  snippet: snippet(file.content, line)
11505
11972
  }
@@ -11511,10 +11978,10 @@ function pythonOrmCrossFileEndpoints(files, serviceDir) {
11511
11978
 
11512
11979
  // src/extract/calls/django-orm.ts
11513
11980
  init_cjs_shims();
11514
- var import_node_path45 = __toESM(require("path"), 1);
11981
+ var import_node_path48 = __toESM(require("path"), 1);
11515
11982
  var import_tree_sitter10 = __toESM(require("tree-sitter"), 1);
11516
11983
  var import_tree_sitter_python6 = __toESM(require("tree-sitter-python"), 1);
11517
- var import_types34 = require("@neat.is/types");
11984
+ var import_types37 = require("@neat.is/types");
11518
11985
  var DJANGO_IMPORT_RE = /(?:from|import)\s+django\b/;
11519
11986
  var PARSE_CHUNK7 = 16384;
11520
11987
  function makePyParser4() {
@@ -11581,7 +12048,7 @@ function djangoOrmEndpointsFromFile(file, serviceDir) {
11581
12048
  const tree = parseSource7(makePyParser4(), file.content);
11582
12049
  const out = [];
11583
12050
  const seen = /* @__PURE__ */ new Set();
11584
- const defaultAppLabel = import_node_path45.default.basename(import_node_path45.default.dirname(file.path));
12051
+ const defaultAppLabel = import_node_path48.default.basename(import_node_path48.default.dirname(file.path));
11585
12052
  walk4(tree.rootNode, (node) => {
11586
12053
  if (node.type !== "class_definition") return;
11587
12054
  if (!extendsDjangoModel(node)) return;
@@ -11594,12 +12061,12 @@ function djangoOrmEndpointsFromFile(file, serviceDir) {
11594
12061
  seen.add(table);
11595
12062
  const line = node.startPosition.row + 1;
11596
12063
  out.push({
11597
- infraId: (0, import_types34.infraId)("sql-table", table),
12064
+ infraId: (0, import_types37.infraId)("sql-table", table),
11598
12065
  name: table,
11599
12066
  kind: "sql-table",
11600
12067
  edgeType: "CALLS",
11601
12068
  confidenceKind: "verified-call-site",
11602
- evidence: { file: import_node_path45.default.relative(serviceDir, file.path), line, snippet: snippet(file.content, line) }
12069
+ evidence: { file: import_node_path48.default.relative(serviceDir, file.path), line, snippet: snippet(file.content, line) }
11603
12070
  });
11604
12071
  });
11605
12072
  return out;
@@ -11607,10 +12074,10 @@ function djangoOrmEndpointsFromFile(file, serviceDir) {
11607
12074
 
11608
12075
  // src/extract/calls/drizzle.ts
11609
12076
  init_cjs_shims();
11610
- var import_node_path46 = __toESM(require("path"), 1);
12077
+ var import_node_path49 = __toESM(require("path"), 1);
11611
12078
  var import_tree_sitter11 = __toESM(require("tree-sitter"), 1);
11612
12079
  var import_tree_sitter_javascript7 = __toESM(require("tree-sitter-javascript"), 1);
11613
- var import_types35 = require("@neat.is/types");
12080
+ var import_types38 = require("@neat.is/types");
11614
12081
  var DRIZZLE_IMPORT_RE = /drizzle-orm/;
11615
12082
  var TABLE_BUILDERS = /* @__PURE__ */ new Set(["pgTable", "mysqlTable", "sqliteTable"]);
11616
12083
  function parserForExt3(ext) {
@@ -11685,7 +12152,7 @@ function columnsFromObject(obj) {
11685
12152
  }
11686
12153
  function drizzleEndpointsFromFile(file, serviceDir) {
11687
12154
  if (!DRIZZLE_IMPORT_RE.test(file.content)) return [];
11688
- const tree = parseSource3(parserForExt3(import_node_path46.default.extname(file.path)), file.content);
12155
+ const tree = parseSource3(parserForExt3(import_node_path49.default.extname(file.path)), file.content);
11689
12156
  const out = [];
11690
12157
  const seen = /* @__PURE__ */ new Set();
11691
12158
  const walk9 = (node) => {
@@ -11701,14 +12168,14 @@ function drizzleEndpointsFromFile(file, serviceDir) {
11701
12168
  const columns = columnsFromObject(obj);
11702
12169
  const line = node.startPosition.row + 1;
11703
12170
  out.push({
11704
- infraId: (0, import_types35.infraId)("sql-table", tableName),
12171
+ infraId: (0, import_types38.infraId)("sql-table", tableName),
11705
12172
  name: tableName,
11706
12173
  kind: "sql-table",
11707
12174
  edgeType: "CALLS",
11708
12175
  confidenceKind: "structural",
11709
12176
  columns,
11710
12177
  evidence: {
11711
- file: import_node_path46.default.relative(serviceDir, file.path),
12178
+ file: import_node_path49.default.relative(serviceDir, file.path),
11712
12179
  line,
11713
12180
  snippet: snippet(file.content, line)
11714
12181
  }
@@ -11774,7 +12241,7 @@ function referencesTargetVar(call) {
11774
12241
  }
11775
12242
  function drizzleForeignKeys(file, serviceDir) {
11776
12243
  if (!DRIZZLE_IMPORT_RE.test(file.content)) return [];
11777
- const tree = parseSource3(parserForExt3(import_node_path46.default.extname(file.path)), file.content);
12244
+ const tree = parseSource3(parserForExt3(import_node_path49.default.extname(file.path)), file.content);
11778
12245
  const { tables, varToTable } = collectDrizzleTables(tree.rootNode);
11779
12246
  const out = [];
11780
12247
  const seen = /* @__PURE__ */ new Set();
@@ -11793,7 +12260,7 @@ function drizzleForeignKeys(file, serviceDir) {
11793
12260
  childTable: table.tableName,
11794
12261
  parentTable,
11795
12262
  evidence: {
11796
- file: import_node_path46.default.relative(serviceDir, file.path),
12263
+ file: import_node_path49.default.relative(serviceDir, file.path),
11797
12264
  line,
11798
12265
  snippet: snippet(file.content, line)
11799
12266
  }
@@ -11810,8 +12277,8 @@ function drizzleForeignKeys(file, serviceDir) {
11810
12277
 
11811
12278
  // src/extract/calls/prisma.ts
11812
12279
  init_cjs_shims();
11813
- var import_node_path47 = __toESM(require("path"), 1);
11814
- var import_types36 = require("@neat.is/types");
12280
+ var import_node_path50 = __toESM(require("path"), 1);
12281
+ var import_types39 = require("@neat.is/types");
11815
12282
  var SCALAR_TYPES = /* @__PURE__ */ new Set([
11816
12283
  "Int",
11817
12284
  "String",
@@ -11859,14 +12326,14 @@ function prismaColumnsFromSchema(file, serviceDir) {
11859
12326
  if (seenTable.has(b.tableName)) return;
11860
12327
  seenTable.add(b.tableName);
11861
12328
  out.push({
11862
- infraId: (0, import_types36.infraId)("sql-table", b.tableName),
12329
+ infraId: (0, import_types39.infraId)("sql-table", b.tableName),
11863
12330
  name: b.tableName,
11864
12331
  kind: "sql-table",
11865
12332
  edgeType: "CALLS",
11866
12333
  confidenceKind: "structural",
11867
12334
  columns: b.columns,
11868
12335
  evidence: {
11869
- file: import_node_path47.default.relative(serviceDir, file.path),
12336
+ file: import_node_path50.default.relative(serviceDir, file.path),
11870
12337
  line: b.startLine,
11871
12338
  snippet: snippet(content, b.startLine)
11872
12339
  }
@@ -11927,7 +12394,7 @@ function prismaColumnsFromSchema(file, serviceDir) {
11927
12394
  }
11928
12395
  async function prismaColumnEndpoints(serviceDir) {
11929
12396
  const schemaPath = await findFirst(serviceDir, [
11930
- import_node_path47.default.join("prisma", "schema.prisma"),
12397
+ import_node_path50.default.join("prisma", "schema.prisma"),
11931
12398
  "schema.prisma"
11932
12399
  ]);
11933
12400
  if (!schemaPath) return [];
@@ -12002,7 +12469,7 @@ function prismaForeignKeysFromSchema(file, serviceDir) {
12002
12469
  childTable: current.table,
12003
12470
  parentTable,
12004
12471
  evidence: {
12005
- file: import_node_path47.default.relative(serviceDir, file.path),
12472
+ file: import_node_path50.default.relative(serviceDir, file.path),
12006
12473
  line: lineNo,
12007
12474
  snippet: snippet(content, lineNo)
12008
12475
  }
@@ -12017,7 +12484,7 @@ function prismaForeignKeysFromSchema(file, serviceDir) {
12017
12484
  }
12018
12485
  async function prismaForeignKeys(serviceDir) {
12019
12486
  const schemaPath = await findFirst(serviceDir, [
12020
- import_node_path47.default.join("prisma", "schema.prisma"),
12487
+ import_node_path50.default.join("prisma", "schema.prisma"),
12021
12488
  "schema.prisma"
12022
12489
  ]);
12023
12490
  if (!schemaPath) return [];
@@ -12028,10 +12495,10 @@ async function prismaForeignKeys(serviceDir) {
12028
12495
 
12029
12496
  // src/extract/calls/activerecord.ts
12030
12497
  init_cjs_shims();
12031
- var import_node_path48 = __toESM(require("path"), 1);
12498
+ var import_node_path51 = __toESM(require("path"), 1);
12032
12499
  var import_tree_sitter12 = __toESM(require("tree-sitter"), 1);
12033
12500
  var import_tree_sitter_ruby3 = __toESM(require("tree-sitter-ruby"), 1);
12034
- var import_types37 = require("@neat.is/types");
12501
+ var import_types40 = require("@neat.is/types");
12035
12502
  var AR_SCHEMA_RE = /ActiveRecord::Schema/;
12036
12503
  var PARSE_CHUNK8 = 16384;
12037
12504
  function makeRubyParser2() {
@@ -12229,14 +12696,14 @@ function railsSchemaEndpointsFromFile(file, serviceDir) {
12229
12696
  if (!table || seen.has(table.name)) return;
12230
12697
  seen.add(table.name);
12231
12698
  out.push({
12232
- infraId: (0, import_types37.infraId)("sql-table", table.name),
12699
+ infraId: (0, import_types40.infraId)("sql-table", table.name),
12233
12700
  name: table.name,
12234
12701
  kind: "sql-table",
12235
12702
  edgeType: "CALLS",
12236
12703
  confidenceKind: "structural",
12237
12704
  ...table.columns.length > 0 ? { columns: table.columns } : {},
12238
12705
  evidence: {
12239
- file: import_node_path48.default.relative(serviceDir, file.path),
12706
+ file: import_node_path51.default.relative(serviceDir, file.path),
12240
12707
  line: table.line,
12241
12708
  snippet: snippet(file.content, table.line)
12242
12709
  }
@@ -12258,7 +12725,7 @@ function railsSchemaForeignKeys(file, serviceDir) {
12258
12725
  childTable,
12259
12726
  parentTable,
12260
12727
  evidence: {
12261
- file: import_node_path48.default.relative(serviceDir, file.path),
12728
+ file: import_node_path51.default.relative(serviceDir, file.path),
12262
12729
  line,
12263
12730
  snippet: snippet(file.content, line)
12264
12731
  }
@@ -12335,13 +12802,13 @@ function railsModelEndpointsFromFile(file, serviceDir) {
12335
12802
  seen.add(table);
12336
12803
  const line = node.startPosition.row + 1;
12337
12804
  out.push({
12338
- infraId: (0, import_types37.infraId)("sql-table", table),
12805
+ infraId: (0, import_types40.infraId)("sql-table", table),
12339
12806
  name: table,
12340
12807
  kind: "sql-table",
12341
12808
  edgeType: "CALLS",
12342
12809
  confidenceKind: "verified-call-site",
12343
12810
  evidence: {
12344
- file: import_node_path48.default.relative(serviceDir, file.path),
12811
+ file: import_node_path51.default.relative(serviceDir, file.path),
12345
12812
  line,
12346
12813
  snippet: snippet(file.content, line)
12347
12814
  }
@@ -12378,7 +12845,7 @@ function railsModelForeignKeys(file, serviceDir) {
12378
12845
  childTable,
12379
12846
  parentTable,
12380
12847
  evidence: {
12381
- file: import_node_path48.default.relative(serviceDir, file.path),
12848
+ file: import_node_path51.default.relative(serviceDir, file.path),
12382
12849
  line,
12383
12850
  snippet: snippet(file.content, line)
12384
12851
  }
@@ -12390,10 +12857,10 @@ function railsModelForeignKeys(file, serviceDir) {
12390
12857
 
12391
12858
  // src/extract/calls/eloquent.ts
12392
12859
  init_cjs_shims();
12393
- var import_node_path49 = __toESM(require("path"), 1);
12860
+ var import_node_path52 = __toESM(require("path"), 1);
12394
12861
  var import_tree_sitter13 = __toESM(require("tree-sitter"), 1);
12395
12862
  var import_tree_sitter_php3 = __toESM(require("tree-sitter-php"), 1);
12396
- var import_types38 = require("@neat.is/types");
12863
+ var import_types41 = require("@neat.is/types");
12397
12864
  var LARAVEL_SCHEMA_RE = /\bSchema::(create|createIfNotExists|table)\b/;
12398
12865
  var PARSE_CHUNK9 = 16384;
12399
12866
  function makePhpParser2() {
@@ -12640,14 +13107,14 @@ function laravelMigrationEndpointsFromFile(file, serviceDir) {
12640
13107
  }
12641
13108
  }
12642
13109
  out.push({
12643
- infraId: (0, import_types38.infraId)("sql-table", bp.table),
13110
+ infraId: (0, import_types41.infraId)("sql-table", bp.table),
12644
13111
  name: bp.table,
12645
13112
  kind: "sql-table",
12646
13113
  edgeType: "CALLS",
12647
13114
  confidenceKind: "structural",
12648
13115
  ...columns.length > 0 ? { columns } : {},
12649
13116
  evidence: {
12650
- file: import_node_path49.default.relative(serviceDir, file.path),
13117
+ file: import_node_path52.default.relative(serviceDir, file.path),
12651
13118
  line: bp.line,
12652
13119
  snippet: snippet(file.content, bp.line)
12653
13120
  }
@@ -12669,7 +13136,7 @@ function laravelMigrationForeignKeys(file, serviceDir) {
12669
13136
  childTable,
12670
13137
  parentTable,
12671
13138
  evidence: {
12672
- file: import_node_path49.default.relative(serviceDir, file.path),
13139
+ file: import_node_path52.default.relative(serviceDir, file.path),
12673
13140
  line,
12674
13141
  snippet: snippet(file.content, line)
12675
13142
  }
@@ -12783,13 +13250,13 @@ function laravelModelEndpointsFromFile(file, serviceDir) {
12783
13250
  seen.add(table);
12784
13251
  const line = node.startPosition.row + 1;
12785
13252
  out.push({
12786
- infraId: (0, import_types38.infraId)("sql-table", table),
13253
+ infraId: (0, import_types41.infraId)("sql-table", table),
12787
13254
  name: table,
12788
13255
  kind: "sql-table",
12789
13256
  edgeType: "CALLS",
12790
13257
  confidenceKind: "verified-call-site",
12791
13258
  evidence: {
12792
- file: import_node_path49.default.relative(serviceDir, file.path),
13259
+ file: import_node_path52.default.relative(serviceDir, file.path),
12793
13260
  line,
12794
13261
  snippet: snippet(file.content, line)
12795
13262
  }
@@ -12825,7 +13292,7 @@ function laravelModelForeignKeys(file, serviceDir) {
12825
13292
  childTable,
12826
13293
  parentTable,
12827
13294
  evidence: {
12828
- file: import_node_path49.default.relative(serviceDir, file.path),
13295
+ file: import_node_path52.default.relative(serviceDir, file.path),
12829
13296
  line,
12830
13297
  snippet: snippet(file.content, line)
12831
13298
  }
@@ -12837,10 +13304,10 @@ function laravelModelForeignKeys(file, serviceDir) {
12837
13304
 
12838
13305
  // src/extract/calls/go.ts
12839
13306
  init_cjs_shims();
12840
- var import_node_path52 = __toESM(require("path"), 1);
13307
+ var import_node_path55 = __toESM(require("path"), 1);
12841
13308
  var import_tree_sitter14 = __toESM(require("tree-sitter"), 1);
12842
13309
  var import_tree_sitter_go4 = __toESM(require("tree-sitter-go"), 1);
12843
- var import_types39 = require("@neat.is/types");
13310
+ var import_types42 = require("@neat.is/types");
12844
13311
  init_otel();
12845
13312
  var PARSE_CHUNK10 = 16384;
12846
13313
  var DATABASE_SQL_METHODS = /* @__PURE__ */ new Set([
@@ -12912,7 +13379,7 @@ function firstStringLiteralArg(argsNode) {
12912
13379
  return null;
12913
13380
  }
12914
13381
  function goSqlEndpointsFromFile(file, serviceDir) {
12915
- if (import_node_path52.default.extname(file.path) !== ".go") return [];
13382
+ if (import_node_path55.default.extname(file.path) !== ".go") return [];
12916
13383
  if (!file.content.includes(DATABASE_SQL_IMPORT) && !file.content.includes(SQLX_IMPORT)) return [];
12917
13384
  const tree = parseSource10(makeGoParser3(), file.content);
12918
13385
  const importsDatabaseSql = goImportsAny(tree.rootNode, /* @__PURE__ */ new Set([DATABASE_SQL_IMPORT]));
@@ -12934,14 +13401,14 @@ function goSqlEndpointsFromFile(file, serviceDir) {
12934
13401
  const columns = columnsFromSqlStatement(sql);
12935
13402
  const line = node.startPosition.row + 1;
12936
13403
  out.push({
12937
- infraId: (0, import_types39.infraId)("sql-table", table),
13404
+ infraId: (0, import_types42.infraId)("sql-table", table),
12938
13405
  name: table,
12939
13406
  kind: "sql-table",
12940
13407
  edgeType: "CALLS",
12941
13408
  confidenceKind: "verified-call-site",
12942
13409
  ...columns.length > 0 ? { columns } : {},
12943
13410
  evidence: {
12944
- file: toPosix(import_node_path52.default.relative(serviceDir, file.path)),
13411
+ file: toPosix(import_node_path55.default.relative(serviceDir, file.path)),
12945
13412
  line,
12946
13413
  snippet: snippet(file.content, line)
12947
13414
  }
@@ -12952,10 +13419,10 @@ function goSqlEndpointsFromFile(file, serviceDir) {
12952
13419
 
12953
13420
  // src/extract/calls/gorm.ts
12954
13421
  init_cjs_shims();
12955
- var import_node_path53 = __toESM(require("path"), 1);
13422
+ var import_node_path56 = __toESM(require("path"), 1);
12956
13423
  var import_tree_sitter15 = __toESM(require("tree-sitter"), 1);
12957
13424
  var import_tree_sitter_go5 = __toESM(require("tree-sitter-go"), 1);
12958
- var import_types40 = require("@neat.is/types");
13425
+ var import_types43 = require("@neat.is/types");
12959
13426
  var GORM_IMPORT_RE = /gorm\.io\/gorm/;
12960
13427
  var PARSE_CHUNK11 = 16384;
12961
13428
  function makeGoParser4() {
@@ -13385,7 +13852,7 @@ function collectColumns(struct, structs, seen, prefix, out, emitted) {
13385
13852
  seen.delete(struct.name);
13386
13853
  }
13387
13854
  function gormEndpointsFromFile(file, serviceDir) {
13388
- if (import_node_path53.default.extname(file.path) !== ".go") return [];
13855
+ if (import_node_path56.default.extname(file.path) !== ".go") return [];
13389
13856
  if (!GORM_IMPORT_RE.test(file.content)) return [];
13390
13857
  const tree = parseSource11(makeGoParser4(), file.content);
13391
13858
  const { structs, models, tableFor } = analyze(tree);
@@ -13400,14 +13867,14 @@ function gormEndpointsFromFile(file, serviceDir) {
13400
13867
  const columns = [];
13401
13868
  collectColumns(struct, structs, /* @__PURE__ */ new Set(), "", columns, /* @__PURE__ */ new Set());
13402
13869
  out.push({
13403
- infraId: (0, import_types40.infraId)("sql-table", table),
13870
+ infraId: (0, import_types43.infraId)("sql-table", table),
13404
13871
  name: table,
13405
13872
  kind: "sql-table",
13406
13873
  edgeType: "CALLS",
13407
13874
  confidenceKind: "structural",
13408
13875
  ...columns.length > 0 ? { columns } : {},
13409
13876
  evidence: {
13410
- file: toPosix(import_node_path53.default.relative(serviceDir, file.path)),
13877
+ file: toPosix(import_node_path56.default.relative(serviceDir, file.path)),
13411
13878
  line: struct.line,
13412
13879
  snippet: snippet(file.content, struct.line)
13413
13880
  }
@@ -13416,7 +13883,7 @@ function gormEndpointsFromFile(file, serviceDir) {
13416
13883
  return out;
13417
13884
  }
13418
13885
  function gormForeignKeys(file, serviceDir) {
13419
- if (import_node_path53.default.extname(file.path) !== ".go") return [];
13886
+ if (import_node_path56.default.extname(file.path) !== ".go") return [];
13420
13887
  if (!GORM_IMPORT_RE.test(file.content)) return [];
13421
13888
  const tree = parseSource11(makeGoParser4(), file.content);
13422
13889
  const { structs, models, tableFor } = analyze(tree);
@@ -13431,7 +13898,7 @@ function gormForeignKeys(file, serviceDir) {
13431
13898
  childTable,
13432
13899
  parentTable,
13433
13900
  evidence: {
13434
- file: toPosix(import_node_path53.default.relative(serviceDir, file.path)),
13901
+ file: toPosix(import_node_path56.default.relative(serviceDir, file.path)),
13435
13902
  line,
13436
13903
  snippet: snippet(file.content, line)
13437
13904
  }
@@ -13470,11 +13937,11 @@ function gormForeignKeys(file, serviceDir) {
13470
13937
  function edgeTypeFromEndpoint(ep) {
13471
13938
  switch (ep.edgeType) {
13472
13939
  case "PUBLISHES_TO":
13473
- return import_types41.EdgeType.PUBLISHES_TO;
13940
+ return import_types44.EdgeType.PUBLISHES_TO;
13474
13941
  case "CONSUMES_FROM":
13475
- return import_types41.EdgeType.CONSUMES_FROM;
13942
+ return import_types44.EdgeType.CONSUMES_FROM;
13476
13943
  default:
13477
- return import_types41.EdgeType.CALLS;
13944
+ return import_types44.EdgeType.CALLS;
13478
13945
  }
13479
13946
  }
13480
13947
  function isAwsKind(kind) {
@@ -13484,7 +13951,7 @@ async function addExternalEndpointEdges(graph, services) {
13484
13951
  let nodesAdded = 0;
13485
13952
  let edgesAdded = 0;
13486
13953
  for (const service of services) {
13487
- const files = await loadSourceFiles(service.dir);
13954
+ const files = await loadSourceFiles(service.dir, service.excludeDirs);
13488
13955
  const endpoints = [];
13489
13956
  const maskedFiles = [];
13490
13957
  for (const file of files) {
@@ -13534,7 +14001,7 @@ async function addExternalEndpointEdges(graph, services) {
13534
14001
  if (!graph.hasNode(ep.infraId)) {
13535
14002
  const node = {
13536
14003
  id: ep.infraId,
13537
- type: import_types41.NodeType.InfraNode,
14004
+ type: import_types44.NodeType.InfraNode,
13538
14005
  name: ep.name,
13539
14006
  // #238 — `aws-*` covers AWS-SDK client kinds (aws-s3, aws-dynamodb,
13540
14007
  // aws-cognito-identity-provider, …); `s3-` / `dynamodb-` cover the
@@ -13547,21 +14014,21 @@ async function addExternalEndpointEdges(graph, services) {
13547
14014
  }
13548
14015
  if (ep.columns && ep.columns.length > 0) {
13549
14016
  const node = graph.getNodeAttributes(ep.infraId);
13550
- if (node.type === import_types41.NodeType.InfraNode) {
14017
+ if (node.type === import_types44.NodeType.InfraNode) {
13551
14018
  graph.replaceNodeAttributes(ep.infraId, {
13552
14019
  ...node,
13553
14020
  columns: foldColumns(
13554
14021
  node.columns,
13555
14022
  ep.columns,
13556
- import_types41.Provenance.EXTRACTED,
13557
- (0, import_types41.confidenceForExtracted)(ep.confidenceKind)
14023
+ import_types44.Provenance.EXTRACTED,
14024
+ (0, import_types44.confidenceForExtracted)(ep.confidenceKind)
13558
14025
  )
13559
14026
  });
13560
14027
  }
13561
14028
  }
13562
14029
  if (ep.sdkWrites && Object.keys(ep.sdkWrites).length > 0) {
13563
14030
  const node = graph.getNodeAttributes(ep.infraId);
13564
- if (node.type === import_types41.NodeType.InfraNode) {
14031
+ if (node.type === import_types44.NodeType.InfraNode) {
13565
14032
  graph.replaceNodeAttributes(ep.infraId, {
13566
14033
  ...node,
13567
14034
  columns: foldSdkWrites(node.columns, ep.sdkWrites)
@@ -13569,7 +14036,7 @@ async function addExternalEndpointEdges(graph, services) {
13569
14036
  }
13570
14037
  }
13571
14038
  const edgeType = edgeTypeFromEndpoint(ep);
13572
- const confidence = (0, import_types41.confidenceForExtracted)(ep.confidenceKind);
14039
+ const confidence = (0, import_types44.confidenceForExtracted)(ep.confidenceKind);
13573
14040
  const relFile = toPosix(ep.evidence.file);
13574
14041
  const { fileNodeId, nodesAdded: n, edgesAdded: e } = ensureFileNode(
13575
14042
  graph,
@@ -13579,7 +14046,7 @@ async function addExternalEndpointEdges(graph, services) {
13579
14046
  );
13580
14047
  nodesAdded += n;
13581
14048
  edgesAdded += e;
13582
- if (!(0, import_types41.passesExtractedFloor)(confidence)) {
14049
+ if (!(0, import_types44.passesExtractedFloor)(confidence)) {
13583
14050
  noteExtractedDropped({
13584
14051
  source: fileNodeId,
13585
14052
  target: ep.infraId,
@@ -13599,7 +14066,7 @@ async function addExternalEndpointEdges(graph, services) {
13599
14066
  source: fileNodeId,
13600
14067
  target: ep.infraId,
13601
14068
  type: edgeType,
13602
- provenance: import_types41.Provenance.EXTRACTED,
14069
+ provenance: import_types44.Provenance.EXTRACTED,
13603
14070
  confidence,
13604
14071
  evidence: ep.evidence
13605
14072
  };
@@ -13622,12 +14089,12 @@ async function addCallEdges(graph, services) {
13622
14089
 
13623
14090
  // src/extract/table-edges.ts
13624
14091
  init_cjs_shims();
13625
- var import_types42 = require("@neat.is/types");
14092
+ var import_types45 = require("@neat.is/types");
13626
14093
  async function addTableEdges(graph, services) {
13627
14094
  let nodesAdded = 0;
13628
14095
  let edgesAdded = 0;
13629
14096
  for (const service of services) {
13630
- const files = await loadSourceFiles(service.dir);
14097
+ const files = await loadSourceFiles(service.dir, service.excludeDirs);
13631
14098
  const refs = [];
13632
14099
  const modelRefs = [];
13633
14100
  for (const file of files) {
@@ -13650,20 +14117,20 @@ async function addTableEdges(graph, services) {
13650
14117
  }
13651
14118
  refs.push(...modelRefs);
13652
14119
  for (const ref of refs) {
13653
- const childId = (0, import_types42.infraId)("sql-table", ref.childTable);
13654
- const parentId = (0, import_types42.infraId)("sql-table", ref.parentTable);
14120
+ const childId = (0, import_types45.infraId)("sql-table", ref.childTable);
14121
+ const parentId = (0, import_types45.infraId)("sql-table", ref.parentTable);
13655
14122
  if (childId === parentId) continue;
13656
14123
  nodesAdded += ensureTableNode(graph, childId, ref.childTable);
13657
14124
  nodesAdded += ensureTableNode(graph, parentId, ref.parentTable);
13658
- const edgeId = (0, import_types42.extractedEdgeId)(childId, parentId, import_types42.EdgeType.REFERENCES);
14125
+ const edgeId = (0, import_types45.extractedEdgeId)(childId, parentId, import_types45.EdgeType.REFERENCES);
13659
14126
  if (graph.hasEdge(edgeId)) continue;
13660
14127
  const edge = {
13661
14128
  id: edgeId,
13662
14129
  source: childId,
13663
14130
  target: parentId,
13664
- type: import_types42.EdgeType.REFERENCES,
13665
- provenance: import_types42.Provenance.EXTRACTED,
13666
- confidence: (0, import_types42.confidenceForExtracted)("structural"),
14131
+ type: import_types45.EdgeType.REFERENCES,
14132
+ provenance: import_types45.Provenance.EXTRACTED,
14133
+ confidence: (0, import_types45.confidenceForExtracted)("structural"),
13667
14134
  evidence: ref.evidence
13668
14135
  };
13669
14136
  graph.addEdgeWithKey(edgeId, childId, parentId, edge);
@@ -13676,7 +14143,7 @@ function ensureTableNode(graph, id, name) {
13676
14143
  if (graph.hasNode(id)) return 0;
13677
14144
  const node = {
13678
14145
  id,
13679
- type: import_types42.NodeType.InfraNode,
14146
+ type: import_types45.NodeType.InfraNode,
13680
14147
  name,
13681
14148
  provider: "self",
13682
14149
  kind: "sql-table"
@@ -13690,16 +14157,16 @@ init_cjs_shims();
13690
14157
 
13691
14158
  // src/extract/infra/docker-compose.ts
13692
14159
  init_cjs_shims();
13693
- var import_node_path54 = __toESM(require("path"), 1);
13694
- var import_types44 = require("@neat.is/types");
14160
+ var import_node_path57 = __toESM(require("path"), 1);
14161
+ var import_types47 = require("@neat.is/types");
13695
14162
 
13696
14163
  // src/extract/infra/shared.ts
13697
14164
  init_cjs_shims();
13698
- var import_types43 = require("@neat.is/types");
14165
+ var import_types46 = require("@neat.is/types");
13699
14166
  function makeInfraNode(kind, name, provider = "self", extras) {
13700
14167
  return {
13701
- id: (0, import_types43.infraId)(kind, name),
13702
- type: import_types43.NodeType.InfraNode,
14168
+ id: (0, import_types46.infraId)(kind, name),
14169
+ type: import_types46.NodeType.InfraNode,
13703
14170
  name,
13704
14171
  provider,
13705
14172
  kind,
@@ -13743,8 +14210,8 @@ function emitPlatformResourceEdge(graph, anchorId, edgeType, kind, name, provide
13743
14210
  source: anchorId,
13744
14211
  target: node.id,
13745
14212
  type: edgeType,
13746
- provenance: import_types43.Provenance.EXTRACTED,
13747
- confidence: (0, import_types43.confidenceForExtracted)("structural"),
14213
+ provenance: import_types46.Provenance.EXTRACTED,
14214
+ confidence: (0, import_types46.confidenceForExtracted)("structural"),
13748
14215
  evidence: { file: evidenceFile, ...line !== void 0 ? { line } : {} }
13749
14216
  };
13750
14217
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -13761,7 +14228,7 @@ function dependsOnList(value) {
13761
14228
  }
13762
14229
  function serviceNameToServiceNode(name, services) {
13763
14230
  for (const s of services) {
13764
- if (s.node.name === name || import_node_path54.default.basename(s.dir) === name) return s.node.id;
14231
+ if (s.node.name === name || import_node_path57.default.basename(s.dir) === name) return s.node.id;
13765
14232
  }
13766
14233
  return null;
13767
14234
  }
@@ -13770,7 +14237,7 @@ async function addComposeInfra(graph, scanPath, services) {
13770
14237
  let edgesAdded = 0;
13771
14238
  let composePath = null;
13772
14239
  for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
13773
- const abs = import_node_path54.default.join(scanPath, name);
14240
+ const abs = import_node_path57.default.join(scanPath, name);
13774
14241
  if (await exists2(abs)) {
13775
14242
  composePath = abs;
13776
14243
  break;
@@ -13783,13 +14250,13 @@ async function addComposeInfra(graph, scanPath, services) {
13783
14250
  } catch (err) {
13784
14251
  recordExtractionError(
13785
14252
  "infra docker-compose",
13786
- import_node_path54.default.relative(scanPath, composePath),
14253
+ import_node_path57.default.relative(scanPath, composePath),
13787
14254
  err
13788
14255
  );
13789
14256
  return { nodesAdded, edgesAdded };
13790
14257
  }
13791
14258
  if (!compose?.services) return { nodesAdded, edgesAdded };
13792
- const evidenceFile = import_node_path54.default.relative(scanPath, composePath).split(import_node_path54.default.sep).join("/");
14259
+ const evidenceFile = import_node_path57.default.relative(scanPath, composePath).split(import_node_path57.default.sep).join("/");
13793
14260
  const composeNameToNodeId = /* @__PURE__ */ new Map();
13794
14261
  for (const [composeName, svc] of Object.entries(compose.services)) {
13795
14262
  const matchedServiceId = serviceNameToServiceNode(composeName, services);
@@ -13811,15 +14278,15 @@ async function addComposeInfra(graph, scanPath, services) {
13811
14278
  for (const dep of dependsOnList(svc.depends_on)) {
13812
14279
  const targetId = composeNameToNodeId.get(dep);
13813
14280
  if (!targetId) continue;
13814
- const edgeId = (0, import_types3.extractedEdgeId)(sourceId, targetId, import_types44.EdgeType.DEPENDS_ON);
14281
+ const edgeId = (0, import_types3.extractedEdgeId)(sourceId, targetId, import_types47.EdgeType.DEPENDS_ON);
13815
14282
  if (graph.hasEdge(edgeId)) continue;
13816
14283
  const edge = {
13817
14284
  id: edgeId,
13818
14285
  source: sourceId,
13819
14286
  target: targetId,
13820
- type: import_types44.EdgeType.DEPENDS_ON,
13821
- provenance: import_types44.Provenance.EXTRACTED,
13822
- confidence: (0, import_types44.confidenceForExtracted)("structural"),
14287
+ type: import_types47.EdgeType.DEPENDS_ON,
14288
+ provenance: import_types47.Provenance.EXTRACTED,
14289
+ confidence: (0, import_types47.confidenceForExtracted)("structural"),
13823
14290
  evidence: { file: evidenceFile }
13824
14291
  };
13825
14292
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -13831,9 +14298,9 @@ async function addComposeInfra(graph, scanPath, services) {
13831
14298
 
13832
14299
  // src/extract/infra/dockerfile.ts
13833
14300
  init_cjs_shims();
13834
- var import_node_path55 = __toESM(require("path"), 1);
13835
- var import_node_fs23 = require("fs");
13836
- var import_types45 = require("@neat.is/types");
14301
+ var import_node_path58 = __toESM(require("path"), 1);
14302
+ var import_node_fs26 = require("fs");
14303
+ var import_types48 = require("@neat.is/types");
13837
14304
  function readDockerfile(content) {
13838
14305
  let image = null;
13839
14306
  const ports = [];
@@ -13862,15 +14329,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
13862
14329
  let nodesAdded = 0;
13863
14330
  let edgesAdded = 0;
13864
14331
  for (const service of services) {
13865
- const dockerfilePath = import_node_path55.default.join(service.dir, "Dockerfile");
14332
+ const dockerfilePath = import_node_path58.default.join(service.dir, "Dockerfile");
13866
14333
  if (!await exists2(dockerfilePath)) continue;
13867
14334
  let content;
13868
14335
  try {
13869
- content = await import_node_fs23.promises.readFile(dockerfilePath, "utf8");
14336
+ content = await import_node_fs26.promises.readFile(dockerfilePath, "utf8");
13870
14337
  } catch (err) {
13871
14338
  recordExtractionError(
13872
14339
  "infra dockerfile",
13873
- import_node_path55.default.relative(scanPath, dockerfilePath),
14340
+ import_node_path58.default.relative(scanPath, dockerfilePath),
13874
14341
  err
13875
14342
  );
13876
14343
  continue;
@@ -13882,8 +14349,8 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
13882
14349
  graph.addNode(node.id, node);
13883
14350
  nodesAdded++;
13884
14351
  }
13885
- const relDockerfile = toPosix(import_node_path55.default.relative(service.dir, dockerfilePath));
13886
- const evidenceFile = toPosix(import_node_path55.default.relative(scanPath, dockerfilePath));
14352
+ const relDockerfile = toPosix(import_node_path58.default.relative(service.dir, dockerfilePath));
14353
+ const evidenceFile = toPosix(import_node_path58.default.relative(scanPath, dockerfilePath));
13887
14354
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
13888
14355
  graph,
13889
14356
  service.pkg.name,
@@ -13892,15 +14359,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
13892
14359
  );
13893
14360
  nodesAdded += fn;
13894
14361
  edgesAdded += fe;
13895
- const edgeId = (0, import_types3.extractedEdgeId)(fileNodeId, node.id, import_types45.EdgeType.RUNS_ON);
14362
+ const edgeId = (0, import_types3.extractedEdgeId)(fileNodeId, node.id, import_types48.EdgeType.RUNS_ON);
13896
14363
  if (!graph.hasEdge(edgeId)) {
13897
14364
  const edge = {
13898
14365
  id: edgeId,
13899
14366
  source: fileNodeId,
13900
14367
  target: node.id,
13901
- type: import_types45.EdgeType.RUNS_ON,
13902
- provenance: import_types45.Provenance.EXTRACTED,
13903
- confidence: (0, import_types45.confidenceForExtracted)("structural"),
14368
+ type: import_types48.EdgeType.RUNS_ON,
14369
+ provenance: import_types48.Provenance.EXTRACTED,
14370
+ confidence: (0, import_types48.confidenceForExtracted)("structural"),
13904
14371
  evidence: {
13905
14372
  file: evidenceFile,
13906
14373
  ...facts.entrypoint ? { snippet: facts.entrypoint.slice(0, 120) } : {}
@@ -13915,15 +14382,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
13915
14382
  graph.addNode(portNode.id, portNode);
13916
14383
  nodesAdded++;
13917
14384
  }
13918
- const portEdgeId = (0, import_types3.extractedEdgeId)(fileNodeId, portNode.id, import_types45.EdgeType.CONNECTS_TO);
14385
+ const portEdgeId = (0, import_types3.extractedEdgeId)(fileNodeId, portNode.id, import_types48.EdgeType.CONNECTS_TO);
13919
14386
  if (graph.hasEdge(portEdgeId)) continue;
13920
14387
  const portEdge = {
13921
14388
  id: portEdgeId,
13922
14389
  source: fileNodeId,
13923
14390
  target: portNode.id,
13924
- type: import_types45.EdgeType.CONNECTS_TO,
13925
- provenance: import_types45.Provenance.EXTRACTED,
13926
- confidence: (0, import_types45.confidenceForExtracted)("structural"),
14391
+ type: import_types48.EdgeType.CONNECTS_TO,
14392
+ provenance: import_types48.Provenance.EXTRACTED,
14393
+ confidence: (0, import_types48.confidenceForExtracted)("structural"),
13927
14394
  evidence: { file: evidenceFile, snippet: `EXPOSE ${port}` }
13928
14395
  };
13929
14396
  graph.addEdgeWithKey(portEdgeId, portEdge.source, portEdge.target, portEdge);
@@ -13935,23 +14402,23 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
13935
14402
 
13936
14403
  // src/extract/infra/terraform.ts
13937
14404
  init_cjs_shims();
13938
- var import_node_fs24 = require("fs");
13939
- var import_node_path56 = __toESM(require("path"), 1);
13940
- var import_types46 = require("@neat.is/types");
14405
+ var import_node_fs27 = require("fs");
14406
+ var import_node_path59 = __toESM(require("path"), 1);
14407
+ var import_types49 = require("@neat.is/types");
13941
14408
  var RESOURCE_RE = /resource\s+"(aws_[A-Za-z0-9_]+)"\s+"([A-Za-z0-9_-]+)"/g;
13942
14409
  var REFERENCE_RE = /(?<![\w.])(aws_[A-Za-z0-9_]+)\.([A-Za-z0-9_-]+)/g;
13943
14410
  async function walkTfFiles(start, depth = 0, max = 5) {
13944
14411
  if (depth > max) return [];
13945
14412
  const out = [];
13946
- const entries = await import_node_fs24.promises.readdir(start, { withFileTypes: true }).catch(() => []);
14413
+ const entries = await import_node_fs27.promises.readdir(start, { withFileTypes: true }).catch(() => []);
13947
14414
  for (const entry of entries) {
13948
14415
  if (entry.isDirectory()) {
13949
14416
  if (IGNORED_DIRS.has(entry.name) || entry.name === ".terraform") continue;
13950
- const child = import_node_path56.default.join(start, entry.name);
14417
+ const child = import_node_path59.default.join(start, entry.name);
13951
14418
  if (await isPythonVenvDir(child)) continue;
13952
14419
  out.push(...await walkTfFiles(child, depth + 1, max));
13953
14420
  } else if (entry.isFile() && entry.name.endsWith(".tf")) {
13954
- out.push(import_node_path56.default.join(start, entry.name));
14421
+ out.push(import_node_path59.default.join(start, entry.name));
13955
14422
  }
13956
14423
  }
13957
14424
  return out;
@@ -13982,8 +14449,8 @@ async function addTerraformResources(graph, scanPath) {
13982
14449
  let edgesAdded = 0;
13983
14450
  const files = await walkTfFiles(scanPath);
13984
14451
  for (const file of files) {
13985
- const content = await import_node_fs24.promises.readFile(file, "utf8");
13986
- const evidenceFile = toPosix(import_node_path56.default.relative(scanPath, file));
14452
+ const content = await import_node_fs27.promises.readFile(file, "utf8");
14453
+ const evidenceFile = toPosix(import_node_path59.default.relative(scanPath, file));
13987
14454
  const resources = [];
13988
14455
  const byKey = /* @__PURE__ */ new Map();
13989
14456
  RESOURCE_RE.lastIndex = 0;
@@ -14018,16 +14485,16 @@ async function addTerraformResources(graph, scanPath) {
14018
14485
  if (!target) continue;
14019
14486
  if (seen.has(target.nodeId)) continue;
14020
14487
  seen.add(target.nodeId);
14021
- const edgeId = (0, import_types3.extractedEdgeId)(resource.nodeId, target.nodeId, import_types46.EdgeType.DEPENDS_ON);
14488
+ const edgeId = (0, import_types3.extractedEdgeId)(resource.nodeId, target.nodeId, import_types49.EdgeType.DEPENDS_ON);
14022
14489
  if (graph.hasEdge(edgeId)) continue;
14023
14490
  const line = lineAt2(content, resource.bodyOffset + ref.index);
14024
14491
  const edge = {
14025
14492
  id: edgeId,
14026
14493
  source: resource.nodeId,
14027
14494
  target: target.nodeId,
14028
- type: import_types46.EdgeType.DEPENDS_ON,
14029
- provenance: import_types46.Provenance.EXTRACTED,
14030
- confidence: (0, import_types46.confidenceForExtracted)("structural"),
14495
+ type: import_types49.EdgeType.DEPENDS_ON,
14496
+ provenance: import_types49.Provenance.EXTRACTED,
14497
+ confidence: (0, import_types49.confidenceForExtracted)("structural"),
14031
14498
  evidence: { file: evidenceFile, line, snippet: key }
14032
14499
  };
14033
14500
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -14040,8 +14507,8 @@ async function addTerraformResources(graph, scanPath) {
14040
14507
 
14041
14508
  // src/extract/infra/k8s.ts
14042
14509
  init_cjs_shims();
14043
- var import_node_fs25 = require("fs");
14044
- var import_node_path57 = __toESM(require("path"), 1);
14510
+ var import_node_fs28 = require("fs");
14511
+ var import_node_path60 = __toESM(require("path"), 1);
14045
14512
  var import_yaml3 = require("yaml");
14046
14513
  var K8S_KIND_TO_INFRA_KIND = {
14047
14514
  Service: "k8s-service",
@@ -14055,15 +14522,15 @@ var K8S_KIND_TO_INFRA_KIND = {
14055
14522
  async function walkYamlFiles2(start, depth = 0, max = 5) {
14056
14523
  if (depth > max) return [];
14057
14524
  const out = [];
14058
- const entries = await import_node_fs25.promises.readdir(start, { withFileTypes: true }).catch(() => []);
14525
+ const entries = await import_node_fs28.promises.readdir(start, { withFileTypes: true }).catch(() => []);
14059
14526
  for (const entry of entries) {
14060
14527
  if (entry.isDirectory()) {
14061
14528
  if (IGNORED_DIRS.has(entry.name)) continue;
14062
- const child = import_node_path57.default.join(start, entry.name);
14529
+ const child = import_node_path60.default.join(start, entry.name);
14063
14530
  if (await isPythonVenvDir(child)) continue;
14064
14531
  out.push(...await walkYamlFiles2(child, depth + 1, max));
14065
- } else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path57.default.extname(entry.name))) {
14066
- out.push(import_node_path57.default.join(start, entry.name));
14532
+ } else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path60.default.extname(entry.name))) {
14533
+ out.push(import_node_path60.default.join(start, entry.name));
14067
14534
  }
14068
14535
  }
14069
14536
  return out;
@@ -14072,7 +14539,7 @@ async function addK8sResources(graph, scanPath) {
14072
14539
  let nodesAdded = 0;
14073
14540
  const files = await walkYamlFiles2(scanPath);
14074
14541
  for (const file of files) {
14075
- const content = await import_node_fs25.promises.readFile(file, "utf8");
14542
+ const content = await import_node_fs28.promises.readFile(file, "utf8");
14076
14543
  let docs;
14077
14544
  try {
14078
14545
  docs = (0, import_yaml3.parseAllDocuments)(content).map((d) => d.toJSON());
@@ -14096,17 +14563,17 @@ async function addK8sResources(graph, scanPath) {
14096
14563
 
14097
14564
  // src/extract/infra/cloudflare.ts
14098
14565
  init_cjs_shims();
14099
- var import_node_fs26 = require("fs");
14100
- var import_node_path58 = __toESM(require("path"), 1);
14101
- var import_smol_toml2 = require("smol-toml");
14102
- var import_types47 = require("@neat.is/types");
14566
+ var import_node_fs29 = require("fs");
14567
+ var import_node_path61 = __toESM(require("path"), 1);
14568
+ var import_smol_toml3 = require("smol-toml");
14569
+ var import_types50 = require("@neat.is/types");
14103
14570
  var WRANGLER_FILENAMES = ["wrangler.toml", "wrangler.jsonc", "wrangler.json"];
14104
14571
  async function readWranglerConfig(dir) {
14105
14572
  for (const filename of WRANGLER_FILENAMES) {
14106
- const abs = import_node_path58.default.join(dir, filename);
14573
+ const abs = import_node_path61.default.join(dir, filename);
14107
14574
  if (!await exists2(abs)) continue;
14108
- const raw = await import_node_fs26.promises.readFile(abs, "utf8");
14109
- const config = filename === "wrangler.toml" ? (0, import_smol_toml2.parse)(raw) : JSON.parse(maskCommentsInSource(raw));
14575
+ const raw = await import_node_fs29.promises.readFile(abs, "utf8");
14576
+ const config = filename === "wrangler.toml" ? (0, import_smol_toml3.parse)(raw) : JSON.parse(maskCommentsInSource(raw));
14110
14577
  return { config, relFile: filename, raw };
14111
14578
  }
14112
14579
  return null;
@@ -14147,8 +14614,8 @@ function addResourceEdge(graph, anchorId, edgeType, kind, name, evidenceFile, li
14147
14614
  source: anchorId,
14148
14615
  target: node.id,
14149
14616
  type: edgeType,
14150
- provenance: import_types47.Provenance.EXTRACTED,
14151
- confidence: (0, import_types47.confidenceForExtracted)("structural"),
14617
+ provenance: import_types50.Provenance.EXTRACTED,
14618
+ confidence: (0, import_types50.confidenceForExtracted)("structural"),
14152
14619
  evidence: { file: evidenceFile, ...line !== void 0 ? { line } : {} }
14153
14620
  };
14154
14621
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -14166,11 +14633,11 @@ async function addCloudflareWorkers(graph, services, scanPath) {
14166
14633
  try {
14167
14634
  read = await readWranglerConfig(service.dir);
14168
14635
  } catch (err) {
14169
- recordExtractionError("infra cloudflare", import_node_path58.default.relative(scanPath, service.dir), err);
14636
+ recordExtractionError("infra cloudflare", import_node_path61.default.relative(scanPath, service.dir), err);
14170
14637
  continue;
14171
14638
  }
14172
14639
  if (!read || !read.config.name) continue;
14173
- const evidenceFile = toPosix(import_node_path58.default.relative(scanPath, import_node_path58.default.join(service.dir, read.relFile)));
14640
+ const evidenceFile = toPosix(import_node_path61.default.relative(scanPath, import_node_path61.default.join(service.dir, read.relFile)));
14174
14641
  discovered.push({ service, config: read.config, relFile: read.relFile, raw: read.raw, evidenceFile });
14175
14642
  }
14176
14643
  for (const worker of discovered) {
@@ -14182,7 +14649,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
14182
14649
  }
14183
14650
  let anchorId = service.node.id;
14184
14651
  if (config.main) {
14185
- const entryRelPath = toPosix(import_node_path58.default.normalize(config.main));
14652
+ const entryRelPath = toPosix(import_node_path61.default.normalize(config.main));
14186
14653
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
14187
14654
  graph,
14188
14655
  service.pkg.name,
@@ -14209,15 +14676,15 @@ async function addCloudflareWorkers(graph, services, scanPath) {
14209
14676
  nodesAdded++;
14210
14677
  }
14211
14678
  if (runtimeNode.id !== anchorId) {
14212
- const runsOnId = (0, import_types3.extractedEdgeId)(anchorId, runtimeNode.id, import_types47.EdgeType.RUNS_ON);
14679
+ const runsOnId = (0, import_types3.extractedEdgeId)(anchorId, runtimeNode.id, import_types50.EdgeType.RUNS_ON);
14213
14680
  if (!graph.hasEdge(runsOnId)) {
14214
14681
  const edge = {
14215
14682
  id: runsOnId,
14216
14683
  source: anchorId,
14217
14684
  target: runtimeNode.id,
14218
- type: import_types47.EdgeType.RUNS_ON,
14219
- provenance: import_types47.Provenance.EXTRACTED,
14220
- confidence: (0, import_types47.confidenceForExtracted)("structural"),
14685
+ type: import_types50.EdgeType.RUNS_ON,
14686
+ provenance: import_types50.Provenance.EXTRACTED,
14687
+ confidence: (0, import_types50.confidenceForExtracted)("structural"),
14221
14688
  evidence: {
14222
14689
  file: evidenceFile,
14223
14690
  ...config.compatibility_date ? { snippet: `compatibility_date = ${config.compatibility_date}`.slice(0, 120) } : {}
@@ -14231,7 +14698,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
14231
14698
  const result = addResourceEdge(
14232
14699
  graph,
14233
14700
  anchorId,
14234
- import_types47.EdgeType.CONNECTS_TO,
14701
+ import_types50.EdgeType.CONNECTS_TO,
14235
14702
  "cloudflare-route",
14236
14703
  route,
14237
14704
  evidenceFile,
@@ -14255,7 +14722,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
14255
14722
  const result = addResourceEdge(
14256
14723
  graph,
14257
14724
  anchorId,
14258
- import_types47.EdgeType.DEPENDS_ON,
14725
+ import_types50.EdgeType.DEPENDS_ON,
14259
14726
  group.kind,
14260
14727
  name,
14261
14728
  evidenceFile,
@@ -14269,7 +14736,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
14269
14736
  const result = addResourceEdge(
14270
14737
  graph,
14271
14738
  anchorId,
14272
- import_types47.EdgeType.DEPENDS_ON,
14739
+ import_types50.EdgeType.DEPENDS_ON,
14273
14740
  "cloudflare-cron",
14274
14741
  cron,
14275
14742
  evidenceFile,
@@ -14282,7 +14749,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
14282
14749
  const result = addResourceEdge(
14283
14750
  graph,
14284
14751
  anchorId,
14285
- import_types47.EdgeType.DEPENDS_ON,
14752
+ import_types50.EdgeType.DEPENDS_ON,
14286
14753
  "cloudflare-env-var",
14287
14754
  varName,
14288
14755
  evidenceFile,
@@ -14295,15 +14762,15 @@ async function addCloudflareWorkers(graph, services, scanPath) {
14295
14762
  if (!svc.service) continue;
14296
14763
  const target = workerIndex.get(svc.service);
14297
14764
  if (target && target.anchorId !== anchorId) {
14298
- const edgeId = (0, import_types3.extractedEdgeId)(anchorId, target.anchorId, import_types47.EdgeType.CALLS);
14765
+ const edgeId = (0, import_types3.extractedEdgeId)(anchorId, target.anchorId, import_types50.EdgeType.CALLS);
14299
14766
  if (!graph.hasEdge(edgeId)) {
14300
14767
  const edge = {
14301
14768
  id: edgeId,
14302
14769
  source: anchorId,
14303
14770
  target: target.anchorId,
14304
- type: import_types47.EdgeType.CALLS,
14305
- provenance: import_types47.Provenance.EXTRACTED,
14306
- confidence: (0, import_types47.confidenceForExtracted)("structural"),
14771
+ type: import_types50.EdgeType.CALLS,
14772
+ provenance: import_types50.Provenance.EXTRACTED,
14773
+ confidence: (0, import_types50.confidenceForExtracted)("structural"),
14307
14774
  evidence: { file: evidenceFile, line: lineContaining2(raw, svc.service) }
14308
14775
  };
14309
14776
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -14314,7 +14781,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
14314
14781
  const result = addResourceEdge(
14315
14782
  graph,
14316
14783
  anchorId,
14317
- import_types47.EdgeType.DEPENDS_ON,
14784
+ import_types50.EdgeType.DEPENDS_ON,
14318
14785
  "cloudflare-service-binding",
14319
14786
  svc.service,
14320
14787
  evidenceFile,
@@ -14329,24 +14796,24 @@ async function addCloudflareWorkers(graph, services, scanPath) {
14329
14796
 
14330
14797
  // src/extract/infra/vercel.ts
14331
14798
  init_cjs_shims();
14332
- var import_node_fs27 = require("fs");
14333
- var import_node_path59 = __toESM(require("path"), 1);
14334
- var import_types48 = require("@neat.is/types");
14799
+ var import_node_fs30 = require("fs");
14800
+ var import_node_path62 = __toESM(require("path"), 1);
14801
+ var import_types51 = require("@neat.is/types");
14335
14802
  var VERCEL_CONFIG_FILENAMES = ["vercel.json", "vercel.jsonc"];
14336
14803
  async function readVercelConfig(dir) {
14337
14804
  for (const filename of VERCEL_CONFIG_FILENAMES) {
14338
- const abs = import_node_path59.default.join(dir, filename);
14805
+ const abs = import_node_path62.default.join(dir, filename);
14339
14806
  if (!await exists2(abs)) continue;
14340
- const raw = await import_node_fs27.promises.readFile(abs, "utf8");
14807
+ const raw = await import_node_fs30.promises.readFile(abs, "utf8");
14341
14808
  const config = JSON.parse(maskCommentsInSource(raw));
14342
14809
  return { config, relFile: filename, raw };
14343
14810
  }
14344
14811
  return null;
14345
14812
  }
14346
14813
  async function readLinkedProjectName(dir) {
14347
- const abs = import_node_path59.default.join(dir, ".vercel", "project.json");
14814
+ const abs = import_node_path62.default.join(dir, ".vercel", "project.json");
14348
14815
  if (!await exists2(abs)) return void 0;
14349
- const parsed = JSON.parse(await import_node_fs27.promises.readFile(abs, "utf8"));
14816
+ const parsed = JSON.parse(await import_node_fs30.promises.readFile(abs, "utf8"));
14350
14817
  return typeof parsed.projectName === "string" ? parsed.projectName : void 0;
14351
14818
  }
14352
14819
  function routeSource(route) {
@@ -14362,7 +14829,7 @@ async function addVercelServices(graph, services, scanPath) {
14362
14829
  read = await readVercelConfig(service.dir);
14363
14830
  projectName = await readLinkedProjectName(service.dir);
14364
14831
  } catch (err) {
14365
- recordExtractionError("infra vercel", import_node_path59.default.relative(scanPath, service.dir), err);
14832
+ recordExtractionError("infra vercel", import_node_path62.default.relative(scanPath, service.dir), err);
14366
14833
  continue;
14367
14834
  }
14368
14835
  if (!read && !projectName) continue;
@@ -14378,7 +14845,7 @@ async function addVercelServices(graph, services, scanPath) {
14378
14845
  const anchorId = service.node.id;
14379
14846
  if (!read) continue;
14380
14847
  const { config, relFile, raw } = read;
14381
- const evidenceFile = toPosix(import_node_path59.default.relative(scanPath, import_node_path59.default.join(service.dir, relFile)));
14848
+ const evidenceFile = toPosix(import_node_path62.default.relative(scanPath, import_node_path62.default.join(service.dir, relFile)));
14382
14849
  const add = (edgeType, kind, name) => {
14383
14850
  if (!name) return;
14384
14851
  const result = emitPlatformResourceEdge(
@@ -14394,12 +14861,12 @@ async function addVercelServices(graph, services, scanPath) {
14394
14861
  nodesAdded += result.nodesAdded;
14395
14862
  edgesAdded += result.edgesAdded;
14396
14863
  };
14397
- add(import_types48.EdgeType.RUNS_ON, "vercel", "vercel");
14398
- for (const cron of config.crons ?? []) add(import_types48.EdgeType.DEPENDS_ON, "vercel-cron", cron.path ?? cron.schedule);
14399
- for (const varName of Object.keys(config.env ?? {})) add(import_types48.EdgeType.DEPENDS_ON, "vercel-env-var", varName);
14400
- for (const varName of Object.keys(config.build?.env ?? {})) add(import_types48.EdgeType.DEPENDS_ON, "vercel-env-var", varName);
14864
+ add(import_types51.EdgeType.RUNS_ON, "vercel", "vercel");
14865
+ for (const cron of config.crons ?? []) add(import_types51.EdgeType.DEPENDS_ON, "vercel-cron", cron.path ?? cron.schedule);
14866
+ for (const varName of Object.keys(config.env ?? {})) add(import_types51.EdgeType.DEPENDS_ON, "vercel-env-var", varName);
14867
+ for (const varName of Object.keys(config.build?.env ?? {})) add(import_types51.EdgeType.DEPENDS_ON, "vercel-env-var", varName);
14401
14868
  for (const route of [...config.rewrites ?? [], ...config.redirects ?? [], ...config.routes ?? []]) {
14402
- add(import_types48.EdgeType.CONNECTS_TO, "vercel-route", routeSource(route));
14869
+ add(import_types51.EdgeType.CONNECTS_TO, "vercel-route", routeSource(route));
14403
14870
  }
14404
14871
  }
14405
14872
  return { nodesAdded, edgesAdded };
@@ -14407,17 +14874,17 @@ async function addVercelServices(graph, services, scanPath) {
14407
14874
 
14408
14875
  // src/extract/infra/railway.ts
14409
14876
  init_cjs_shims();
14410
- var import_node_fs28 = require("fs");
14411
- var import_node_path60 = __toESM(require("path"), 1);
14412
- var import_smol_toml3 = require("smol-toml");
14413
- var import_types49 = require("@neat.is/types");
14877
+ var import_node_fs31 = require("fs");
14878
+ var import_node_path63 = __toESM(require("path"), 1);
14879
+ var import_smol_toml4 = require("smol-toml");
14880
+ var import_types52 = require("@neat.is/types");
14414
14881
  var RAILWAY_FILENAMES = ["railway.toml", "railway.json", "railway.jsonc"];
14415
14882
  async function readRailwayConfig(dir) {
14416
14883
  for (const filename of RAILWAY_FILENAMES) {
14417
- const abs = import_node_path60.default.join(dir, filename);
14884
+ const abs = import_node_path63.default.join(dir, filename);
14418
14885
  if (!await exists2(abs)) continue;
14419
- const raw = await import_node_fs28.promises.readFile(abs, "utf8");
14420
- const config = filename === "railway.toml" ? (0, import_smol_toml3.parse)(raw) : JSON.parse(maskCommentsInSource(raw));
14886
+ const raw = await import_node_fs31.promises.readFile(abs, "utf8");
14887
+ const config = filename === "railway.toml" ? (0, import_smol_toml4.parse)(raw) : JSON.parse(maskCommentsInSource(raw));
14421
14888
  return { config, relFile: filename, raw };
14422
14889
  }
14423
14890
  return null;
@@ -14430,7 +14897,7 @@ async function addRailwayServices(graph, services, scanPath) {
14430
14897
  try {
14431
14898
  read = await readRailwayConfig(service.dir);
14432
14899
  } catch (err) {
14433
- recordExtractionError("infra railway", import_node_path60.default.relative(scanPath, service.dir), err);
14900
+ recordExtractionError("infra railway", import_node_path63.default.relative(scanPath, service.dir), err);
14434
14901
  continue;
14435
14902
  }
14436
14903
  if (!read) continue;
@@ -14440,7 +14907,7 @@ async function addRailwayServices(graph, services, scanPath) {
14440
14907
  }
14441
14908
  const anchorId = service.node.id;
14442
14909
  const { config, relFile, raw } = read;
14443
- const evidenceFile = toPosix(import_node_path60.default.relative(scanPath, import_node_path60.default.join(service.dir, relFile)));
14910
+ const evidenceFile = toPosix(import_node_path63.default.relative(scanPath, import_node_path63.default.join(service.dir, relFile)));
14444
14911
  const add = (edgeType, kind, name) => {
14445
14912
  if (!name) return;
14446
14913
  const result = emitPlatformResourceEdge(
@@ -14456,25 +14923,25 @@ async function addRailwayServices(graph, services, scanPath) {
14456
14923
  nodesAdded += result.nodesAdded;
14457
14924
  edgesAdded += result.edgesAdded;
14458
14925
  };
14459
- add(import_types49.EdgeType.RUNS_ON, "railway", "railway");
14460
- add(import_types49.EdgeType.CONNECTS_TO, "railway-route", config.deploy?.healthcheckPath);
14461
- add(import_types49.EdgeType.DEPENDS_ON, "railway-cron", config.deploy?.cronSchedule);
14926
+ add(import_types52.EdgeType.RUNS_ON, "railway", "railway");
14927
+ add(import_types52.EdgeType.CONNECTS_TO, "railway-route", config.deploy?.healthcheckPath);
14928
+ add(import_types52.EdgeType.DEPENDS_ON, "railway-cron", config.deploy?.cronSchedule);
14462
14929
  }
14463
14930
  return { nodesAdded, edgesAdded };
14464
14931
  }
14465
14932
 
14466
14933
  // src/extract/infra/supabase.ts
14467
14934
  init_cjs_shims();
14468
- var import_node_fs29 = require("fs");
14469
- var import_node_path61 = __toESM(require("path"), 1);
14470
- var import_smol_toml4 = require("smol-toml");
14471
- var import_types50 = require("@neat.is/types");
14935
+ var import_node_fs32 = require("fs");
14936
+ var import_node_path64 = __toESM(require("path"), 1);
14937
+ var import_smol_toml5 = require("smol-toml");
14938
+ var import_types53 = require("@neat.is/types");
14472
14939
  async function readSupabaseConfig(dir) {
14473
- const relFile = import_node_path61.default.join("supabase", "config.toml");
14474
- const abs = import_node_path61.default.join(dir, relFile);
14940
+ const relFile = import_node_path64.default.join("supabase", "config.toml");
14941
+ const abs = import_node_path64.default.join(dir, relFile);
14475
14942
  if (!await exists2(abs)) return null;
14476
- const raw = await import_node_fs29.promises.readFile(abs, "utf8");
14477
- const config = (0, import_smol_toml4.parse)(raw);
14943
+ const raw = await import_node_fs32.promises.readFile(abs, "utf8");
14944
+ const config = (0, import_smol_toml5.parse)(raw);
14478
14945
  return { config, relFile, raw };
14479
14946
  }
14480
14947
  async function addSupabaseProjects(graph, services, scanPath) {
@@ -14485,7 +14952,7 @@ async function addSupabaseProjects(graph, services, scanPath) {
14485
14952
  try {
14486
14953
  read = await readSupabaseConfig(service.dir);
14487
14954
  } catch (err) {
14488
- recordExtractionError("infra supabase", import_node_path61.default.relative(scanPath, service.dir), err);
14955
+ recordExtractionError("infra supabase", import_node_path64.default.relative(scanPath, service.dir), err);
14489
14956
  continue;
14490
14957
  }
14491
14958
  if (!read) continue;
@@ -14500,7 +14967,7 @@ async function addSupabaseProjects(graph, services, scanPath) {
14500
14967
  });
14501
14968
  }
14502
14969
  const anchorId = service.node.id;
14503
- const evidenceFile = toPosix(import_node_path61.default.relative(scanPath, import_node_path61.default.join(service.dir, relFile)));
14970
+ const evidenceFile = toPosix(import_node_path64.default.relative(scanPath, import_node_path64.default.join(service.dir, relFile)));
14504
14971
  const add = (edgeType, kind, name) => {
14505
14972
  if (!name) return;
14506
14973
  const result = emitPlatformResourceEdge(
@@ -14516,10 +14983,10 @@ async function addSupabaseProjects(graph, services, scanPath) {
14516
14983
  nodesAdded += result.nodesAdded;
14517
14984
  edgesAdded += result.edgesAdded;
14518
14985
  };
14519
- add(import_types50.EdgeType.RUNS_ON, "supabase", "supabase");
14520
- for (const fn of Object.keys(config.functions ?? {})) add(import_types50.EdgeType.DEPENDS_ON, "supabase-function", fn);
14521
- if (config.storage) add(import_types50.EdgeType.DEPENDS_ON, "supabase-storage", "storage");
14522
- if (config.auth) add(import_types50.EdgeType.DEPENDS_ON, "supabase-auth", "auth");
14986
+ add(import_types53.EdgeType.RUNS_ON, "supabase", "supabase");
14987
+ for (const fn of Object.keys(config.functions ?? {})) add(import_types53.EdgeType.DEPENDS_ON, "supabase-function", fn);
14988
+ if (config.storage) add(import_types53.EdgeType.DEPENDS_ON, "supabase-storage", "storage");
14989
+ if (config.auth) add(import_types53.EdgeType.DEPENDS_ON, "supabase-auth", "auth");
14523
14990
  }
14524
14991
  return { nodesAdded, edgesAdded };
14525
14992
  }
@@ -14542,10 +15009,10 @@ async function addInfra(graph, scanPath, services) {
14542
15009
 
14543
15010
  // src/extract/zod-shapes.ts
14544
15011
  init_cjs_shims();
14545
- var import_node_path62 = __toESM(require("path"), 1);
15012
+ var import_node_path65 = __toESM(require("path"), 1);
14546
15013
  var import_tree_sitter16 = __toESM(require("tree-sitter"), 1);
14547
15014
  var import_tree_sitter_javascript8 = __toESM(require("tree-sitter-javascript"), 1);
14548
- var import_types51 = require("@neat.is/types");
15015
+ var import_types54 = require("@neat.is/types");
14549
15016
  var ZOD_IMPORT_RE = /\bzod\b/;
14550
15017
  var ZOD_OBJECTS = /* @__PURE__ */ new Set(["z", "zod"]);
14551
15018
  function parserForExt4(ext) {
@@ -14637,7 +15104,7 @@ function topLevelSchemas(root) {
14637
15104
  }
14638
15105
  function zodShapesFromFile(file, serviceDir) {
14639
15106
  if (!ZOD_IMPORT_RE.test(file.content)) return [];
14640
- const tree = parseSource3(parserForExt4(import_node_path62.default.extname(file.path)), file.content);
15107
+ const tree = parseSource3(parserForExt4(import_node_path65.default.extname(file.path)), file.content);
14641
15108
  const out = [];
14642
15109
  const seen = /* @__PURE__ */ new Set();
14643
15110
  for (const { name, call } of topLevelSchemas(tree.rootNode)) {
@@ -14651,11 +15118,11 @@ function zodShapesFromFile(file, serviceDir) {
14651
15118
  seen.add(name);
14652
15119
  const line = call.startPosition.row + 1;
14653
15120
  out.push({
14654
- infraId: (0, import_types51.infraId)("zod-schema", name),
15121
+ infraId: (0, import_types54.infraId)("zod-schema", name),
14655
15122
  name,
14656
15123
  fields,
14657
15124
  evidence: {
14658
- file: import_node_path62.default.relative(serviceDir, file.path),
15125
+ file: import_node_path65.default.relative(serviceDir, file.path),
14659
15126
  line,
14660
15127
  snippet: snippet(file.content, line)
14661
15128
  }
@@ -14672,7 +15139,7 @@ async function addZodShapes(graph, services) {
14672
15139
  ...service.pkg.devDependencies ?? {}
14673
15140
  };
14674
15141
  if (deps["zod"] === void 0) continue;
14675
- const files = await loadSourceFiles(service.dir);
15142
+ const files = await loadSourceFiles(service.dir, service.excludeDirs);
14676
15143
  for (const file of files) {
14677
15144
  if (isTestPath(file.path)) continue;
14678
15145
  let shapes;
@@ -14686,7 +15153,7 @@ async function addZodShapes(graph, services) {
14686
15153
  if (!graph.hasNode(shape.infraId)) {
14687
15154
  const node = {
14688
15155
  id: shape.infraId,
14689
- type: import_types51.NodeType.InfraNode,
15156
+ type: import_types54.NodeType.InfraNode,
14690
15157
  name: shape.name,
14691
15158
  provider: "self",
14692
15159
  kind: "zod-schema"
@@ -14696,14 +15163,14 @@ async function addZodShapes(graph, services) {
14696
15163
  }
14697
15164
  if (shape.fields.length > 0) {
14698
15165
  const node = graph.getNodeAttributes(shape.infraId);
14699
- if (node.type === import_types51.NodeType.InfraNode) {
15166
+ if (node.type === import_types54.NodeType.InfraNode) {
14700
15167
  graph.replaceNodeAttributes(shape.infraId, {
14701
15168
  ...node,
14702
15169
  columns: foldColumns(
14703
15170
  node.columns,
14704
15171
  shape.fields,
14705
- import_types51.Provenance.EXTRACTED,
14706
- (0, import_types51.confidenceForExtracted)("structural")
15172
+ import_types54.Provenance.EXTRACTED,
15173
+ (0, import_types54.confidenceForExtracted)("structural")
14707
15174
  )
14708
15175
  });
14709
15176
  }
@@ -14717,15 +15184,15 @@ async function addZodShapes(graph, services) {
14717
15184
  );
14718
15185
  nodesAdded += n;
14719
15186
  edgesAdded += e;
14720
- const edgeId = (0, import_types51.extractedEdgeId)(fileNodeId, shape.infraId, import_types51.EdgeType.CONTAINS);
15187
+ const edgeId = (0, import_types54.extractedEdgeId)(fileNodeId, shape.infraId, import_types54.EdgeType.CONTAINS);
14721
15188
  if (!graph.hasEdge(edgeId)) {
14722
15189
  const edge = {
14723
15190
  id: edgeId,
14724
15191
  source: fileNodeId,
14725
15192
  target: shape.infraId,
14726
- type: import_types51.EdgeType.CONTAINS,
14727
- provenance: import_types51.Provenance.EXTRACTED,
14728
- confidence: (0, import_types51.confidenceForExtracted)("structural"),
15193
+ type: import_types54.EdgeType.CONTAINS,
15194
+ provenance: import_types54.Provenance.EXTRACTED,
15195
+ confidence: (0, import_types54.confidenceForExtracted)("structural"),
14729
15196
  evidence: shape.evidence
14730
15197
  };
14731
15198
  graph.addEdgeWithKey(edgeId, fileNodeId, shape.infraId, edge);
@@ -14739,7 +15206,7 @@ async function addZodShapes(graph, services) {
14739
15206
 
14740
15207
  // src/extract/firestore-rules.ts
14741
15208
  init_cjs_shims();
14742
- var import_types52 = require("@neat.is/types");
15209
+ var import_types55 = require("@neat.is/types");
14743
15210
  var FIRESTORE_COLLECTION_KIND = "firestore-collection";
14744
15211
  var WRITE_METHODS = /* @__PURE__ */ new Set(["write", "create", "update"]);
14745
15212
  function stripComments(src) {
@@ -14879,7 +15346,7 @@ async function addFirestoreRules(graph, services) {
14879
15346
  if (guards.size === 0) return { nodesAdded: 0, edgesAdded: 0 };
14880
15347
  graph.forEachNode((id, attrs) => {
14881
15348
  const node = attrs;
14882
- if (node.type !== import_types52.NodeType.InfraNode) return;
15349
+ if (node.type !== import_types55.NodeType.InfraNode) return;
14883
15350
  if (node.kind !== FIRESTORE_COLLECTION_KIND) return;
14884
15351
  const fields = guards.get(collectionKeyFromName(node.name));
14885
15352
  if (!fields || fields.size === 0) return;
@@ -14892,17 +15359,17 @@ async function addFirestoreRules(graph, services) {
14892
15359
  }
14893
15360
 
14894
15361
  // src/extract/index.ts
14895
- var import_node_path64 = __toESM(require("path"), 1);
15362
+ var import_node_path67 = __toESM(require("path"), 1);
14896
15363
 
14897
15364
  // src/extract/retire.ts
14898
15365
  init_cjs_shims();
14899
- var import_node_fs30 = require("fs");
14900
- var import_node_path63 = __toESM(require("path"), 1);
14901
- var import_types53 = require("@neat.is/types");
15366
+ var import_node_fs33 = require("fs");
15367
+ var import_node_path66 = __toESM(require("path"), 1);
15368
+ var import_types56 = require("@neat.is/types");
14902
15369
  function dropOrphanedFileNodes(graph) {
14903
15370
  const orphans = [];
14904
15371
  graph.forEachNode((id, attrs) => {
14905
- if (attrs.type !== import_types53.NodeType.FileNode) return;
15372
+ if (attrs.type !== import_types56.NodeType.FileNode) return;
14906
15373
  if (graph.inboundEdges(id).length === 0 && graph.outboundEdges(id).length === 0) {
14907
15374
  orphans.push(id);
14908
15375
  }
@@ -14915,14 +15382,14 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
14915
15382
  const bases = [scanPath, ...serviceDirs];
14916
15383
  graph.forEachEdge((id, attrs) => {
14917
15384
  const edge = attrs;
14918
- if (edge.provenance !== import_types53.Provenance.EXTRACTED) return;
15385
+ if (edge.provenance !== import_types56.Provenance.EXTRACTED) return;
14919
15386
  const evidenceFile = edge.evidence?.file;
14920
15387
  if (!evidenceFile) return;
14921
- if (import_node_path63.default.isAbsolute(evidenceFile)) {
14922
- if (!(0, import_node_fs30.existsSync)(evidenceFile)) toDrop.push(id);
15388
+ if (import_node_path66.default.isAbsolute(evidenceFile)) {
15389
+ if (!(0, import_node_fs33.existsSync)(evidenceFile)) toDrop.push(id);
14923
15390
  return;
14924
15391
  }
14925
- const found = bases.some((base) => (0, import_node_fs30.existsSync)(import_node_path63.default.join(base, evidenceFile)));
15392
+ const found = bases.some((base) => (0, import_node_fs33.existsSync)(import_node_path66.default.join(base, evidenceFile)));
14926
15393
  if (!found) toDrop.push(id);
14927
15394
  });
14928
15395
  for (const id of toDrop) graph.dropEdge(id);
@@ -14979,7 +15446,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
14979
15446
  }
14980
15447
  const droppedEntries = drainDroppedExtracted();
14981
15448
  if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
14982
- const rejectedPath = import_node_path64.default.join(import_node_path64.default.dirname(opts.errorsPath), "rejected.ndjson");
15449
+ const rejectedPath = import_node_path67.default.join(import_node_path67.default.dirname(opts.errorsPath), "rejected.ndjson");
14983
15450
  try {
14984
15451
  await writeRejectedExtracted(droppedEntries, rejectedPath);
14985
15452
  } catch (err) {
@@ -15013,7 +15480,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
15013
15480
 
15014
15481
  // src/diff.ts
15015
15482
  init_cjs_shims();
15016
- var import_node_fs31 = require("fs");
15483
+ var import_node_fs34 = require("fs");
15017
15484
  async function loadSnapshotForDiff(target) {
15018
15485
  if (/^https?:\/\//i.test(target)) {
15019
15486
  const res = await fetch(target);
@@ -15022,7 +15489,7 @@ async function loadSnapshotForDiff(target) {
15022
15489
  }
15023
15490
  return await res.json();
15024
15491
  }
15025
- const raw = await import_node_fs31.promises.readFile(target, "utf8");
15492
+ const raw = await import_node_fs34.promises.readFile(target, "utf8");
15026
15493
  return JSON.parse(raw);
15027
15494
  }
15028
15495
  function indexEntries(entries) {
@@ -15090,9 +15557,9 @@ function canonicalJson(value) {
15090
15557
 
15091
15558
  // src/persist.ts
15092
15559
  init_cjs_shims();
15093
- var import_node_fs32 = require("fs");
15094
- var import_node_path65 = __toESM(require("path"), 1);
15095
- var import_types54 = require("@neat.is/types");
15560
+ var import_node_fs35 = require("fs");
15561
+ var import_node_path68 = __toESM(require("path"), 1);
15562
+ var import_types57 = require("@neat.is/types");
15096
15563
  var SCHEMA_VERSION = 7;
15097
15564
  function migrateV1ToV2(payload) {
15098
15565
  const nodes = payload.graph.nodes;
@@ -15116,7 +15583,7 @@ function migrateV5ToV6(payload) {
15116
15583
  if (Array.isArray(nodes)) {
15117
15584
  for (const node of nodes) {
15118
15585
  const attrs = node.attributes;
15119
- if (!attrs || attrs.type !== import_types54.NodeType.InfraNode) continue;
15586
+ if (!attrs || attrs.type !== import_types57.NodeType.InfraNode) continue;
15120
15587
  if (attrs.kind !== "sql-table" && attrs.kind !== "supabase-table") continue;
15121
15588
  if (!Array.isArray(attrs.columns)) attrs.columns = [];
15122
15589
  }
@@ -15132,12 +15599,12 @@ function migrateV2ToV3(payload) {
15132
15599
  for (const edge of edges) {
15133
15600
  const attrs = edge.attributes;
15134
15601
  if (!attrs || attrs.provenance !== "FRONTIER") continue;
15135
- attrs.provenance = import_types54.Provenance.OBSERVED;
15602
+ attrs.provenance = import_types57.Provenance.OBSERVED;
15136
15603
  const type = typeof attrs.type === "string" ? attrs.type : void 0;
15137
15604
  const source = typeof attrs.source === "string" ? attrs.source : void 0;
15138
15605
  const target = typeof attrs.target === "string" ? attrs.target : void 0;
15139
15606
  if (type && source && target) {
15140
- const newId = (0, import_types54.observedEdgeId)(source, target, type);
15607
+ const newId = (0, import_types57.observedEdgeId)(source, target, type);
15141
15608
  attrs.id = newId;
15142
15609
  if (edge.key) edge.key = newId;
15143
15610
  }
@@ -15146,7 +15613,7 @@ function migrateV2ToV3(payload) {
15146
15613
  return { ...payload, schemaVersion: 3 };
15147
15614
  }
15148
15615
  async function ensureDir(filePath) {
15149
- await import_node_fs32.promises.mkdir(import_node_path65.default.dirname(filePath), { recursive: true });
15616
+ await import_node_fs35.promises.mkdir(import_node_path68.default.dirname(filePath), { recursive: true });
15150
15617
  }
15151
15618
  async function saveGraphToDisk(graph, outPath) {
15152
15619
  await ensureDir(outPath);
@@ -15156,13 +15623,13 @@ async function saveGraphToDisk(graph, outPath) {
15156
15623
  graph: graph.export()
15157
15624
  };
15158
15625
  const tmp = `${outPath}.tmp`;
15159
- await import_node_fs32.promises.writeFile(tmp, JSON.stringify(payload), "utf8");
15160
- await import_node_fs32.promises.rename(tmp, outPath);
15626
+ await import_node_fs35.promises.writeFile(tmp, JSON.stringify(payload), "utf8");
15627
+ await import_node_fs35.promises.rename(tmp, outPath);
15161
15628
  }
15162
15629
  async function loadGraphFromDisk(graph, outPath) {
15163
15630
  let raw;
15164
15631
  try {
15165
- raw = await import_node_fs32.promises.readFile(outPath, "utf8");
15632
+ raw = await import_node_fs35.promises.readFile(outPath, "utf8");
15166
15633
  } catch (err) {
15167
15634
  if (err.code === "ENOENT") return;
15168
15635
  throw err;
@@ -15236,23 +15703,23 @@ function startPersistLoop(graph, outPath, opts = {}) {
15236
15703
 
15237
15704
  // src/projects.ts
15238
15705
  init_cjs_shims();
15239
- var import_node_path66 = __toESM(require("path"), 1);
15706
+ var import_node_path69 = __toESM(require("path"), 1);
15240
15707
  function pathsForProject(project, baseDir) {
15241
15708
  if (project === DEFAULT_PROJECT) {
15242
15709
  return {
15243
- snapshotPath: import_node_path66.default.join(baseDir, "graph.json"),
15244
- errorsPath: import_node_path66.default.join(baseDir, "errors.ndjson"),
15245
- staleEventsPath: import_node_path66.default.join(baseDir, "stale-events.ndjson"),
15246
- embeddingsCachePath: import_node_path66.default.join(baseDir, "embeddings.json"),
15247
- policyViolationsPath: import_node_path66.default.join(baseDir, "policy-violations.ndjson")
15710
+ snapshotPath: import_node_path69.default.join(baseDir, "graph.json"),
15711
+ errorsPath: import_node_path69.default.join(baseDir, "errors.ndjson"),
15712
+ staleEventsPath: import_node_path69.default.join(baseDir, "stale-events.ndjson"),
15713
+ embeddingsCachePath: import_node_path69.default.join(baseDir, "embeddings.json"),
15714
+ policyViolationsPath: import_node_path69.default.join(baseDir, "policy-violations.ndjson")
15248
15715
  };
15249
15716
  }
15250
15717
  return {
15251
- snapshotPath: import_node_path66.default.join(baseDir, `${project}.json`),
15252
- errorsPath: import_node_path66.default.join(baseDir, `errors.${project}.ndjson`),
15253
- staleEventsPath: import_node_path66.default.join(baseDir, `stale-events.${project}.ndjson`),
15254
- embeddingsCachePath: import_node_path66.default.join(baseDir, `embeddings.${project}.json`),
15255
- policyViolationsPath: import_node_path66.default.join(baseDir, `policy-violations.${project}.ndjson`)
15718
+ snapshotPath: import_node_path69.default.join(baseDir, `${project}.json`),
15719
+ errorsPath: import_node_path69.default.join(baseDir, `errors.${project}.ndjson`),
15720
+ staleEventsPath: import_node_path69.default.join(baseDir, `stale-events.${project}.ndjson`),
15721
+ embeddingsCachePath: import_node_path69.default.join(baseDir, `embeddings.${project}.json`),
15722
+ policyViolationsPath: import_node_path69.default.join(baseDir, `policy-violations.${project}.ndjson`)
15256
15723
  };
15257
15724
  }
15258
15725
  var Projects = class {
@@ -15292,20 +15759,20 @@ function parseExtraProjects(raw) {
15292
15759
 
15293
15760
  // src/registry.ts
15294
15761
  init_cjs_shims();
15295
- var import_node_fs33 = require("fs");
15762
+ var import_node_fs36 = require("fs");
15296
15763
  var import_node_os3 = __toESM(require("os"), 1);
15297
- var import_node_path67 = __toESM(require("path"), 1);
15298
- var import_types55 = require("@neat.is/types");
15764
+ var import_node_path70 = __toESM(require("path"), 1);
15765
+ var import_types58 = require("@neat.is/types");
15299
15766
  function neatHome() {
15300
15767
  const override = process.env.NEAT_HOME;
15301
- if (override && override.length > 0) return import_node_path67.default.resolve(override);
15302
- return import_node_path67.default.join(import_node_os3.default.homedir(), ".neat");
15768
+ if (override && override.length > 0) return import_node_path70.default.resolve(override);
15769
+ return import_node_path70.default.join(import_node_os3.default.homedir(), ".neat");
15303
15770
  }
15304
15771
  function registryPath() {
15305
- return import_node_path67.default.join(neatHome(), "projects.json");
15772
+ return import_node_path70.default.join(neatHome(), "projects.json");
15306
15773
  }
15307
15774
  function daemonsDir() {
15308
- return import_node_path67.default.join(neatHome(), "daemons");
15775
+ return import_node_path70.default.join(neatHome(), "daemons");
15309
15776
  }
15310
15777
  function isFiniteInt(v) {
15311
15778
  return typeof v === "number" && Number.isFinite(v);
@@ -15338,7 +15805,7 @@ async function discoverDaemons(probe = defaultDiscoveryProbe) {
15338
15805
  const dir = daemonsDir();
15339
15806
  let names;
15340
15807
  try {
15341
- names = await import_node_fs33.promises.readdir(dir);
15808
+ names = await import_node_fs36.promises.readdir(dir);
15342
15809
  } catch (err) {
15343
15810
  if (err.code === "ENOENT") return [];
15344
15811
  throw err;
@@ -15346,10 +15813,10 @@ async function discoverDaemons(probe = defaultDiscoveryProbe) {
15346
15813
  const out = [];
15347
15814
  for (const name of names) {
15348
15815
  if (!name.endsWith(".json")) continue;
15349
- const file = import_node_path67.default.join(dir, name);
15816
+ const file = import_node_path70.default.join(dir, name);
15350
15817
  let raw;
15351
15818
  try {
15352
- raw = await import_node_fs33.promises.readFile(file, "utf8");
15819
+ raw = await import_node_fs36.promises.readFile(file, "utf8");
15353
15820
  } catch {
15354
15821
  continue;
15355
15822
  }
@@ -15377,7 +15844,7 @@ async function readRegistry() {
15377
15844
  const file = registryPath();
15378
15845
  let raw;
15379
15846
  try {
15380
- raw = await import_node_fs33.promises.readFile(file, "utf8");
15847
+ raw = await import_node_fs36.promises.readFile(file, "utf8");
15381
15848
  } catch (err) {
15382
15849
  if (err.code === "ENOENT") {
15383
15850
  return { version: 1, projects: [] };
@@ -15385,7 +15852,7 @@ async function readRegistry() {
15385
15852
  throw err;
15386
15853
  }
15387
15854
  const parsed = JSON.parse(raw);
15388
- return import_types55.RegistryFileSchema.parse(parsed);
15855
+ return import_types58.RegistryFileSchema.parse(parsed);
15389
15856
  }
15390
15857
  async function getProject(name) {
15391
15858
  const reg = await readRegistry();
@@ -15460,8 +15927,8 @@ init_auth();
15460
15927
  // src/connectors-config.ts
15461
15928
  init_cjs_shims();
15462
15929
  var import_node_os4 = __toESM(require("os"), 1);
15463
- var import_node_path68 = __toESM(require("path"), 1);
15464
- var import_node_fs34 = require("fs");
15930
+ var import_node_path71 = __toESM(require("path"), 1);
15931
+ var import_node_fs37 = require("fs");
15465
15932
  var CONNECTORS_CONFIG_VERSION = 1;
15466
15933
  var EnvRefUnsetError = class extends Error {
15467
15934
  ref;
@@ -15475,17 +15942,17 @@ var EnvRefUnsetError = class extends Error {
15475
15942
  };
15476
15943
  function neatHome2() {
15477
15944
  const override = process.env.NEAT_HOME;
15478
- if (override && override.length > 0) return import_node_path68.default.resolve(override);
15479
- return import_node_path68.default.join(import_node_os4.default.homedir(), ".neat");
15945
+ if (override && override.length > 0) return import_node_path71.default.resolve(override);
15946
+ return import_node_path71.default.join(import_node_os4.default.homedir(), ".neat");
15480
15947
  }
15481
15948
  function connectorsConfigPath(home = neatHome2()) {
15482
- return import_node_path68.default.join(home, "connectors.json");
15949
+ return import_node_path71.default.join(home, "connectors.json");
15483
15950
  }
15484
15951
  var MODE_MASK_LOOSER_THAN_0600 = 63;
15485
15952
  async function warnIfModeLooserThan0600(file) {
15486
15953
  if (process.platform === "win32") return;
15487
15954
  try {
15488
- const stat = await import_node_fs34.promises.stat(file);
15955
+ const stat = await import_node_fs37.promises.stat(file);
15489
15956
  if ((stat.mode & MODE_MASK_LOOSER_THAN_0600) !== 0) {
15490
15957
  const mode = (stat.mode & 511).toString(8).padStart(3, "0");
15491
15958
  console.warn(
@@ -15499,7 +15966,7 @@ async function readConnectorsConfig(home = neatHome2()) {
15499
15966
  const file = connectorsConfigPath(home);
15500
15967
  let raw;
15501
15968
  try {
15502
- raw = await import_node_fs34.promises.readFile(file, "utf8");
15969
+ raw = await import_node_fs37.promises.readFile(file, "utf8");
15503
15970
  } catch (err) {
15504
15971
  if (err.code === "ENOENT") {
15505
15972
  return { version: CONNECTORS_CONFIG_VERSION, connectors: [] };
@@ -15666,15 +16133,15 @@ function getConnectorStatus(id, now = Date.now(), thresholdMs = CONNECTOR_STALE_
15666
16133
 
15667
16134
  // src/connectors/index.ts
15668
16135
  init_cjs_shims();
15669
- var import_types56 = require("@neat.is/types");
16136
+ var import_types59 = require("@neat.is/types");
15670
16137
  var NO_ENV = "unknown";
15671
16138
  function staticCallSiteFor(graph, serviceName, targetNodeId) {
15672
16139
  if (!graph.hasNode(targetNodeId)) return void 0;
15673
16140
  const sites = [];
15674
16141
  for (const edgeId of graph.inboundEdges(targetNodeId)) {
15675
16142
  const edge = graph.getEdgeAttributes(edgeId);
15676
- if (edge.provenance !== import_types56.Provenance.EXTRACTED) continue;
15677
- const parsed = (0, import_types56.parseFileId)(edge.source);
16143
+ if (edge.provenance !== import_types59.Provenance.EXTRACTED) continue;
16144
+ const parsed = (0, import_types59.parseFileId)(edge.source);
15678
16145
  if (!parsed || parsed.service !== serviceName || !edge.evidence) continue;
15679
16146
  const site = { relPath: edge.evidence.file };
15680
16147
  if (edge.evidence.line !== void 0) site.line = edge.evidence.line;
@@ -15685,7 +16152,7 @@ function staticCallSiteFor(graph, serviceName, targetNodeId) {
15685
16152
  function routeCallSiteFor(graph, targetNodeId) {
15686
16153
  if (!graph.hasNode(targetNodeId)) return void 0;
15687
16154
  const attrs = graph.getNodeAttributes(targetNodeId);
15688
- if (attrs.type !== import_types56.NodeType.RouteNode || !attrs.path) return void 0;
16155
+ if (attrs.type !== import_types59.NodeType.RouteNode || !attrs.path) return void 0;
15689
16156
  const site = { relPath: attrs.path };
15690
16157
  if (attrs.line !== void 0) site.line = attrs.line;
15691
16158
  return site;
@@ -16142,10 +16609,10 @@ var SUPABASE_RPC_TARGET_KIND = "supabase-rpc";
16142
16609
  // src/connectors/supabase/map.ts
16143
16610
  var REST_RPC_PATH_RE = /^\/rest\/v1\/rpc\/([^/?]+)/;
16144
16611
  var REST_TABLE_PATH_RE = /^\/rest\/v1\/([^/?]+)/;
16145
- function targetFromRestPath(path71) {
16146
- const rpcMatch = REST_RPC_PATH_RE.exec(path71);
16612
+ function targetFromRestPath(path74) {
16613
+ const rpcMatch = REST_RPC_PATH_RE.exec(path74);
16147
16614
  if (rpcMatch) return { targetKind: SUPABASE_RPC_TARGET_KIND, name: rpcMatch[1] };
16148
- const tableMatch = REST_TABLE_PATH_RE.exec(path71);
16615
+ const tableMatch = REST_TABLE_PATH_RE.exec(path74);
16149
16616
  if (tableMatch) return { targetKind: SUPABASE_TABLE_TARGET_KIND, name: tableMatch[1] };
16150
16617
  return null;
16151
16618
  }
@@ -16256,23 +16723,23 @@ async function fetchPgStatStatements(connectionString, limit = DEFAULT_STATEMENT
16256
16723
 
16257
16724
  // src/connectors/supabase/resolve.ts
16258
16725
  init_cjs_shims();
16259
- var import_types58 = require("@neat.is/types");
16726
+ var import_types61 = require("@neat.is/types");
16260
16727
  function createSupabaseResolveTarget(graph, config) {
16261
16728
  return (signal, _ctx) => {
16262
16729
  if (signal.targetKind !== SUPABASE_TABLE_TARGET_KIND && signal.targetKind !== SUPABASE_RPC_TARGET_KIND) {
16263
16730
  return null;
16264
16731
  }
16265
- const subResourceId = (0, import_types58.infraId)(signal.targetKind, `${config.nodeRef}/${signal.targetName}`);
16732
+ const subResourceId = (0, import_types61.infraId)(signal.targetKind, `${config.nodeRef}/${signal.targetName}`);
16266
16733
  if (graph.hasNode(subResourceId)) {
16267
- return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType: import_types58.EdgeType.CALLS };
16734
+ return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType: import_types61.EdgeType.CALLS };
16268
16735
  }
16269
- const bareResourceId = (0, import_types58.infraId)(signal.targetKind, signal.targetName);
16736
+ const bareResourceId = (0, import_types61.infraId)(signal.targetKind, signal.targetName);
16270
16737
  if (graph.hasNode(bareResourceId)) {
16271
- return { targetNodeId: bareResourceId, serviceName: config.serviceName, edgeType: import_types58.EdgeType.CALLS };
16738
+ return { targetNodeId: bareResourceId, serviceName: config.serviceName, edgeType: import_types61.EdgeType.CALLS };
16272
16739
  }
16273
- const projectLevelId = (0, import_types58.infraId)("supabase", config.nodeRef);
16740
+ const projectLevelId = (0, import_types61.infraId)("supabase", config.nodeRef);
16274
16741
  if (graph.hasNode(projectLevelId)) {
16275
- return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType: import_types58.EdgeType.CALLS };
16742
+ return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType: import_types61.EdgeType.CALLS };
16276
16743
  }
16277
16744
  return null;
16278
16745
  };
@@ -16365,7 +16832,7 @@ function createSupabaseConnector(graph, config, deps = {}) {
16365
16832
 
16366
16833
  // src/connectors/railway/index.ts
16367
16834
  init_cjs_shims();
16368
- var import_types62 = require("@neat.is/types");
16835
+ var import_types65 = require("@neat.is/types");
16369
16836
 
16370
16837
  // src/connectors/railway/client.ts
16371
16838
  init_cjs_shims();
@@ -16516,7 +16983,7 @@ function buildRailwayRouteIndex(graph, serviceName) {
16516
16983
  const out = [];
16517
16984
  graph.forEachNode((_id, attrs) => {
16518
16985
  const node = attrs;
16519
- if (node.type !== import_types62.NodeType.RouteNode) return;
16986
+ if (node.type !== import_types65.NodeType.RouteNode) return;
16520
16987
  const route = attrs;
16521
16988
  if (route.service !== serviceName) return;
16522
16989
  out.push({
@@ -16620,12 +17087,12 @@ function createRailwayResolveTarget(config) {
16620
17087
  const serviceName = config.serviceNameById[config.serviceId];
16621
17088
  if (!serviceName) return null;
16622
17089
  if (signal.targetKind === ROUTE_TARGET_KIND) {
16623
- return { targetNodeId: signal.targetName, serviceName, edgeType: import_types62.EdgeType.CALLS };
17090
+ return { targetNodeId: signal.targetName, serviceName, edgeType: import_types65.EdgeType.CALLS };
16624
17091
  }
16625
17092
  if (signal.targetKind === PEER_SERVICE_TARGET_KIND) {
16626
17093
  const peerName = config.serviceNameById[signal.targetName];
16627
17094
  if (!peerName) return null;
16628
- return { targetNodeId: (0, import_types62.serviceId)(peerName), serviceName, edgeType: import_types62.EdgeType.CONNECTS_TO };
17095
+ return { targetNodeId: (0, import_types65.serviceId)(peerName), serviceName, edgeType: import_types65.EdgeType.CONNECTS_TO };
16629
17096
  }
16630
17097
  return null;
16631
17098
  };
@@ -16749,9 +17216,9 @@ function parseFirebaseTargetName(targetName) {
16749
17216
  const secondSep = rest.indexOf(FIELD_SEP);
16750
17217
  if (secondSep === -1) return null;
16751
17218
  const method = rest.slice(0, secondSep);
16752
- const path71 = rest.slice(secondSep + 1);
16753
- if (!resourceName || !method || !path71) return null;
16754
- return { resourceName, method, path: path71 };
17219
+ const path74 = rest.slice(secondSep + 1);
17220
+ if (!resourceName || !method || !path74) return null;
17221
+ return { resourceName, method, path: path74 };
16755
17222
  }
16756
17223
  function resourceNameFor(type, labels) {
16757
17224
  if (!labels) return null;
@@ -16789,14 +17256,14 @@ function mapLogEntryToSignal(entry) {
16789
17256
  if (!req) return null;
16790
17257
  if (typeof req.requestMethod !== "string" || req.requestMethod.length === 0) return null;
16791
17258
  const method = req.requestMethod.toUpperCase();
16792
- const path71 = pathFromRequestUrl(req.requestUrl);
16793
- if (path71 === null) return null;
17259
+ const path74 = pathFromRequestUrl(req.requestUrl);
17260
+ if (path74 === null) return null;
16794
17261
  const timestamp = entry.timestamp;
16795
17262
  if (typeof timestamp !== "string" || timestamp.length === 0) return null;
16796
17263
  const isError = typeof req.status === "number" && req.status >= ERROR_STATUS_THRESHOLD2;
16797
17264
  return {
16798
17265
  targetKind: resourceType,
16799
- targetName: packFirebaseTargetName({ resourceName, method, path: path71 }),
17266
+ targetName: packFirebaseTargetName({ resourceName, method, path: path74 }),
16800
17267
  callCount: 1,
16801
17268
  errorCount: isError ? 1 : 0,
16802
17269
  lastObservedIso: timestamp
@@ -16813,7 +17280,7 @@ function mapLogEntriesToSignals(entries) {
16813
17280
 
16814
17281
  // src/connectors/firebase/resolve.ts
16815
17282
  init_cjs_shims();
16816
- var import_types63 = require("@neat.is/types");
17283
+ var import_types66 = require("@neat.is/types");
16817
17284
  function neatServiceNameFor(resourceType, resourceName, serviceMap) {
16818
17285
  switch (resourceType) {
16819
17286
  case "cloud_function":
@@ -16828,7 +17295,7 @@ function routeEntriesFor(graph, serviceName) {
16828
17295
  const entries = [];
16829
17296
  graph.forEachNode((_id, attrs) => {
16830
17297
  const node = attrs;
16831
- if (node.type !== import_types63.NodeType.RouteNode) return;
17298
+ if (node.type !== import_types66.NodeType.RouteNode) return;
16832
17299
  const route = attrs;
16833
17300
  if (route.service !== serviceName) return;
16834
17301
  entries.push({
@@ -16860,7 +17327,7 @@ function createFirebaseResolveTarget(graph, serviceMap) {
16860
17327
  return {
16861
17328
  targetNodeId: match.routeNodeId,
16862
17329
  serviceName,
16863
- edgeType: import_types63.EdgeType.CALLS
17330
+ edgeType: import_types66.EdgeType.CALLS
16864
17331
  };
16865
17332
  };
16866
17333
  }
@@ -16887,7 +17354,7 @@ init_cjs_shims();
16887
17354
 
16888
17355
  // src/connectors/cloudflare/connector.ts
16889
17356
  init_cjs_shims();
16890
- var import_types65 = require("@neat.is/types");
17357
+ var import_types68 = require("@neat.is/types");
16891
17358
 
16892
17359
  // src/connectors/cloudflare/client.ts
16893
17360
  init_cjs_shims();
@@ -17003,7 +17470,7 @@ function mapEventToSignal(event) {
17003
17470
  if (Number.isNaN(observedAt.getTime())) return null;
17004
17471
  const statusCode = metadata?.statusCode;
17005
17472
  const isError = typeof statusCode === "number" && statusCode >= ERROR_STATUS_THRESHOLD3;
17006
- const path71 = metadata?.trigger ? parsePathFromTrigger(metadata.trigger) : void 0;
17473
+ const path74 = metadata?.trigger ? parsePathFromTrigger(metadata.trigger) : void 0;
17007
17474
  return {
17008
17475
  targetKind: CLOUDFLARE_TARGET_KIND,
17009
17476
  targetName: scriptName,
@@ -17011,7 +17478,7 @@ function mapEventToSignal(event) {
17011
17478
  errorCount: isError ? 1 : 0,
17012
17479
  lastObservedIso: observedAt.toISOString(),
17013
17480
  method,
17014
- ...path71 ? { path: path71 } : {},
17481
+ ...path74 ? { path: path74 } : {},
17015
17482
  ...typeof statusCode === "number" ? { statusCode } : {},
17016
17483
  ...typeof metadata?.duration === "number" ? { duration: metadata.duration } : {}
17017
17484
  };
@@ -17051,19 +17518,19 @@ function findTaggedWorkerFileNode(graph, workerName) {
17051
17518
  graph.forEachNode((id, attrs) => {
17052
17519
  if (found) return;
17053
17520
  const a = attrs;
17054
- if (a.type === import_types65.NodeType.FileNode && a.platform === "cloudflare" && a.platformName === workerName) {
17521
+ if (a.type === import_types68.NodeType.FileNode && a.platform === "cloudflare" && a.platformName === workerName) {
17055
17522
  found = id;
17056
17523
  }
17057
17524
  });
17058
17525
  return found;
17059
17526
  }
17060
- function findMatchingRouteNode(graph, serviceName, method, path71) {
17061
- const normalizedPath = normalizePathTemplate(path71);
17527
+ function findMatchingRouteNode(graph, serviceName, method, path74) {
17528
+ const normalizedPath = normalizePathTemplate(path74);
17062
17529
  let found = null;
17063
17530
  graph.forEachNode((id, attrs) => {
17064
17531
  if (found) return;
17065
17532
  const a = attrs;
17066
- if (a.type !== import_types65.NodeType.RouteNode || a.service !== serviceName) return;
17533
+ if (a.type !== import_types68.NodeType.RouteNode || a.service !== serviceName) return;
17067
17534
  if (!a.pathTemplate || normalizePathTemplate(a.pathTemplate) !== normalizedPath) return;
17068
17535
  const routeMethod = (a.method ?? "").toUpperCase();
17069
17536
  if (routeMethod !== "ALL" && routeMethod !== method) return;
@@ -17075,18 +17542,18 @@ function createCloudflareResolveTarget(config, graph) {
17075
17542
  return (signal) => {
17076
17543
  if (signal.targetKind !== CLOUDFLARE_TARGET_KIND) return null;
17077
17544
  const scriptName = signal.targetName;
17078
- const { method, path: path71 } = signal;
17545
+ const { method, path: path74 } = signal;
17079
17546
  const resolveRouteGrain = (serviceName, wholeFileId) => {
17080
- if (!method || !path71) return wholeFileId;
17081
- return findMatchingRouteNode(graph, serviceName, method, path71) ?? wholeFileId;
17547
+ if (!method || !path74) return wholeFileId;
17548
+ return findMatchingRouteNode(graph, serviceName, method, path74) ?? wholeFileId;
17082
17549
  };
17083
17550
  const mapping = config.workers?.[scriptName];
17084
17551
  if (mapping) {
17085
- const wholeFileId = (0, import_types65.fileId)(mapping.service, mapping.entryFile);
17552
+ const wholeFileId = (0, import_types68.fileId)(mapping.service, mapping.entryFile);
17086
17553
  return {
17087
17554
  targetNodeId: resolveRouteGrain(mapping.service, wholeFileId),
17088
17555
  serviceName: mapping.service,
17089
- edgeType: import_types65.EdgeType.CALLS
17556
+ edgeType: import_types68.EdgeType.CALLS
17090
17557
  };
17091
17558
  }
17092
17559
  const taggedFileId = findTaggedWorkerFileNode(graph, scriptName);
@@ -17095,13 +17562,13 @@ function createCloudflareResolveTarget(config, graph) {
17095
17562
  return {
17096
17563
  targetNodeId: resolveRouteGrain(fileNode.service, taggedFileId),
17097
17564
  serviceName: fileNode.service,
17098
- edgeType: import_types65.EdgeType.CALLS
17565
+ edgeType: import_types68.EdgeType.CALLS
17099
17566
  };
17100
17567
  }
17101
17568
  return {
17102
- targetNodeId: (0, import_types65.infraId)("cloudflare-worker", scriptName),
17569
+ targetNodeId: (0, import_types68.infraId)("cloudflare-worker", scriptName),
17103
17570
  serviceName: scriptName,
17104
- edgeType: import_types65.EdgeType.CALLS,
17571
+ edgeType: import_types68.EdgeType.CALLS,
17105
17572
  ensureInfraNode: { kind: "cloudflare-worker", name: scriptName, provider: "cloudflare" }
17106
17573
  };
17107
17574
  };
@@ -17297,14 +17764,14 @@ function diffNeonStatementsToSignals(rows, previous, observedAtIso) {
17297
17764
 
17298
17765
  // src/connectors/neon/resolve.ts
17299
17766
  init_cjs_shims();
17300
- var import_types69 = require("@neat.is/types");
17767
+ var import_types72 = require("@neat.is/types");
17301
17768
  function createNeonResolveTarget(config) {
17302
17769
  return (signal) => {
17303
17770
  if (signal.targetKind !== NEON_SQL_TABLE_TARGET_KIND || !signal.targetName) return null;
17304
17771
  return {
17305
- targetNodeId: (0, import_types69.infraId)("sql-table", signal.targetName),
17772
+ targetNodeId: (0, import_types72.infraId)("sql-table", signal.targetName),
17306
17773
  serviceName: config.serviceName,
17307
- edgeType: import_types69.EdgeType.CALLS,
17774
+ edgeType: import_types72.EdgeType.CALLS,
17308
17775
  ensureInfraNode: { kind: "sql-table", name: signal.targetName, provider: "neon" }
17309
17776
  };
17310
17777
  };
@@ -17430,9 +17897,9 @@ function parseCloudRunTargetName(targetName) {
17430
17897
  const secondSep = rest.indexOf(FIELD_SEP2);
17431
17898
  if (secondSep === -1) return null;
17432
17899
  const method = rest.slice(0, secondSep);
17433
- const path71 = rest.slice(secondSep + 1);
17434
- if (!serviceName || !method || !path71) return null;
17435
- return { serviceName, method, path: path71 };
17900
+ const path74 = rest.slice(secondSep + 1);
17901
+ if (!serviceName || !method || !path74) return null;
17902
+ return { serviceName, method, path: path74 };
17436
17903
  }
17437
17904
 
17438
17905
  // src/connectors/cloud-run/map.ts
@@ -17461,14 +17928,14 @@ function mapLogEntryToSignal2(entry) {
17461
17928
  if (!req) return null;
17462
17929
  if (typeof req.requestMethod !== "string" || req.requestMethod.length === 0) return null;
17463
17930
  const method = req.requestMethod.toUpperCase();
17464
- const path71 = pathFromRequestUrl2(req.requestUrl);
17465
- if (path71 === null) return null;
17931
+ const path74 = pathFromRequestUrl2(req.requestUrl);
17932
+ if (path74 === null) return null;
17466
17933
  const timestamp = entry.timestamp;
17467
17934
  if (typeof timestamp !== "string" || timestamp.length === 0) return null;
17468
17935
  const isError = typeof req.status === "number" && req.status >= ERROR_STATUS_THRESHOLD4;
17469
17936
  return {
17470
17937
  targetKind: CLOUD_RUN_TARGET_KIND,
17471
- targetName: packCloudRunTargetName({ serviceName, method, path: path71 }),
17938
+ targetName: packCloudRunTargetName({ serviceName, method, path: path74 }),
17472
17939
  callCount: 1,
17473
17940
  errorCount: isError ? 1 : 0,
17474
17941
  lastObservedIso: timestamp
@@ -17485,14 +17952,14 @@ function mapLogEntriesToSignals2(entries) {
17485
17952
 
17486
17953
  // src/connectors/cloud-run/resolve.ts
17487
17954
  init_cjs_shims();
17488
- var import_types73 = require("@neat.is/types");
17955
+ var import_types76 = require("@neat.is/types");
17489
17956
  var CLOUD_RUN_SERVICE_INFRA_KIND = "cloud-run-service";
17490
17957
  function findMatchingRouteNode2(graph, serviceName, method, normalizedPath) {
17491
17958
  let found = null;
17492
17959
  graph.forEachNode((_id, attrs) => {
17493
17960
  if (found) return;
17494
17961
  const node = attrs;
17495
- if (node.type !== import_types73.NodeType.RouteNode) return;
17962
+ if (node.type !== import_types76.NodeType.RouteNode) return;
17496
17963
  const route = attrs;
17497
17964
  if (route.service !== serviceName || !route.pathTemplate) return;
17498
17965
  if (normalizePathTemplate(route.pathTemplate) !== normalizedPath) return;
@@ -17507,23 +17974,23 @@ function createCloudRunResolveTarget(graph, config) {
17507
17974
  if (signal.targetKind !== CLOUD_RUN_TARGET_KIND) return null;
17508
17975
  const identity = parseCloudRunTargetName(signal.targetName);
17509
17976
  if (!identity) return null;
17510
- const { serviceName: gcpServiceName, method, path: path71 } = identity;
17977
+ const { serviceName: gcpServiceName, method, path: path74 } = identity;
17511
17978
  const mappedService = config.serviceMap?.[gcpServiceName];
17512
17979
  if (mappedService) {
17513
17980
  const routeNodeId = findMatchingRouteNode2(
17514
17981
  graph,
17515
17982
  mappedService,
17516
17983
  method,
17517
- normalizePathTemplate(path71)
17984
+ normalizePathTemplate(path74)
17518
17985
  );
17519
17986
  if (routeNodeId) {
17520
- return { targetNodeId: routeNodeId, serviceName: mappedService, edgeType: import_types73.EdgeType.CALLS };
17987
+ return { targetNodeId: routeNodeId, serviceName: mappedService, edgeType: import_types76.EdgeType.CALLS };
17521
17988
  }
17522
17989
  }
17523
17990
  return {
17524
- targetNodeId: (0, import_types73.infraId)(CLOUD_RUN_SERVICE_INFRA_KIND, gcpServiceName),
17991
+ targetNodeId: (0, import_types76.infraId)(CLOUD_RUN_SERVICE_INFRA_KIND, gcpServiceName),
17525
17992
  serviceName: mappedService ?? gcpServiceName,
17526
- edgeType: import_types73.EdgeType.CALLS,
17993
+ edgeType: import_types76.EdgeType.CALLS,
17527
17994
  ensureInfraNode: {
17528
17995
  kind: CLOUD_RUN_SERVICE_INFRA_KIND,
17529
17996
  name: gcpServiceName,
@@ -17564,7 +18031,7 @@ function createCloudRunConnector(graph, config = {}) {
17564
18031
 
17565
18032
  // src/connectors/render/index.ts
17566
18033
  init_cjs_shims();
17567
- var import_types76 = require("@neat.is/types");
18034
+ var import_types79 = require("@neat.is/types");
17568
18035
 
17569
18036
  // src/connectors/render/types.ts
17570
18037
  init_cjs_shims();
@@ -17642,7 +18109,7 @@ function buildRenderRouteIndex(graph, serviceName) {
17642
18109
  const out = [];
17643
18110
  graph.forEachNode((_id, attrs) => {
17644
18111
  const node = attrs;
17645
- if (node.type !== import_types76.NodeType.RouteNode) return;
18112
+ if (node.type !== import_types79.NodeType.RouteNode) return;
17646
18113
  const route = attrs;
17647
18114
  if (route.service !== serviceName) return;
17648
18115
  out.push({
@@ -17727,7 +18194,7 @@ function mapRenderRequestLogsToSignals(entries, routeIndex) {
17727
18194
  function createRenderResolveTarget(config) {
17728
18195
  return (signal) => {
17729
18196
  if (signal.targetKind === ROUTE_TARGET_KIND2) {
17730
- return { targetNodeId: signal.targetName, serviceName: config.serviceName, edgeType: import_types76.EdgeType.CALLS };
18197
+ return { targetNodeId: signal.targetName, serviceName: config.serviceName, edgeType: import_types79.EdgeType.CALLS };
17731
18198
  }
17732
18199
  return null;
17733
18200
  };
@@ -17865,21 +18332,21 @@ function mapInsightsToSignals(rows, observedAtIso) {
17865
18332
 
17866
18333
  // src/connectors/planetscale/resolve.ts
17867
18334
  init_cjs_shims();
17868
- var import_types80 = require("@neat.is/types");
18335
+ var import_types83 = require("@neat.is/types");
17869
18336
  var PLANETSCALE_DATABASE_KIND = "planetscale-database";
17870
18337
  function createPlanetscaleResolveTarget(graph, config) {
17871
18338
  const databaseName = `${config.organization}/${config.database}`;
17872
18339
  return (signal, _ctx) => {
17873
18340
  if (signal.targetKind !== PLANETSCALE_SQL_TABLE_TARGET_KIND || !signal.targetName) return null;
17874
- const tableId = (0, import_types80.infraId)("sql-table", signal.targetName);
18341
+ const tableId = (0, import_types83.infraId)("sql-table", signal.targetName);
17875
18342
  if (graph.hasNode(tableId)) {
17876
- return { targetNodeId: tableId, serviceName: config.serviceName, edgeType: import_types80.EdgeType.CALLS };
18343
+ return { targetNodeId: tableId, serviceName: config.serviceName, edgeType: import_types83.EdgeType.CALLS };
17877
18344
  }
17878
- const providerId = (0, import_types80.infraId)(PLANETSCALE_DATABASE_KIND, databaseName);
18345
+ const providerId = (0, import_types83.infraId)(PLANETSCALE_DATABASE_KIND, databaseName);
17879
18346
  return {
17880
18347
  targetNodeId: providerId,
17881
18348
  serviceName: config.serviceName,
17882
- edgeType: import_types80.EdgeType.CALLS,
18349
+ edgeType: import_types83.EdgeType.CALLS,
17883
18350
  ensureInfraNode: { kind: PLANETSCALE_DATABASE_KIND, name: databaseName, provider: "planetscale" }
17884
18351
  };
17885
18352
  };
@@ -18144,7 +18611,7 @@ function mapBuildsToSignals(builds, serviceName) {
18144
18611
 
18145
18612
  // src/connectors/eas/resolve.ts
18146
18613
  init_cjs_shims();
18147
- var import_types85 = require("@neat.is/types");
18614
+ var import_types88 = require("@neat.is/types");
18148
18615
  var NO_ENV2 = "unknown";
18149
18616
  var EAS_JSON_PHASES = /* @__PURE__ */ new Set(["READ_EAS_JSON"]);
18150
18617
  var APP_CONFIG_PHASES = /* @__PURE__ */ new Set([
@@ -18160,8 +18627,8 @@ function configBasenamesForPhase(phase) {
18160
18627
  function configNodeService(graph, configNodeId) {
18161
18628
  for (const edgeId of graph.inboundEdges(configNodeId)) {
18162
18629
  const edge = graph.getEdgeAttributes(edgeId);
18163
- if (edge.type !== import_types85.EdgeType.CONFIGURED_BY) continue;
18164
- const parsed = (0, import_types85.parseFileId)(edge.source);
18630
+ if (edge.type !== import_types88.EdgeType.CONFIGURED_BY) continue;
18631
+ const parsed = (0, import_types88.parseFileId)(edge.source);
18165
18632
  if (parsed) return parsed.service;
18166
18633
  }
18167
18634
  return null;
@@ -18172,7 +18639,7 @@ function findConfigNode(graph, basenames, serviceName) {
18172
18639
  graph.forEachNode((id, attrs) => {
18173
18640
  if (scoped) return;
18174
18641
  const node = attrs;
18175
- if (node.type !== import_types85.NodeType.ConfigNode) return;
18642
+ if (node.type !== import_types88.NodeType.ConfigNode) return;
18176
18643
  if (typeof node.name !== "string" || !basenames.includes(node.name)) return;
18177
18644
  if (anyMatch === null) anyMatch = id;
18178
18645
  if (configNodeService(graph, id) === serviceName) scoped = id;
@@ -18189,13 +18656,13 @@ function createEasResolveTarget(graph) {
18189
18656
  if (basenames.length > 0) {
18190
18657
  const configNodeId = findConfigNode(graph, basenames, serviceName);
18191
18658
  if (configNodeId) {
18192
- return { targetNodeId: configNodeId, serviceName, edgeType: import_types85.EdgeType.CALLS };
18659
+ return { targetNodeId: configNodeId, serviceName, edgeType: import_types88.EdgeType.CALLS };
18193
18660
  }
18194
18661
  }
18195
18662
  return {
18196
18663
  targetNodeId: resolveFusedServiceId(graph, serviceName, NO_ENV2),
18197
18664
  serviceName,
18198
- edgeType: import_types85.EdgeType.CALLS
18665
+ edgeType: import_types88.EdgeType.CALLS
18199
18666
  };
18200
18667
  };
18201
18668
  }
@@ -18874,11 +19341,11 @@ function registerRoutes(scope, ctx) {
18874
19341
  const candidates = req.query.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
18875
19342
  const parsed = [];
18876
19343
  for (const c of candidates) {
18877
- const r = import_types88.DivergenceTypeSchema.safeParse(c);
19344
+ const r = import_types91.DivergenceTypeSchema.safeParse(c);
18878
19345
  if (!r.success) {
18879
19346
  return reply.code(400).send({
18880
19347
  error: `unknown divergence type "${c}"`,
18881
- allowed: import_types88.DivergenceTypeSchema.options
19348
+ allowed: import_types91.DivergenceTypeSchema.options
18882
19349
  });
18883
19350
  }
18884
19351
  parsed.push(r.data);
@@ -19220,7 +19687,7 @@ function registerRoutes(scope, ctx) {
19220
19687
  const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
19221
19688
  let violations = await log.readAll();
19222
19689
  if (req.query.severity) {
19223
- const sev = import_types88.PolicySeveritySchema.safeParse(req.query.severity);
19690
+ const sev = import_types91.PolicySeveritySchema.safeParse(req.query.severity);
19224
19691
  if (!sev.success) {
19225
19692
  return reply.code(400).send({
19226
19693
  error: "invalid severity",
@@ -19259,7 +19726,7 @@ function registerRoutes(scope, ctx) {
19259
19726
  scope.post("/policies/check", async (req, reply) => {
19260
19727
  const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
19261
19728
  if (!proj) return;
19262
- const parsed = import_types88.PoliciesCheckBodySchema.safeParse(req.body ?? {});
19729
+ const parsed = import_types91.PoliciesCheckBodySchema.safeParse(req.body ?? {});
19263
19730
  if (!parsed.success) {
19264
19731
  return reply.code(400).send({
19265
19732
  error: "invalid /policies/check body",
@@ -19580,8 +20047,8 @@ init_otel_grpc();
19580
20047
 
19581
20048
  // src/search.ts
19582
20049
  init_cjs_shims();
19583
- var import_node_fs35 = require("fs");
19584
- var import_node_path69 = __toESM(require("path"), 1);
20050
+ var import_node_fs38 = require("fs");
20051
+ var import_node_path72 = __toESM(require("path"), 1);
19585
20052
  var import_node_crypto4 = require("crypto");
19586
20053
  var DEFAULT_LIMIT = 10;
19587
20054
  var NOMIC_DIM = 768;
@@ -19735,7 +20202,7 @@ async function pickEmbedder() {
19735
20202
  }
19736
20203
  async function readCache(cachePath) {
19737
20204
  try {
19738
- const raw = await import_node_fs35.promises.readFile(cachePath, "utf8");
20205
+ const raw = await import_node_fs38.promises.readFile(cachePath, "utf8");
19739
20206
  const parsed = JSON.parse(raw);
19740
20207
  if (parsed.version !== 1) return null;
19741
20208
  return parsed;
@@ -19744,8 +20211,8 @@ async function readCache(cachePath) {
19744
20211
  }
19745
20212
  }
19746
20213
  async function writeCache(cachePath, cache) {
19747
- await import_node_fs35.promises.mkdir(import_node_path69.default.dirname(cachePath), { recursive: true });
19748
- await import_node_fs35.promises.writeFile(cachePath, JSON.stringify(cache));
20214
+ await import_node_fs38.promises.mkdir(import_node_path72.default.dirname(cachePath), { recursive: true });
20215
+ await import_node_fs38.promises.writeFile(cachePath, JSON.stringify(cache));
19749
20216
  }
19750
20217
  var VectorIndex = class {
19751
20218
  constructor(embedder, cachePath) {
@@ -19959,14 +20426,14 @@ async function bootProject(registry, name, scanPath, baseDir) {
19959
20426
  async function main() {
19960
20427
  const baseDirEnv = process.env.NEAT_OUT_DIR;
19961
20428
  const legacyOutPath = process.env.NEAT_OUT_PATH;
19962
- const baseDir = baseDirEnv ? import_node_path70.default.resolve(baseDirEnv) : legacyOutPath ? import_node_path70.default.resolve(import_node_path70.default.dirname(legacyOutPath)) : import_node_path70.default.resolve("./neat-out");
19963
- const defaultScanPath = import_node_path70.default.resolve(process.env.NEAT_SCAN_PATH ?? "./demo");
20429
+ const baseDir = baseDirEnv ? import_node_path73.default.resolve(baseDirEnv) : legacyOutPath ? import_node_path73.default.resolve(import_node_path73.default.dirname(legacyOutPath)) : import_node_path73.default.resolve("./neat-out");
20430
+ const defaultScanPath = import_node_path73.default.resolve(process.env.NEAT_SCAN_PATH ?? "./demo");
19964
20431
  const registry = new Projects();
19965
20432
  await bootProject(registry, DEFAULT_PROJECT, defaultScanPath, baseDir);
19966
20433
  for (const name of parseExtraProjects(process.env.NEAT_PROJECTS)) {
19967
20434
  const envKey = `NEAT_PROJECT_SCAN_PATH_${name.toUpperCase().replace(/[^A-Z0-9]/g, "_")}`;
19968
20435
  const projectScan = process.env[envKey];
19969
- await bootProject(registry, name, projectScan ? import_node_path70.default.resolve(projectScan) : void 0, baseDir);
20436
+ await bootProject(registry, name, projectScan ? import_node_path73.default.resolve(projectScan) : void 0, baseDir);
19970
20437
  }
19971
20438
  const host = process.env.HOST ?? "0.0.0.0";
19972
20439
  const port = Number(process.env.PORT ?? 8080);