@neat.is/core 0.4.26-dev.20260703 → 0.4.26

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
@@ -56,9 +56,9 @@ function mountBearerAuth(app, opts) {
56
56
  const suffixes = [...DEFAULT_UNAUTH_SUFFIXES, ...opts.extraUnauthenticatedSuffixes ?? []];
57
57
  const publicRead = opts.publicRead === true;
58
58
  app.addHook("preHandler", (req, reply, done) => {
59
- const path45 = (req.url.split("?")[0] ?? "").replace(/\/+$/, "");
59
+ const path46 = (req.url.split("?")[0] ?? "").replace(/\/+$/, "");
60
60
  for (const suffix of suffixes) {
61
- if (path45 === suffix || path45.endsWith(suffix)) {
61
+ if (path46 === suffix || path46.endsWith(suffix)) {
62
62
  done();
63
63
  return;
64
64
  }
@@ -188,8 +188,8 @@ function reshapeGrpcRequest(req) {
188
188
  };
189
189
  }
190
190
  function resolveProtoRoot() {
191
- const here = import_node_path41.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
192
- return import_node_path41.default.resolve(here, "..", "proto");
191
+ const here = import_node_path42.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
192
+ return import_node_path42.default.resolve(here, "..", "proto");
193
193
  }
194
194
  function loadTraceService() {
195
195
  const protoRoot = resolveProtoRoot();
@@ -257,13 +257,13 @@ async function startOtelGrpcReceiver(opts) {
257
257
  })
258
258
  };
259
259
  }
260
- var import_node_url, import_node_path41, import_node_crypto2, grpc, protoLoader;
260
+ var import_node_url, import_node_path42, import_node_crypto2, grpc, protoLoader;
261
261
  var init_otel_grpc = __esm({
262
262
  "src/otel-grpc.ts"() {
263
263
  "use strict";
264
264
  init_cjs_shims();
265
265
  import_node_url = require("url");
266
- import_node_path41 = __toESM(require("path"), 1);
266
+ import_node_path42 = __toESM(require("path"), 1);
267
267
  import_node_crypto2 = require("crypto");
268
268
  grpc = __toESM(require("@grpc/grpc-js"), 1);
269
269
  protoLoader = __toESM(require("@grpc/proto-loader"), 1);
@@ -343,6 +343,29 @@ function messagingDestinationOf(attrs) {
343
343
  }
344
344
  return void 0;
345
345
  }
346
+ function hasWebsocketUpgradeHeader(attrs) {
347
+ const v = attrs["http.request.header.upgrade"];
348
+ const matches = (s) => typeof s === "string" && s.trim().toLowerCase() === "websocket";
349
+ if (Array.isArray(v)) return v.some(matches);
350
+ return matches(v);
351
+ }
352
+ function websocketChannelPathOf(attrs) {
353
+ const route = attrs["http.route"];
354
+ if (typeof route === "string" && route.length > 0) return route;
355
+ for (const key of ["url.path", "http.target"]) {
356
+ const v = attrs[key];
357
+ if (typeof v === "string" && v.length > 0) {
358
+ const q = v.indexOf("?");
359
+ const path46 = q === -1 ? v : v.slice(0, q);
360
+ if (path46.length > 0) return path46;
361
+ }
362
+ }
363
+ return void 0;
364
+ }
365
+ function websocketChannelOf(attrs) {
366
+ if (!hasWebsocketUpgradeHeader(attrs)) return void 0;
367
+ return websocketChannelPathOf(attrs);
368
+ }
346
369
  function parseOtlpRequest(body) {
347
370
  const out = [];
348
371
  for (const rs of body.resourceSpans ?? []) {
@@ -373,6 +396,10 @@ function parseOtlpRequest(body) {
373
396
  messagingDestination: messagingDestinationOf(attrs),
374
397
  graphqlOperationName: typeof attrs["graphql.operation.name"] === "string" && attrs["graphql.operation.name"].length > 0 ? attrs["graphql.operation.name"] : void 0,
375
398
  graphqlOperationType: typeof attrs["graphql.operation.type"] === "string" && attrs["graphql.operation.type"].length > 0 ? attrs["graphql.operation.type"] : void 0,
399
+ rpcSystem: typeof attrs["rpc.system"] === "string" && attrs["rpc.system"].length > 0 ? attrs["rpc.system"] : void 0,
400
+ rpcService: typeof attrs["rpc.service"] === "string" && attrs["rpc.service"].length > 0 ? attrs["rpc.service"] : void 0,
401
+ rpcMethod: typeof attrs["rpc.method"] === "string" && attrs["rpc.method"].length > 0 ? attrs["rpc.method"] : void 0,
402
+ websocketChannel: websocketChannelOf(attrs),
376
403
  statusCode: span.status?.code,
377
404
  errorMessage: span.status?.message,
378
405
  exception: extractExceptionFromEvents(span.events)
@@ -384,10 +411,10 @@ function parseOtlpRequest(body) {
384
411
  return out;
385
412
  }
386
413
  function loadProtoRoot() {
387
- const here = import_node_path42.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
388
- const protoRoot = import_node_path42.default.resolve(here, "..", "proto");
414
+ const here = import_node_path43.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
415
+ const protoRoot = import_node_path43.default.resolve(here, "..", "proto");
389
416
  const root = new import_protobufjs.default.Root();
390
- root.resolvePath = (_origin, target) => import_node_path42.default.resolve(protoRoot, target);
417
+ root.resolvePath = (_origin, target) => import_node_path43.default.resolve(protoRoot, target);
391
418
  root.loadSync(
392
419
  "opentelemetry/proto/collector/trace/v1/trace_service.proto",
393
420
  { keepCase: true }
@@ -599,12 +626,12 @@ async function buildOtelReceiver(opts) {
599
626
  };
600
627
  return decorated;
601
628
  }
602
- var import_node_path42, import_node_url2, import_fastify2, import_protobufjs, ENV_ATTR_CANONICAL, ENV_ATTR_COMPAT, ENV_FALLBACK, exportTraceServiceRequestType, exportTraceServiceResponseType, cachedProtobufResponseBody;
629
+ var import_node_path43, import_node_url2, import_fastify2, import_protobufjs, ENV_ATTR_CANONICAL, ENV_ATTR_COMPAT, ENV_FALLBACK, exportTraceServiceRequestType, exportTraceServiceResponseType, cachedProtobufResponseBody;
603
630
  var init_otel = __esm({
604
631
  "src/otel.ts"() {
605
632
  "use strict";
606
633
  init_cjs_shims();
607
- import_node_path42 = __toESM(require("path"), 1);
634
+ import_node_path43 = __toESM(require("path"), 1);
608
635
  import_node_url2 = require("url");
609
636
  import_fastify2 = __toESM(require("fastify"), 1);
610
637
  import_protobufjs = __toESM(require("protobufjs"), 1);
@@ -620,7 +647,7 @@ var init_otel = __esm({
620
647
 
621
648
  // src/server.ts
622
649
  init_cjs_shims();
623
- var import_node_path44 = __toESM(require("path"), 1);
650
+ var import_node_path45 = __toESM(require("path"), 1);
624
651
 
625
652
  // src/graph.ts
626
653
  init_cjs_shims();
@@ -644,7 +671,7 @@ function getGraph(project = DEFAULT_PROJECT) {
644
671
  init_cjs_shims();
645
672
  var import_fastify = __toESM(require("fastify"), 1);
646
673
  var import_cors = __toESM(require("@fastify/cors"), 1);
647
- var import_types28 = require("@neat.is/types");
674
+ var import_types29 = require("@neat.is/types");
648
675
 
649
676
  // src/extend/index.ts
650
677
  init_cjs_shims();
@@ -1387,19 +1414,19 @@ function confidenceFromMix(edges, now = Date.now()) {
1387
1414
  function longestIncomingWalk(graph, start, maxDepth) {
1388
1415
  let best = { path: [start], edges: [] };
1389
1416
  const visited = /* @__PURE__ */ new Set([start]);
1390
- function step(node, path45, edges) {
1391
- if (path45.length > best.path.length) {
1392
- best = { path: [...path45], edges: [...edges] };
1417
+ function step(node, path46, edges) {
1418
+ if (path46.length > best.path.length) {
1419
+ best = { path: [...path46], edges: [...edges] };
1393
1420
  }
1394
- if (path45.length - 1 >= maxDepth) return;
1421
+ if (path46.length - 1 >= maxDepth) return;
1395
1422
  const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
1396
1423
  for (const [srcId, edge] of incoming) {
1397
1424
  if (visited.has(srcId)) continue;
1398
1425
  visited.add(srcId);
1399
- path45.push(srcId);
1426
+ path46.push(srcId);
1400
1427
  edges.push(edge);
1401
- step(srcId, path45, edges);
1402
- path45.pop();
1428
+ step(srcId, path46, edges);
1429
+ path46.pop();
1403
1430
  edges.pop();
1404
1431
  visited.delete(srcId);
1405
1432
  }
@@ -1593,26 +1620,26 @@ function dominantFailingCall(graph, serviceId4, visited) {
1593
1620
  return best;
1594
1621
  }
1595
1622
  function followFailingCallChain(graph, originServiceId, maxDepth) {
1596
- const path45 = [originServiceId];
1623
+ const path46 = [originServiceId];
1597
1624
  const edges = [];
1598
1625
  const visited = /* @__PURE__ */ new Set([originServiceId]);
1599
1626
  let current = originServiceId;
1600
1627
  for (let depth = 0; depth < maxDepth; depth++) {
1601
1628
  const hop = dominantFailingCall(graph, current, visited);
1602
1629
  if (!hop) break;
1603
- path45.push(hop.nextService);
1630
+ path46.push(hop.nextService);
1604
1631
  edges.push(hop.edge);
1605
1632
  visited.add(hop.nextService);
1606
1633
  current = hop.nextService;
1607
1634
  }
1608
1635
  if (edges.length === 0) return null;
1609
- return { path: path45, edges, culprit: current };
1636
+ return { path: path46, edges, culprit: current };
1610
1637
  }
1611
1638
  function crossServiceRootCause(graph, originId, incidents, errorEvent) {
1612
1639
  const chain = followFailingCallChain(graph, originId, ROOT_CAUSE_MAX_DEPTH);
1613
1640
  if (!chain) return null;
1614
1641
  const culprit = chain.culprit;
1615
- const path45 = [...chain.path];
1642
+ const path46 = [...chain.path];
1616
1643
  const edgeProvenances = chain.edges.map((e) => e.provenance);
1617
1644
  const baseConfidence = confidenceFromMix(chain.edges);
1618
1645
  const confidence = Math.max(0, Math.min(1, baseConfidence * INCIDENT_ROOT_CAUSE_CONFIDENCE));
@@ -1620,14 +1647,14 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
1620
1647
  if (loc) {
1621
1648
  let rootCauseNode = culprit;
1622
1649
  if (loc.fileNode) {
1623
- path45.push(loc.fileNode);
1650
+ path46.push(loc.fileNode);
1624
1651
  edgeProvenances.push(import_types.Provenance.OBSERVED);
1625
1652
  rootCauseNode = loc.fileNode;
1626
1653
  }
1627
1654
  return import_types.RootCauseResultSchema.parse({
1628
1655
  rootCauseNode,
1629
1656
  rootCauseReason: loc.rootCauseReason,
1630
- traversalPath: path45,
1657
+ traversalPath: path46,
1631
1658
  edgeProvenances,
1632
1659
  confidence,
1633
1660
  ...loc.fixRecommendation ? { fixRecommendation: loc.fixRecommendation } : {}
@@ -1639,7 +1666,7 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
1639
1666
  return import_types.RootCauseResultSchema.parse({
1640
1667
  rootCauseNode: culprit,
1641
1668
  rootCauseReason: `${culpritName} is failing downstream calls (${errs} observed error${errs === 1 ? "" : "s"})`,
1642
- traversalPath: path45,
1669
+ traversalPath: path46,
1643
1670
  edgeProvenances,
1644
1671
  confidence,
1645
1672
  fixRecommendation: `Inspect ${culpritName}'s failing handler`
@@ -1819,6 +1846,11 @@ function nodeIsFrontier(graph, nodeId) {
1819
1846
  const attrs = graph.getNodeAttributes(nodeId);
1820
1847
  return attrs.type === import_types2.NodeType.FrontierNode;
1821
1848
  }
1849
+ function nodeIsWebsocketChannel(graph, nodeId) {
1850
+ if (!graph.hasNode(nodeId)) return false;
1851
+ const attrs = graph.getNodeAttributes(nodeId);
1852
+ return attrs.type === import_types2.NodeType.WebSocketChannelNode;
1853
+ }
1822
1854
  function clampConfidence(n) {
1823
1855
  if (!Number.isFinite(n)) return 0;
1824
1856
  return Math.max(0, Math.min(1, n));
@@ -1859,7 +1891,7 @@ function detectMissingDivergences(graph, bucket) {
1859
1891
  });
1860
1892
  }
1861
1893
  }
1862
- if (bucket.observed && !bucket.extracted) {
1894
+ if (bucket.observed && !bucket.extracted && !nodeIsWebsocketChannel(graph, bucket.target)) {
1863
1895
  out.push({
1864
1896
  type: "missing-extracted",
1865
1897
  source: bucket.source,
@@ -2877,6 +2909,40 @@ function ensureGraphqlOperationNode(graph, serviceName, operationType, operation
2877
2909
  graph.addNode(id, node);
2878
2910
  return id;
2879
2911
  }
2912
+ function spanServesGrpcMethod(kind) {
2913
+ return kind !== WIRE_SPAN_KIND_CLIENT && kind !== WIRE_SPAN_KIND_PRODUCER && kind !== WIRE_SPAN_KIND_CONSUMER;
2914
+ }
2915
+ function ensureGrpcMethodNode(graph, rpcService, rpcMethod) {
2916
+ const id = (0, import_types4.grpcMethodId)(rpcService, rpcMethod);
2917
+ if (graph.hasNode(id)) return id;
2918
+ const node = {
2919
+ id,
2920
+ type: import_types4.NodeType.GrpcMethodNode,
2921
+ name: `${rpcService}/${rpcMethod}`,
2922
+ rpcService,
2923
+ rpcMethod,
2924
+ discoveredVia: "otel"
2925
+ };
2926
+ graph.addNode(id, node);
2927
+ return id;
2928
+ }
2929
+ function spanServesWebsocketChannel(kind) {
2930
+ return kind !== WIRE_SPAN_KIND_CLIENT && kind !== WIRE_SPAN_KIND_PRODUCER && kind !== WIRE_SPAN_KIND_CONSUMER;
2931
+ }
2932
+ function ensureWebsocketChannelNode(graph, serviceName, channel) {
2933
+ const id = (0, import_types4.websocketChannelId)(serviceName, channel);
2934
+ if (graph.hasNode(id)) return id;
2935
+ const node = {
2936
+ id,
2937
+ type: import_types4.NodeType.WebSocketChannelNode,
2938
+ name: channel,
2939
+ service: serviceName,
2940
+ channel,
2941
+ discoveredVia: "otel"
2942
+ };
2943
+ graph.addNode(id, node);
2944
+ return id;
2945
+ }
2880
2946
  function messagingDestinationKind(system) {
2881
2947
  return `${system}-topic`;
2882
2948
  }
@@ -3287,6 +3353,34 @@ async function handleSpan(ctx, span) {
3287
3353
  callSiteEvidence
3288
3354
  );
3289
3355
  if (result) affectedNode = targetId;
3356
+ } else if (span.rpcSystem === "grpc" && span.rpcService && span.rpcMethod && spanServesGrpcMethod(span.kind)) {
3357
+ const targetId = ensureGrpcMethodNode(ctx.graph, span.rpcService, span.rpcMethod);
3358
+ const result = upsertObservedEdge(
3359
+ ctx.graph,
3360
+ import_types4.EdgeType.CONTAINS,
3361
+ observedSource(),
3362
+ targetId,
3363
+ ts,
3364
+ isError,
3365
+ callSiteEvidence
3366
+ );
3367
+ if (result) affectedNode = targetId;
3368
+ } else if (span.websocketChannel && spanServesWebsocketChannel(span.kind)) {
3369
+ const targetId = ensureWebsocketChannelNode(
3370
+ ctx.graph,
3371
+ span.service,
3372
+ span.websocketChannel
3373
+ );
3374
+ const result = upsertObservedEdge(
3375
+ ctx.graph,
3376
+ import_types4.EdgeType.CONNECTS_TO,
3377
+ observedSource(),
3378
+ targetId,
3379
+ ts,
3380
+ isError,
3381
+ callSiteEvidence
3382
+ );
3383
+ if (result) affectedNode = targetId;
3290
3384
  } else {
3291
3385
  const host = pickAddress(span);
3292
3386
  let resolvedViaAddress = false;
@@ -5857,17 +5951,138 @@ async function addRoutes(graph, services) {
5857
5951
  return { nodesAdded, edgesAdded };
5858
5952
  }
5859
5953
 
5954
+ // src/extract/proto.ts
5955
+ init_cjs_shims();
5956
+ var import_node_fs17 = require("fs");
5957
+ var import_node_path25 = __toESM(require("path"), 1);
5958
+ var import_types13 = require("@neat.is/types");
5959
+ var PROTO_EXTENSION = ".proto";
5960
+ function packageOf(content) {
5961
+ const m = content.match(/^\s*package\s+([A-Za-z_][A-Za-z0-9_.]*)\s*;/m);
5962
+ return m ? m[1] : null;
5963
+ }
5964
+ function lineAt(content, offset) {
5965
+ return content.slice(0, offset).split("\n").length;
5966
+ }
5967
+ function grpcMethodsFromProto(content, fqPackage) {
5968
+ const out = [];
5969
+ const serviceRe = /\bservice\s+([A-Za-z_][A-Za-z0-9_]*)\s*\{/g;
5970
+ let sm;
5971
+ while ((sm = serviceRe.exec(content)) !== null) {
5972
+ const serviceName = sm[1];
5973
+ const rpcService = fqPackage ? `${fqPackage}.${serviceName}` : serviceName;
5974
+ const bodyStart = serviceRe.lastIndex;
5975
+ let depth = 1;
5976
+ let i = bodyStart;
5977
+ for (; i < content.length && depth > 0; i++) {
5978
+ const ch = content[i];
5979
+ if (ch === "{") depth++;
5980
+ else if (ch === "}") depth--;
5981
+ }
5982
+ const body = content.slice(bodyStart, i - 1);
5983
+ const rpcRe = /\brpc\s+([A-Za-z_][A-Za-z0-9_]*)\s*\(/g;
5984
+ let rm;
5985
+ while ((rm = rpcRe.exec(body)) !== null) {
5986
+ const rpcMethod = rm[1];
5987
+ const line = lineAt(content, bodyStart + rm.index);
5988
+ out.push({ rpcService, rpcMethod, line });
5989
+ }
5990
+ serviceRe.lastIndex = i;
5991
+ }
5992
+ return out;
5993
+ }
5994
+ async function walkProtoFiles(dir) {
5995
+ const out = [];
5996
+ async function walk3(current) {
5997
+ const entries = await import_node_fs17.promises.readdir(current, { withFileTypes: true }).catch(() => []);
5998
+ for (const entry of entries) {
5999
+ const full = import_node_path25.default.join(current, entry.name);
6000
+ if (entry.isDirectory()) {
6001
+ if (IGNORED_DIRS.has(entry.name)) continue;
6002
+ if (await isPythonVenvDir(full)) continue;
6003
+ await walk3(full);
6004
+ } else if (entry.isFile() && import_node_path25.default.extname(entry.name) === PROTO_EXTENSION) {
6005
+ out.push(full);
6006
+ }
6007
+ }
6008
+ }
6009
+ await walk3(dir);
6010
+ return out;
6011
+ }
6012
+ async function addGrpcMethods(graph, services) {
6013
+ let nodesAdded = 0;
6014
+ let edgesAdded = 0;
6015
+ for (const service of services) {
6016
+ const protoPaths = await walkProtoFiles(service.dir);
6017
+ for (const protoPath of protoPaths) {
6018
+ if (isTestPath(protoPath)) continue;
6019
+ const relFile = toPosix2(import_node_path25.default.relative(service.dir, protoPath));
6020
+ let content;
6021
+ try {
6022
+ content = await import_node_fs17.promises.readFile(protoPath, "utf8");
6023
+ } catch (err) {
6024
+ recordExtractionError("proto extraction", protoPath, err);
6025
+ continue;
6026
+ }
6027
+ let methods;
6028
+ try {
6029
+ methods = grpcMethodsFromProto(content, packageOf(content));
6030
+ } catch (err) {
6031
+ recordExtractionError("proto extraction", protoPath, err);
6032
+ continue;
6033
+ }
6034
+ if (methods.length === 0) continue;
6035
+ for (const method of methods) {
6036
+ const mid = (0, import_types13.grpcMethodId)(method.rpcService, method.rpcMethod);
6037
+ if (!graph.hasNode(mid)) {
6038
+ const node = {
6039
+ id: mid,
6040
+ type: import_types13.NodeType.GrpcMethodNode,
6041
+ name: `${method.rpcService}/${method.rpcMethod}`,
6042
+ rpcService: method.rpcService,
6043
+ rpcMethod: method.rpcMethod,
6044
+ path: relFile,
6045
+ line: method.line,
6046
+ discoveredVia: "static"
6047
+ };
6048
+ graph.addNode(mid, node);
6049
+ nodesAdded++;
6050
+ }
6051
+ const containsId = (0, import_types13.extractedEdgeId)(service.node.id, mid, import_types13.EdgeType.CONTAINS);
6052
+ if (!graph.hasEdge(containsId)) {
6053
+ const edge = {
6054
+ id: containsId,
6055
+ source: service.node.id,
6056
+ target: mid,
6057
+ type: import_types13.EdgeType.CONTAINS,
6058
+ provenance: import_types13.Provenance.EXTRACTED,
6059
+ confidence: (0, import_types13.confidenceForExtracted)("structural"),
6060
+ evidence: {
6061
+ file: relFile,
6062
+ line: method.line,
6063
+ snippet: snippet(content, method.line)
6064
+ }
6065
+ };
6066
+ graph.addEdgeWithKey(containsId, service.node.id, mid, edge);
6067
+ edgesAdded++;
6068
+ }
6069
+ }
6070
+ }
6071
+ }
6072
+ return { nodesAdded, edgesAdded };
6073
+ }
6074
+
5860
6075
  // src/extract/calls/index.ts
5861
6076
  init_cjs_shims();
5862
- var import_types20 = require("@neat.is/types");
6077
+ var import_types21 = require("@neat.is/types");
5863
6078
 
5864
6079
  // src/extract/calls/http.ts
5865
6080
  init_cjs_shims();
5866
- var import_node_path25 = __toESM(require("path"), 1);
6081
+ var import_node_path26 = __toESM(require("path"), 1);
5867
6082
  var import_tree_sitter3 = __toESM(require("tree-sitter"), 1);
5868
6083
  var import_tree_sitter_javascript3 = __toESM(require("tree-sitter-javascript"), 1);
5869
6084
  var import_tree_sitter_python2 = __toESM(require("tree-sitter-python"), 1);
5870
- var import_types13 = require("@neat.is/types");
6085
+ var import_types14 = require("@neat.is/types");
5871
6086
  var STRING_LITERAL_NODE_TYPES = /* @__PURE__ */ new Set(["string_fragment", "string_content"]);
5872
6087
  var JSX_EXTERNAL_LINK_TAGS = /* @__PURE__ */ new Set(["a", "Link", "NavLink", "ExternalLink", "Anchor"]);
5873
6088
  function isInsideJsxExternalLink(node) {
@@ -5937,7 +6152,7 @@ async function addHttpCallEdges(graph, services) {
5937
6152
  const seen = /* @__PURE__ */ new Set();
5938
6153
  for (const file of files) {
5939
6154
  if (isTestPath(file.path)) continue;
5940
- const parser = import_node_path25.default.extname(file.path) === ".py" ? pyParser : jsParser;
6155
+ const parser = import_node_path26.default.extname(file.path) === ".py" ? pyParser : jsParser;
5941
6156
  let sites;
5942
6157
  try {
5943
6158
  sites = callsFromSource(file.content, parser, knownHosts);
@@ -5946,14 +6161,14 @@ async function addHttpCallEdges(graph, services) {
5946
6161
  continue;
5947
6162
  }
5948
6163
  if (sites.length === 0) continue;
5949
- const relFile = toPosix2(import_node_path25.default.relative(service.dir, file.path));
6164
+ const relFile = toPosix2(import_node_path26.default.relative(service.dir, file.path));
5950
6165
  for (const site of sites) {
5951
6166
  const targetId = hostToNodeId.get(site.host);
5952
6167
  if (!targetId || targetId === service.node.id) continue;
5953
6168
  const dedupKey = `${relFile}|${targetId}`;
5954
6169
  if (seen.has(dedupKey)) continue;
5955
6170
  seen.add(dedupKey);
5956
- const confidence = (0, import_types13.confidenceForExtracted)("url-literal-service-target");
6171
+ const confidence = (0, import_types14.confidenceForExtracted)("url-literal-service-target");
5957
6172
  const ev = {
5958
6173
  file: relFile,
5959
6174
  line: site.line,
@@ -5967,25 +6182,25 @@ async function addHttpCallEdges(graph, services) {
5967
6182
  );
5968
6183
  nodesAdded += n;
5969
6184
  edgesAdded += e;
5970
- if (!(0, import_types13.passesExtractedFloor)(confidence)) {
6185
+ if (!(0, import_types14.passesExtractedFloor)(confidence)) {
5971
6186
  noteExtractedDropped({
5972
6187
  source: fileNodeId,
5973
6188
  target: targetId,
5974
- type: import_types13.EdgeType.CALLS,
6189
+ type: import_types14.EdgeType.CALLS,
5975
6190
  confidence,
5976
6191
  confidenceKind: "url-literal-service-target",
5977
6192
  evidence: ev
5978
6193
  });
5979
6194
  continue;
5980
6195
  }
5981
- const edgeId = (0, import_types5.extractedEdgeId)(fileNodeId, targetId, import_types13.EdgeType.CALLS);
6196
+ const edgeId = (0, import_types5.extractedEdgeId)(fileNodeId, targetId, import_types14.EdgeType.CALLS);
5982
6197
  if (!graph.hasEdge(edgeId)) {
5983
6198
  const edge = {
5984
6199
  id: edgeId,
5985
6200
  source: fileNodeId,
5986
6201
  target: targetId,
5987
- type: import_types13.EdgeType.CALLS,
5988
- provenance: import_types13.Provenance.EXTRACTED,
6202
+ type: import_types14.EdgeType.CALLS,
6203
+ provenance: import_types14.Provenance.EXTRACTED,
5989
6204
  confidence,
5990
6205
  evidence: ev
5991
6206
  };
@@ -6000,10 +6215,10 @@ async function addHttpCallEdges(graph, services) {
6000
6215
 
6001
6216
  // src/extract/calls/route-match.ts
6002
6217
  init_cjs_shims();
6003
- var import_node_path26 = __toESM(require("path"), 1);
6218
+ var import_node_path27 = __toESM(require("path"), 1);
6004
6219
  var import_tree_sitter4 = __toESM(require("tree-sitter"), 1);
6005
6220
  var import_tree_sitter_javascript4 = __toESM(require("tree-sitter-javascript"), 1);
6006
- var import_types14 = require("@neat.is/types");
6221
+ var import_types15 = require("@neat.is/types");
6007
6222
  var PARSE_CHUNK4 = 16384;
6008
6223
  function parseSource4(parser, source) {
6009
6224
  return parser.parse(
@@ -6168,9 +6383,9 @@ function buildRouteIndex(graph) {
6168
6383
  const index = /* @__PURE__ */ new Map();
6169
6384
  graph.forEachNode((_id, attrs) => {
6170
6385
  const node = attrs;
6171
- if (node.type !== import_types14.NodeType.RouteNode) return;
6386
+ if (node.type !== import_types15.NodeType.RouteNode) return;
6172
6387
  const route = attrs;
6173
- const owner = (0, import_types14.serviceId)(route.service);
6388
+ const owner = (0, import_types15.serviceId)(route.service);
6174
6389
  const entry = {
6175
6390
  method: route.method.toUpperCase(),
6176
6391
  normalizedPath: normalizePathTemplate(route.pathTemplate),
@@ -6199,7 +6414,7 @@ async function addRouteCallEdges(graph, services) {
6199
6414
  const seen = /* @__PURE__ */ new Set();
6200
6415
  for (const file of files) {
6201
6416
  if (isTestPath(file.path)) continue;
6202
- if (!JS_CLIENT_EXTENSIONS.has(import_node_path26.default.extname(file.path))) continue;
6417
+ if (!JS_CLIENT_EXTENSIONS.has(import_node_path27.default.extname(file.path))) continue;
6203
6418
  let sites;
6204
6419
  try {
6205
6420
  sites = clientCallSitesFromSource(file.content, jsParser, knownHosts);
@@ -6208,7 +6423,7 @@ async function addRouteCallEdges(graph, services) {
6208
6423
  continue;
6209
6424
  }
6210
6425
  if (sites.length === 0) continue;
6211
- const relFile = toPosix2(import_node_path26.default.relative(service.dir, file.path));
6426
+ const relFile = toPosix2(import_node_path27.default.relative(service.dir, file.path));
6212
6427
  for (const site of sites) {
6213
6428
  const serverServiceId = hostToNodeId.get(site.host);
6214
6429
  if (!serverServiceId || serverServiceId === service.node.id) continue;
@@ -6228,7 +6443,7 @@ async function addRouteCallEdges(graph, services) {
6228
6443
  );
6229
6444
  nodesAdded += n;
6230
6445
  edgesAdded += e;
6231
- const confidence = (0, import_types14.confidenceForExtracted)("verified-call-site");
6446
+ const confidence = (0, import_types15.confidenceForExtracted)("verified-call-site");
6232
6447
  const ev = {
6233
6448
  file: relFile,
6234
6449
  line: site.line,
@@ -6236,25 +6451,25 @@ async function addRouteCallEdges(graph, services) {
6236
6451
  method: site.method ?? match.method,
6237
6452
  pathTemplate: site.pathTemplate
6238
6453
  };
6239
- if (!(0, import_types14.passesExtractedFloor)(confidence)) {
6454
+ if (!(0, import_types15.passesExtractedFloor)(confidence)) {
6240
6455
  noteExtractedDropped({
6241
6456
  source: fileNodeId,
6242
6457
  target: match.routeNodeId,
6243
- type: import_types14.EdgeType.CALLS,
6458
+ type: import_types15.EdgeType.CALLS,
6244
6459
  confidence,
6245
6460
  confidenceKind: "verified-call-site",
6246
6461
  evidence: ev
6247
6462
  });
6248
6463
  continue;
6249
6464
  }
6250
- const edgeId = (0, import_types5.extractedEdgeId)(fileNodeId, match.routeNodeId, import_types14.EdgeType.CALLS);
6465
+ const edgeId = (0, import_types5.extractedEdgeId)(fileNodeId, match.routeNodeId, import_types15.EdgeType.CALLS);
6251
6466
  if (!graph.hasEdge(edgeId)) {
6252
6467
  const edge = {
6253
6468
  id: edgeId,
6254
6469
  source: fileNodeId,
6255
6470
  target: match.routeNodeId,
6256
- type: import_types14.EdgeType.CALLS,
6257
- provenance: import_types14.Provenance.EXTRACTED,
6471
+ type: import_types15.EdgeType.CALLS,
6472
+ provenance: import_types15.Provenance.EXTRACTED,
6258
6473
  confidence,
6259
6474
  evidence: ev
6260
6475
  };
@@ -6269,8 +6484,8 @@ async function addRouteCallEdges(graph, services) {
6269
6484
 
6270
6485
  // src/extract/calls/kafka.ts
6271
6486
  init_cjs_shims();
6272
- var import_node_path27 = __toESM(require("path"), 1);
6273
- var import_types15 = require("@neat.is/types");
6487
+ var import_node_path28 = __toESM(require("path"), 1);
6488
+ var import_types16 = require("@neat.is/types");
6274
6489
  var PRODUCER_TOPIC_RE = /(?:producer|kafkaProducer)[\s\S]{0,40}?\.send\s*\(\s*\{[\s\S]{0,200}?topic\s*:\s*['"`]([^'"`]+)['"`]/g;
6275
6490
  var CONSUMER_TOPIC_RE = /(?:consumer|kafkaConsumer)[\s\S]{0,40}?\.(?:subscribe|run)\s*\(\s*\{[\s\S]{0,200}?topic[s]?\s*:\s*(?:\[\s*)?['"`]([^'"`]+)['"`]/g;
6276
6491
  function findAll(re, text) {
@@ -6291,7 +6506,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
6291
6506
  seen.add(key);
6292
6507
  const line = lineOf(file.content, topic);
6293
6508
  out.push({
6294
- infraId: (0, import_types15.infraId)("kafka-topic", topic),
6509
+ infraId: (0, import_types16.infraId)("kafka-topic", topic),
6295
6510
  name: topic,
6296
6511
  kind: "kafka-topic",
6297
6512
  edgeType,
@@ -6300,7 +6515,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
6300
6515
  // tier (ADR-066).
6301
6516
  confidenceKind: "verified-call-site",
6302
6517
  evidence: {
6303
- file: import_node_path27.default.relative(serviceDir, file.path),
6518
+ file: import_node_path28.default.relative(serviceDir, file.path),
6304
6519
  line,
6305
6520
  snippet: snippet(file.content, line)
6306
6521
  }
@@ -6313,8 +6528,8 @@ function kafkaEndpointsFromFile(file, serviceDir) {
6313
6528
 
6314
6529
  // src/extract/calls/redis.ts
6315
6530
  init_cjs_shims();
6316
- var import_node_path28 = __toESM(require("path"), 1);
6317
- var import_types16 = require("@neat.is/types");
6531
+ var import_node_path29 = __toESM(require("path"), 1);
6532
+ var import_types17 = require("@neat.is/types");
6318
6533
  var REDIS_URL_RE = /redis(?:s)?:\/\/(?:[^@'"`\s]+@)?([^:/'"`\s]+)(?::(\d+))?/g;
6319
6534
  function redisEndpointsFromFile(file, serviceDir) {
6320
6535
  const out = [];
@@ -6327,7 +6542,7 @@ function redisEndpointsFromFile(file, serviceDir) {
6327
6542
  seen.add(host);
6328
6543
  const line = lineOf(file.content, host);
6329
6544
  out.push({
6330
- infraId: (0, import_types16.infraId)("redis", host),
6545
+ infraId: (0, import_types17.infraId)("redis", host),
6331
6546
  name: host,
6332
6547
  kind: "redis",
6333
6548
  edgeType: "CALLS",
@@ -6336,7 +6551,7 @@ function redisEndpointsFromFile(file, serviceDir) {
6336
6551
  // support tier (ADR-066).
6337
6552
  confidenceKind: "url-with-structural-support",
6338
6553
  evidence: {
6339
- file: import_node_path28.default.relative(serviceDir, file.path),
6554
+ file: import_node_path29.default.relative(serviceDir, file.path),
6340
6555
  line,
6341
6556
  snippet: snippet(file.content, line)
6342
6557
  }
@@ -6347,8 +6562,8 @@ function redisEndpointsFromFile(file, serviceDir) {
6347
6562
 
6348
6563
  // src/extract/calls/aws.ts
6349
6564
  init_cjs_shims();
6350
- var import_node_path29 = __toESM(require("path"), 1);
6351
- var import_types17 = require("@neat.is/types");
6565
+ var import_node_path30 = __toESM(require("path"), 1);
6566
+ var import_types18 = require("@neat.is/types");
6352
6567
  var S3_BUCKET_RE = /Bucket\s*:\s*['"`]([^'"`]+)['"`]/g;
6353
6568
  var DYNAMO_TABLE_RE = /TableName\s*:\s*['"`]([^'"`]+)['"`]/g;
6354
6569
  function hasMarker(text, markers) {
@@ -6372,7 +6587,7 @@ function awsEndpointsFromFile(file, serviceDir) {
6372
6587
  seen.add(key);
6373
6588
  const line = lineOf(file.content, name);
6374
6589
  out.push({
6375
- infraId: (0, import_types17.infraId)(kind, name),
6590
+ infraId: (0, import_types18.infraId)(kind, name),
6376
6591
  name,
6377
6592
  kind,
6378
6593
  edgeType: "CALLS",
@@ -6381,7 +6596,7 @@ function awsEndpointsFromFile(file, serviceDir) {
6381
6596
  // (ADR-066).
6382
6597
  confidenceKind: "verified-call-site",
6383
6598
  evidence: {
6384
- file: import_node_path29.default.relative(serviceDir, file.path),
6599
+ file: import_node_path30.default.relative(serviceDir, file.path),
6385
6600
  line,
6386
6601
  snippet: snippet(file.content, line)
6387
6602
  }
@@ -6406,8 +6621,8 @@ function awsEndpointsFromFile(file, serviceDir) {
6406
6621
 
6407
6622
  // src/extract/calls/grpc.ts
6408
6623
  init_cjs_shims();
6409
- var import_node_path30 = __toESM(require("path"), 1);
6410
- var import_types18 = require("@neat.is/types");
6624
+ var import_node_path31 = __toESM(require("path"), 1);
6625
+ var import_types19 = require("@neat.is/types");
6411
6626
  var GRPC_CLIENT_RE = /new\s+([A-Z][A-Za-z0-9_]*)Client\s*\(\s*['"`]?([^,'"`)]+)?/g;
6412
6627
  var AWS_SDK_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@aws-sdk\/client-([a-z0-9-]+)['"`]/g;
6413
6628
  var GRPC_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@grpc\/grpc-js['"`]|_grpc_pb['"`]/;
@@ -6456,7 +6671,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
6456
6671
  const { kind } = classified;
6457
6672
  const line = lineOf(file.content, m[0]);
6458
6673
  out.push({
6459
- infraId: (0, import_types18.infraId)(kind, name),
6674
+ infraId: (0, import_types19.infraId)(kind, name),
6460
6675
  name,
6461
6676
  kind,
6462
6677
  edgeType: "CALLS",
@@ -6465,7 +6680,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
6465
6680
  // tier (ADR-066).
6466
6681
  confidenceKind: "verified-call-site",
6467
6682
  evidence: {
6468
- file: import_node_path30.default.relative(serviceDir, file.path),
6683
+ file: import_node_path31.default.relative(serviceDir, file.path),
6469
6684
  line,
6470
6685
  snippet: snippet(file.content, line)
6471
6686
  }
@@ -6476,8 +6691,8 @@ function grpcEndpointsFromFile(file, serviceDir) {
6476
6691
 
6477
6692
  // src/extract/calls/supabase.ts
6478
6693
  init_cjs_shims();
6479
- var import_node_path31 = __toESM(require("path"), 1);
6480
- var import_types19 = require("@neat.is/types");
6694
+ var import_node_path32 = __toESM(require("path"), 1);
6695
+ var import_types20 = require("@neat.is/types");
6481
6696
  var SUPABASE_JS_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@supabase\/supabase-js['"`]/;
6482
6697
  var SUPABASE_SSR_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@supabase\/ssr['"`]/;
6483
6698
  var SUPABASE_CLIENT_RE = /\b(createClient|createServerClient|createBrowserClient)\s*\(\s*(?:['"`]([^'"`]*)['"`])?/g;
@@ -6515,7 +6730,7 @@ function supabaseEndpointsFromFile(file, serviceDir) {
6515
6730
  seen.add(name);
6516
6731
  const line = lineOf(file.content, m[0]);
6517
6732
  out.push({
6518
- infraId: (0, import_types19.infraId)("supabase", name),
6733
+ infraId: (0, import_types20.infraId)("supabase", name),
6519
6734
  name,
6520
6735
  kind: "supabase",
6521
6736
  edgeType: "CALLS",
@@ -6525,7 +6740,7 @@ function supabaseEndpointsFromFile(file, serviceDir) {
6525
6740
  // tier (ADR-066), the same grade aws.ts / grpc.ts emit at.
6526
6741
  confidenceKind: "verified-call-site",
6527
6742
  evidence: {
6528
- file: import_node_path31.default.relative(serviceDir, file.path),
6743
+ file: import_node_path32.default.relative(serviceDir, file.path),
6529
6744
  line,
6530
6745
  snippet: snippet(file.content, line)
6531
6746
  }
@@ -6538,11 +6753,11 @@ function supabaseEndpointsFromFile(file, serviceDir) {
6538
6753
  function edgeTypeFromEndpoint(ep) {
6539
6754
  switch (ep.edgeType) {
6540
6755
  case "PUBLISHES_TO":
6541
- return import_types20.EdgeType.PUBLISHES_TO;
6756
+ return import_types21.EdgeType.PUBLISHES_TO;
6542
6757
  case "CONSUMES_FROM":
6543
- return import_types20.EdgeType.CONSUMES_FROM;
6758
+ return import_types21.EdgeType.CONSUMES_FROM;
6544
6759
  default:
6545
- return import_types20.EdgeType.CALLS;
6760
+ return import_types21.EdgeType.CALLS;
6546
6761
  }
6547
6762
  }
6548
6763
  function isAwsKind(kind) {
@@ -6570,7 +6785,7 @@ async function addExternalEndpointEdges(graph, services) {
6570
6785
  if (!graph.hasNode(ep.infraId)) {
6571
6786
  const node = {
6572
6787
  id: ep.infraId,
6573
- type: import_types20.NodeType.InfraNode,
6788
+ type: import_types21.NodeType.InfraNode,
6574
6789
  name: ep.name,
6575
6790
  // #238 — `aws-*` covers AWS-SDK client kinds (aws-s3, aws-dynamodb,
6576
6791
  // aws-cognito-identity-provider, …); `s3-` / `dynamodb-` cover the
@@ -6582,7 +6797,7 @@ async function addExternalEndpointEdges(graph, services) {
6582
6797
  nodesAdded++;
6583
6798
  }
6584
6799
  const edgeType = edgeTypeFromEndpoint(ep);
6585
- const confidence = (0, import_types20.confidenceForExtracted)(ep.confidenceKind);
6800
+ const confidence = (0, import_types21.confidenceForExtracted)(ep.confidenceKind);
6586
6801
  const relFile = toPosix2(ep.evidence.file);
6587
6802
  const { fileNodeId, nodesAdded: n, edgesAdded: e } = ensureFileNode(
6588
6803
  graph,
@@ -6592,7 +6807,7 @@ async function addExternalEndpointEdges(graph, services) {
6592
6807
  );
6593
6808
  nodesAdded += n;
6594
6809
  edgesAdded += e;
6595
- if (!(0, import_types20.passesExtractedFloor)(confidence)) {
6810
+ if (!(0, import_types21.passesExtractedFloor)(confidence)) {
6596
6811
  noteExtractedDropped({
6597
6812
  source: fileNodeId,
6598
6813
  target: ep.infraId,
@@ -6612,7 +6827,7 @@ async function addExternalEndpointEdges(graph, services) {
6612
6827
  source: fileNodeId,
6613
6828
  target: ep.infraId,
6614
6829
  type: edgeType,
6615
- provenance: import_types20.Provenance.EXTRACTED,
6830
+ provenance: import_types21.Provenance.EXTRACTED,
6616
6831
  confidence,
6617
6832
  evidence: ep.evidence
6618
6833
  };
@@ -6638,16 +6853,16 @@ init_cjs_shims();
6638
6853
 
6639
6854
  // src/extract/infra/docker-compose.ts
6640
6855
  init_cjs_shims();
6641
- var import_node_path32 = __toESM(require("path"), 1);
6642
- var import_types22 = require("@neat.is/types");
6856
+ var import_node_path33 = __toESM(require("path"), 1);
6857
+ var import_types23 = require("@neat.is/types");
6643
6858
 
6644
6859
  // src/extract/infra/shared.ts
6645
6860
  init_cjs_shims();
6646
- var import_types21 = require("@neat.is/types");
6861
+ var import_types22 = require("@neat.is/types");
6647
6862
  function makeInfraNode(kind, name, provider = "self", extras) {
6648
6863
  return {
6649
- id: (0, import_types21.infraId)(kind, name),
6650
- type: import_types21.NodeType.InfraNode,
6864
+ id: (0, import_types22.infraId)(kind, name),
6865
+ type: import_types22.NodeType.InfraNode,
6651
6866
  name,
6652
6867
  provider,
6653
6868
  kind,
@@ -6676,7 +6891,7 @@ function dependsOnList(value) {
6676
6891
  }
6677
6892
  function serviceNameToServiceNode(name, services) {
6678
6893
  for (const s of services) {
6679
- if (s.node.name === name || import_node_path32.default.basename(s.dir) === name) return s.node.id;
6894
+ if (s.node.name === name || import_node_path33.default.basename(s.dir) === name) return s.node.id;
6680
6895
  }
6681
6896
  return null;
6682
6897
  }
@@ -6685,7 +6900,7 @@ async function addComposeInfra(graph, scanPath, services) {
6685
6900
  let edgesAdded = 0;
6686
6901
  let composePath = null;
6687
6902
  for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
6688
- const abs = import_node_path32.default.join(scanPath, name);
6903
+ const abs = import_node_path33.default.join(scanPath, name);
6689
6904
  if (await exists2(abs)) {
6690
6905
  composePath = abs;
6691
6906
  break;
@@ -6698,13 +6913,13 @@ async function addComposeInfra(graph, scanPath, services) {
6698
6913
  } catch (err) {
6699
6914
  recordExtractionError(
6700
6915
  "infra docker-compose",
6701
- import_node_path32.default.relative(scanPath, composePath),
6916
+ import_node_path33.default.relative(scanPath, composePath),
6702
6917
  err
6703
6918
  );
6704
6919
  return { nodesAdded, edgesAdded };
6705
6920
  }
6706
6921
  if (!compose?.services) return { nodesAdded, edgesAdded };
6707
- const evidenceFile = import_node_path32.default.relative(scanPath, composePath).split(import_node_path32.default.sep).join("/");
6922
+ const evidenceFile = import_node_path33.default.relative(scanPath, composePath).split(import_node_path33.default.sep).join("/");
6708
6923
  const composeNameToNodeId = /* @__PURE__ */ new Map();
6709
6924
  for (const [composeName, svc] of Object.entries(compose.services)) {
6710
6925
  const matchedServiceId = serviceNameToServiceNode(composeName, services);
@@ -6726,15 +6941,15 @@ async function addComposeInfra(graph, scanPath, services) {
6726
6941
  for (const dep of dependsOnList(svc.depends_on)) {
6727
6942
  const targetId = composeNameToNodeId.get(dep);
6728
6943
  if (!targetId) continue;
6729
- const edgeId = (0, import_types5.extractedEdgeId)(sourceId, targetId, import_types22.EdgeType.DEPENDS_ON);
6944
+ const edgeId = (0, import_types5.extractedEdgeId)(sourceId, targetId, import_types23.EdgeType.DEPENDS_ON);
6730
6945
  if (graph.hasEdge(edgeId)) continue;
6731
6946
  const edge = {
6732
6947
  id: edgeId,
6733
6948
  source: sourceId,
6734
6949
  target: targetId,
6735
- type: import_types22.EdgeType.DEPENDS_ON,
6736
- provenance: import_types22.Provenance.EXTRACTED,
6737
- confidence: (0, import_types22.confidenceForExtracted)("structural"),
6950
+ type: import_types23.EdgeType.DEPENDS_ON,
6951
+ provenance: import_types23.Provenance.EXTRACTED,
6952
+ confidence: (0, import_types23.confidenceForExtracted)("structural"),
6738
6953
  evidence: { file: evidenceFile }
6739
6954
  };
6740
6955
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -6746,9 +6961,9 @@ async function addComposeInfra(graph, scanPath, services) {
6746
6961
 
6747
6962
  // src/extract/infra/dockerfile.ts
6748
6963
  init_cjs_shims();
6749
- var import_node_path33 = __toESM(require("path"), 1);
6750
- var import_node_fs17 = require("fs");
6751
- var import_types23 = require("@neat.is/types");
6964
+ var import_node_path34 = __toESM(require("path"), 1);
6965
+ var import_node_fs18 = require("fs");
6966
+ var import_types24 = require("@neat.is/types");
6752
6967
  function readDockerfile(content) {
6753
6968
  let image = null;
6754
6969
  const ports = [];
@@ -6777,15 +6992,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
6777
6992
  let nodesAdded = 0;
6778
6993
  let edgesAdded = 0;
6779
6994
  for (const service of services) {
6780
- const dockerfilePath = import_node_path33.default.join(service.dir, "Dockerfile");
6995
+ const dockerfilePath = import_node_path34.default.join(service.dir, "Dockerfile");
6781
6996
  if (!await exists2(dockerfilePath)) continue;
6782
6997
  let content;
6783
6998
  try {
6784
- content = await import_node_fs17.promises.readFile(dockerfilePath, "utf8");
6999
+ content = await import_node_fs18.promises.readFile(dockerfilePath, "utf8");
6785
7000
  } catch (err) {
6786
7001
  recordExtractionError(
6787
7002
  "infra dockerfile",
6788
- import_node_path33.default.relative(scanPath, dockerfilePath),
7003
+ import_node_path34.default.relative(scanPath, dockerfilePath),
6789
7004
  err
6790
7005
  );
6791
7006
  continue;
@@ -6797,8 +7012,8 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
6797
7012
  graph.addNode(node.id, node);
6798
7013
  nodesAdded++;
6799
7014
  }
6800
- const relDockerfile = toPosix2(import_node_path33.default.relative(service.dir, dockerfilePath));
6801
- const evidenceFile = toPosix2(import_node_path33.default.relative(scanPath, dockerfilePath));
7015
+ const relDockerfile = toPosix2(import_node_path34.default.relative(service.dir, dockerfilePath));
7016
+ const evidenceFile = toPosix2(import_node_path34.default.relative(scanPath, dockerfilePath));
6802
7017
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
6803
7018
  graph,
6804
7019
  service.pkg.name,
@@ -6807,15 +7022,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
6807
7022
  );
6808
7023
  nodesAdded += fn;
6809
7024
  edgesAdded += fe;
6810
- const edgeId = (0, import_types5.extractedEdgeId)(fileNodeId, node.id, import_types23.EdgeType.RUNS_ON);
7025
+ const edgeId = (0, import_types5.extractedEdgeId)(fileNodeId, node.id, import_types24.EdgeType.RUNS_ON);
6811
7026
  if (!graph.hasEdge(edgeId)) {
6812
7027
  const edge = {
6813
7028
  id: edgeId,
6814
7029
  source: fileNodeId,
6815
7030
  target: node.id,
6816
- type: import_types23.EdgeType.RUNS_ON,
6817
- provenance: import_types23.Provenance.EXTRACTED,
6818
- confidence: (0, import_types23.confidenceForExtracted)("structural"),
7031
+ type: import_types24.EdgeType.RUNS_ON,
7032
+ provenance: import_types24.Provenance.EXTRACTED,
7033
+ confidence: (0, import_types24.confidenceForExtracted)("structural"),
6819
7034
  evidence: {
6820
7035
  file: evidenceFile,
6821
7036
  ...facts.entrypoint ? { snippet: facts.entrypoint.slice(0, 120) } : {}
@@ -6830,15 +7045,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
6830
7045
  graph.addNode(portNode.id, portNode);
6831
7046
  nodesAdded++;
6832
7047
  }
6833
- const portEdgeId = (0, import_types5.extractedEdgeId)(fileNodeId, portNode.id, import_types23.EdgeType.CONNECTS_TO);
7048
+ const portEdgeId = (0, import_types5.extractedEdgeId)(fileNodeId, portNode.id, import_types24.EdgeType.CONNECTS_TO);
6834
7049
  if (graph.hasEdge(portEdgeId)) continue;
6835
7050
  const portEdge = {
6836
7051
  id: portEdgeId,
6837
7052
  source: fileNodeId,
6838
7053
  target: portNode.id,
6839
- type: import_types23.EdgeType.CONNECTS_TO,
6840
- provenance: import_types23.Provenance.EXTRACTED,
6841
- confidence: (0, import_types23.confidenceForExtracted)("structural"),
7054
+ type: import_types24.EdgeType.CONNECTS_TO,
7055
+ provenance: import_types24.Provenance.EXTRACTED,
7056
+ confidence: (0, import_types24.confidenceForExtracted)("structural"),
6842
7057
  evidence: { file: evidenceFile, snippet: `EXPOSE ${port}` }
6843
7058
  };
6844
7059
  graph.addEdgeWithKey(portEdgeId, portEdge.source, portEdge.target, portEdge);
@@ -6850,23 +7065,23 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
6850
7065
 
6851
7066
  // src/extract/infra/terraform.ts
6852
7067
  init_cjs_shims();
6853
- var import_node_fs18 = require("fs");
6854
- var import_node_path34 = __toESM(require("path"), 1);
6855
- var import_types24 = require("@neat.is/types");
7068
+ var import_node_fs19 = require("fs");
7069
+ var import_node_path35 = __toESM(require("path"), 1);
7070
+ var import_types25 = require("@neat.is/types");
6856
7071
  var RESOURCE_RE = /resource\s+"(aws_[A-Za-z0-9_]+)"\s+"([A-Za-z0-9_-]+)"/g;
6857
7072
  var REFERENCE_RE = /(?<![\w.])(aws_[A-Za-z0-9_]+)\.([A-Za-z0-9_-]+)/g;
6858
7073
  async function walkTfFiles(start, depth = 0, max = 5) {
6859
7074
  if (depth > max) return [];
6860
7075
  const out = [];
6861
- const entries = await import_node_fs18.promises.readdir(start, { withFileTypes: true }).catch(() => []);
7076
+ const entries = await import_node_fs19.promises.readdir(start, { withFileTypes: true }).catch(() => []);
6862
7077
  for (const entry of entries) {
6863
7078
  if (entry.isDirectory()) {
6864
7079
  if (IGNORED_DIRS.has(entry.name) || entry.name === ".terraform") continue;
6865
- const child = import_node_path34.default.join(start, entry.name);
7080
+ const child = import_node_path35.default.join(start, entry.name);
6866
7081
  if (await isPythonVenvDir(child)) continue;
6867
7082
  out.push(...await walkTfFiles(child, depth + 1, max));
6868
7083
  } else if (entry.isFile() && entry.name.endsWith(".tf")) {
6869
- out.push(import_node_path34.default.join(start, entry.name));
7084
+ out.push(import_node_path35.default.join(start, entry.name));
6870
7085
  }
6871
7086
  }
6872
7087
  return out;
@@ -6885,7 +7100,7 @@ function blockBody(content, from) {
6885
7100
  }
6886
7101
  return null;
6887
7102
  }
6888
- function lineAt(content, index) {
7103
+ function lineAt2(content, index) {
6889
7104
  let line = 1;
6890
7105
  for (let i = 0; i < index && i < content.length; i++) {
6891
7106
  if (content[i] === "\n") line++;
@@ -6897,8 +7112,8 @@ async function addTerraformResources(graph, scanPath) {
6897
7112
  let edgesAdded = 0;
6898
7113
  const files = await walkTfFiles(scanPath);
6899
7114
  for (const file of files) {
6900
- const content = await import_node_fs18.promises.readFile(file, "utf8");
6901
- const evidenceFile = toPosix2(import_node_path34.default.relative(scanPath, file));
7115
+ const content = await import_node_fs19.promises.readFile(file, "utf8");
7116
+ const evidenceFile = toPosix2(import_node_path35.default.relative(scanPath, file));
6902
7117
  const resources = [];
6903
7118
  const byKey = /* @__PURE__ */ new Map();
6904
7119
  RESOURCE_RE.lastIndex = 0;
@@ -6933,16 +7148,16 @@ async function addTerraformResources(graph, scanPath) {
6933
7148
  if (!target) continue;
6934
7149
  if (seen.has(target.nodeId)) continue;
6935
7150
  seen.add(target.nodeId);
6936
- const edgeId = (0, import_types5.extractedEdgeId)(resource.nodeId, target.nodeId, import_types24.EdgeType.DEPENDS_ON);
7151
+ const edgeId = (0, import_types5.extractedEdgeId)(resource.nodeId, target.nodeId, import_types25.EdgeType.DEPENDS_ON);
6937
7152
  if (graph.hasEdge(edgeId)) continue;
6938
- const line = lineAt(content, resource.bodyOffset + ref.index);
7153
+ const line = lineAt2(content, resource.bodyOffset + ref.index);
6939
7154
  const edge = {
6940
7155
  id: edgeId,
6941
7156
  source: resource.nodeId,
6942
7157
  target: target.nodeId,
6943
- type: import_types24.EdgeType.DEPENDS_ON,
6944
- provenance: import_types24.Provenance.EXTRACTED,
6945
- confidence: (0, import_types24.confidenceForExtracted)("structural"),
7158
+ type: import_types25.EdgeType.DEPENDS_ON,
7159
+ provenance: import_types25.Provenance.EXTRACTED,
7160
+ confidence: (0, import_types25.confidenceForExtracted)("structural"),
6946
7161
  evidence: { file: evidenceFile, line, snippet: key }
6947
7162
  };
6948
7163
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -6955,8 +7170,8 @@ async function addTerraformResources(graph, scanPath) {
6955
7170
 
6956
7171
  // src/extract/infra/k8s.ts
6957
7172
  init_cjs_shims();
6958
- var import_node_fs19 = require("fs");
6959
- var import_node_path35 = __toESM(require("path"), 1);
7173
+ var import_node_fs20 = require("fs");
7174
+ var import_node_path36 = __toESM(require("path"), 1);
6960
7175
  var import_yaml3 = require("yaml");
6961
7176
  var K8S_KIND_TO_INFRA_KIND = {
6962
7177
  Service: "k8s-service",
@@ -6970,15 +7185,15 @@ var K8S_KIND_TO_INFRA_KIND = {
6970
7185
  async function walkYamlFiles2(start, depth = 0, max = 5) {
6971
7186
  if (depth > max) return [];
6972
7187
  const out = [];
6973
- const entries = await import_node_fs19.promises.readdir(start, { withFileTypes: true }).catch(() => []);
7188
+ const entries = await import_node_fs20.promises.readdir(start, { withFileTypes: true }).catch(() => []);
6974
7189
  for (const entry of entries) {
6975
7190
  if (entry.isDirectory()) {
6976
7191
  if (IGNORED_DIRS.has(entry.name)) continue;
6977
- const child = import_node_path35.default.join(start, entry.name);
7192
+ const child = import_node_path36.default.join(start, entry.name);
6978
7193
  if (await isPythonVenvDir(child)) continue;
6979
7194
  out.push(...await walkYamlFiles2(child, depth + 1, max));
6980
- } else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path35.default.extname(entry.name))) {
6981
- out.push(import_node_path35.default.join(start, entry.name));
7195
+ } else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path36.default.extname(entry.name))) {
7196
+ out.push(import_node_path36.default.join(start, entry.name));
6982
7197
  }
6983
7198
  }
6984
7199
  return out;
@@ -6987,7 +7202,7 @@ async function addK8sResources(graph, scanPath) {
6987
7202
  let nodesAdded = 0;
6988
7203
  const files = await walkYamlFiles2(scanPath);
6989
7204
  for (const file of files) {
6990
- const content = await import_node_fs19.promises.readFile(file, "utf8");
7205
+ const content = await import_node_fs20.promises.readFile(file, "utf8");
6991
7206
  let docs;
6992
7207
  try {
6993
7208
  docs = (0, import_yaml3.parseAllDocuments)(content).map((d) => d.toJSON());
@@ -7022,17 +7237,17 @@ async function addInfra(graph, scanPath, services) {
7022
7237
  }
7023
7238
 
7024
7239
  // src/extract/index.ts
7025
- var import_node_path37 = __toESM(require("path"), 1);
7240
+ var import_node_path38 = __toESM(require("path"), 1);
7026
7241
 
7027
7242
  // src/extract/retire.ts
7028
7243
  init_cjs_shims();
7029
- var import_node_fs20 = require("fs");
7030
- var import_node_path36 = __toESM(require("path"), 1);
7031
- var import_types25 = require("@neat.is/types");
7244
+ var import_node_fs21 = require("fs");
7245
+ var import_node_path37 = __toESM(require("path"), 1);
7246
+ var import_types26 = require("@neat.is/types");
7032
7247
  function dropOrphanedFileNodes(graph) {
7033
7248
  const orphans = [];
7034
7249
  graph.forEachNode((id, attrs) => {
7035
- if (attrs.type !== import_types25.NodeType.FileNode) return;
7250
+ if (attrs.type !== import_types26.NodeType.FileNode) return;
7036
7251
  if (graph.inboundEdges(id).length === 0 && graph.outboundEdges(id).length === 0) {
7037
7252
  orphans.push(id);
7038
7253
  }
@@ -7045,14 +7260,14 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
7045
7260
  const bases = [scanPath, ...serviceDirs];
7046
7261
  graph.forEachEdge((id, attrs) => {
7047
7262
  const edge = attrs;
7048
- if (edge.provenance !== import_types25.Provenance.EXTRACTED) return;
7263
+ if (edge.provenance !== import_types26.Provenance.EXTRACTED) return;
7049
7264
  const evidenceFile = edge.evidence?.file;
7050
7265
  if (!evidenceFile) return;
7051
- if (import_node_path36.default.isAbsolute(evidenceFile)) {
7052
- if (!(0, import_node_fs20.existsSync)(evidenceFile)) toDrop.push(id);
7266
+ if (import_node_path37.default.isAbsolute(evidenceFile)) {
7267
+ if (!(0, import_node_fs21.existsSync)(evidenceFile)) toDrop.push(id);
7053
7268
  return;
7054
7269
  }
7055
- const found = bases.some((base) => (0, import_node_fs20.existsSync)(import_node_path36.default.join(base, evidenceFile)));
7270
+ const found = bases.some((base) => (0, import_node_fs21.existsSync)(import_node_path37.default.join(base, evidenceFile)));
7056
7271
  if (!found) toDrop.push(id);
7057
7272
  });
7058
7273
  for (const id of toDrop) graph.dropEdge(id);
@@ -7072,6 +7287,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
7072
7287
  const phase2 = await addDatabasesAndCompat(graph, services, scanPath);
7073
7288
  const phase3 = await addConfigNodes(graph, services, scanPath);
7074
7289
  const routePhase = await addRoutes(graph, services);
7290
+ const grpcPhase = await addGrpcMethods(graph, services);
7075
7291
  const phase4 = await addCallEdges(graph, services);
7076
7292
  const phase5 = await addInfra(graph, scanPath, services);
7077
7293
  const ghostsRetired = retireExtractedEdgesByMissingFile(
@@ -7093,7 +7309,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
7093
7309
  }
7094
7310
  const droppedEntries = drainDroppedExtracted();
7095
7311
  if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
7096
- const rejectedPath = import_node_path37.default.join(import_node_path37.default.dirname(opts.errorsPath), "rejected.ndjson");
7312
+ const rejectedPath = import_node_path38.default.join(import_node_path38.default.dirname(opts.errorsPath), "rejected.ndjson");
7097
7313
  try {
7098
7314
  await writeRejectedExtracted(droppedEntries, rejectedPath);
7099
7315
  } catch (err) {
@@ -7103,8 +7319,8 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
7103
7319
  }
7104
7320
  }
7105
7321
  const result = {
7106
- nodesAdded: phase1Nodes + fileEnum.nodesAdded + importGraph.nodesAdded + phase2.nodesAdded + phase3.nodesAdded + routePhase.nodesAdded + phase4.nodesAdded + phase5.nodesAdded,
7107
- edgesAdded: fileEnum.edgesAdded + importGraph.edgesAdded + phase2.edgesAdded + phase3.edgesAdded + routePhase.edgesAdded + phase4.edgesAdded + phase5.edgesAdded,
7322
+ nodesAdded: phase1Nodes + fileEnum.nodesAdded + importGraph.nodesAdded + phase2.nodesAdded + phase3.nodesAdded + routePhase.nodesAdded + grpcPhase.nodesAdded + phase4.nodesAdded + phase5.nodesAdded,
7323
+ edgesAdded: fileEnum.edgesAdded + importGraph.edgesAdded + phase2.edgesAdded + phase3.edgesAdded + routePhase.edgesAdded + grpcPhase.edgesAdded + phase4.edgesAdded + phase5.edgesAdded,
7108
7324
  frontiersPromoted,
7109
7325
  extractionErrors: errorEntries.length,
7110
7326
  errorEntries,
@@ -7127,7 +7343,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
7127
7343
 
7128
7344
  // src/diff.ts
7129
7345
  init_cjs_shims();
7130
- var import_node_fs21 = require("fs");
7346
+ var import_node_fs22 = require("fs");
7131
7347
  async function loadSnapshotForDiff(target) {
7132
7348
  if (/^https?:\/\//i.test(target)) {
7133
7349
  const res = await fetch(target);
@@ -7136,7 +7352,7 @@ async function loadSnapshotForDiff(target) {
7136
7352
  }
7137
7353
  return await res.json();
7138
7354
  }
7139
- const raw = await import_node_fs21.promises.readFile(target, "utf8");
7355
+ const raw = await import_node_fs22.promises.readFile(target, "utf8");
7140
7356
  return JSON.parse(raw);
7141
7357
  }
7142
7358
  function indexEntries(entries) {
@@ -7204,9 +7420,9 @@ function canonicalJson(value) {
7204
7420
 
7205
7421
  // src/persist.ts
7206
7422
  init_cjs_shims();
7207
- var import_node_fs22 = require("fs");
7208
- var import_node_path38 = __toESM(require("path"), 1);
7209
- var import_types26 = require("@neat.is/types");
7423
+ var import_node_fs23 = require("fs");
7424
+ var import_node_path39 = __toESM(require("path"), 1);
7425
+ var import_types27 = require("@neat.is/types");
7210
7426
  var SCHEMA_VERSION = 4;
7211
7427
  function migrateV1ToV2(payload) {
7212
7428
  const nodes = payload.graph.nodes;
@@ -7228,12 +7444,12 @@ function migrateV2ToV3(payload) {
7228
7444
  for (const edge of edges) {
7229
7445
  const attrs = edge.attributes;
7230
7446
  if (!attrs || attrs.provenance !== "FRONTIER") continue;
7231
- attrs.provenance = import_types26.Provenance.OBSERVED;
7447
+ attrs.provenance = import_types27.Provenance.OBSERVED;
7232
7448
  const type = typeof attrs.type === "string" ? attrs.type : void 0;
7233
7449
  const source = typeof attrs.source === "string" ? attrs.source : void 0;
7234
7450
  const target = typeof attrs.target === "string" ? attrs.target : void 0;
7235
7451
  if (type && source && target) {
7236
- const newId = (0, import_types26.observedEdgeId)(source, target, type);
7452
+ const newId = (0, import_types27.observedEdgeId)(source, target, type);
7237
7453
  attrs.id = newId;
7238
7454
  if (edge.key) edge.key = newId;
7239
7455
  }
@@ -7242,7 +7458,7 @@ function migrateV2ToV3(payload) {
7242
7458
  return { ...payload, schemaVersion: 3 };
7243
7459
  }
7244
7460
  async function ensureDir(filePath) {
7245
- await import_node_fs22.promises.mkdir(import_node_path38.default.dirname(filePath), { recursive: true });
7461
+ await import_node_fs23.promises.mkdir(import_node_path39.default.dirname(filePath), { recursive: true });
7246
7462
  }
7247
7463
  async function saveGraphToDisk(graph, outPath) {
7248
7464
  await ensureDir(outPath);
@@ -7252,13 +7468,13 @@ async function saveGraphToDisk(graph, outPath) {
7252
7468
  graph: graph.export()
7253
7469
  };
7254
7470
  const tmp = `${outPath}.tmp`;
7255
- await import_node_fs22.promises.writeFile(tmp, JSON.stringify(payload), "utf8");
7256
- await import_node_fs22.promises.rename(tmp, outPath);
7471
+ await import_node_fs23.promises.writeFile(tmp, JSON.stringify(payload), "utf8");
7472
+ await import_node_fs23.promises.rename(tmp, outPath);
7257
7473
  }
7258
7474
  async function loadGraphFromDisk(graph, outPath) {
7259
7475
  let raw;
7260
7476
  try {
7261
- raw = await import_node_fs22.promises.readFile(outPath, "utf8");
7477
+ raw = await import_node_fs23.promises.readFile(outPath, "utf8");
7262
7478
  } catch (err) {
7263
7479
  if (err.code === "ENOENT") return;
7264
7480
  throw err;
@@ -7323,23 +7539,23 @@ function startPersistLoop(graph, outPath, opts = {}) {
7323
7539
 
7324
7540
  // src/projects.ts
7325
7541
  init_cjs_shims();
7326
- var import_node_path39 = __toESM(require("path"), 1);
7542
+ var import_node_path40 = __toESM(require("path"), 1);
7327
7543
  function pathsForProject(project, baseDir) {
7328
7544
  if (project === DEFAULT_PROJECT) {
7329
7545
  return {
7330
- snapshotPath: import_node_path39.default.join(baseDir, "graph.json"),
7331
- errorsPath: import_node_path39.default.join(baseDir, "errors.ndjson"),
7332
- staleEventsPath: import_node_path39.default.join(baseDir, "stale-events.ndjson"),
7333
- embeddingsCachePath: import_node_path39.default.join(baseDir, "embeddings.json"),
7334
- policyViolationsPath: import_node_path39.default.join(baseDir, "policy-violations.ndjson")
7546
+ snapshotPath: import_node_path40.default.join(baseDir, "graph.json"),
7547
+ errorsPath: import_node_path40.default.join(baseDir, "errors.ndjson"),
7548
+ staleEventsPath: import_node_path40.default.join(baseDir, "stale-events.ndjson"),
7549
+ embeddingsCachePath: import_node_path40.default.join(baseDir, "embeddings.json"),
7550
+ policyViolationsPath: import_node_path40.default.join(baseDir, "policy-violations.ndjson")
7335
7551
  };
7336
7552
  }
7337
7553
  return {
7338
- snapshotPath: import_node_path39.default.join(baseDir, `${project}.json`),
7339
- errorsPath: import_node_path39.default.join(baseDir, `errors.${project}.ndjson`),
7340
- staleEventsPath: import_node_path39.default.join(baseDir, `stale-events.${project}.ndjson`),
7341
- embeddingsCachePath: import_node_path39.default.join(baseDir, `embeddings.${project}.json`),
7342
- policyViolationsPath: import_node_path39.default.join(baseDir, `policy-violations.${project}.ndjson`)
7554
+ snapshotPath: import_node_path40.default.join(baseDir, `${project}.json`),
7555
+ errorsPath: import_node_path40.default.join(baseDir, `errors.${project}.ndjson`),
7556
+ staleEventsPath: import_node_path40.default.join(baseDir, `stale-events.${project}.ndjson`),
7557
+ embeddingsCachePath: import_node_path40.default.join(baseDir, `embeddings.${project}.json`),
7558
+ policyViolationsPath: import_node_path40.default.join(baseDir, `policy-violations.${project}.ndjson`)
7343
7559
  };
7344
7560
  }
7345
7561
  var Projects = class {
@@ -7379,23 +7595,23 @@ function parseExtraProjects(raw) {
7379
7595
 
7380
7596
  // src/registry.ts
7381
7597
  init_cjs_shims();
7382
- var import_node_fs23 = require("fs");
7598
+ var import_node_fs24 = require("fs");
7383
7599
  var import_node_os3 = __toESM(require("os"), 1);
7384
- var import_node_path40 = __toESM(require("path"), 1);
7385
- var import_types27 = require("@neat.is/types");
7600
+ var import_node_path41 = __toESM(require("path"), 1);
7601
+ var import_types28 = require("@neat.is/types");
7386
7602
  function neatHome() {
7387
7603
  const override = process.env.NEAT_HOME;
7388
- if (override && override.length > 0) return import_node_path40.default.resolve(override);
7389
- return import_node_path40.default.join(import_node_os3.default.homedir(), ".neat");
7604
+ if (override && override.length > 0) return import_node_path41.default.resolve(override);
7605
+ return import_node_path41.default.join(import_node_os3.default.homedir(), ".neat");
7390
7606
  }
7391
7607
  function registryPath() {
7392
- return import_node_path40.default.join(neatHome(), "projects.json");
7608
+ return import_node_path41.default.join(neatHome(), "projects.json");
7393
7609
  }
7394
7610
  async function readRegistry() {
7395
7611
  const file = registryPath();
7396
7612
  let raw;
7397
7613
  try {
7398
- raw = await import_node_fs23.promises.readFile(file, "utf8");
7614
+ raw = await import_node_fs24.promises.readFile(file, "utf8");
7399
7615
  } catch (err) {
7400
7616
  if (err.code === "ENOENT") {
7401
7617
  return { version: 1, projects: [] };
@@ -7403,7 +7619,7 @@ async function readRegistry() {
7403
7619
  throw err;
7404
7620
  }
7405
7621
  const parsed = JSON.parse(raw);
7406
- return import_types27.RegistryFileSchema.parse(parsed);
7622
+ return import_types28.RegistryFileSchema.parse(parsed);
7407
7623
  }
7408
7624
  async function getProject(name) {
7409
7625
  const reg = await readRegistry();
@@ -7624,11 +7840,11 @@ function registerRoutes(scope, ctx) {
7624
7840
  const candidates = req.query.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
7625
7841
  const parsed = [];
7626
7842
  for (const c of candidates) {
7627
- const r = import_types28.DivergenceTypeSchema.safeParse(c);
7843
+ const r = import_types29.DivergenceTypeSchema.safeParse(c);
7628
7844
  if (!r.success) {
7629
7845
  return reply.code(400).send({
7630
7846
  error: `unknown divergence type "${c}"`,
7631
- allowed: import_types28.DivergenceTypeSchema.options
7847
+ allowed: import_types29.DivergenceTypeSchema.options
7632
7848
  });
7633
7849
  }
7634
7850
  parsed.push(r.data);
@@ -7847,7 +8063,7 @@ function registerRoutes(scope, ctx) {
7847
8063
  const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
7848
8064
  let violations = await log.readAll();
7849
8065
  if (req.query.severity) {
7850
- const sev = import_types28.PolicySeveritySchema.safeParse(req.query.severity);
8066
+ const sev = import_types29.PolicySeveritySchema.safeParse(req.query.severity);
7851
8067
  if (!sev.success) {
7852
8068
  return reply.code(400).send({
7853
8069
  error: "invalid severity",
@@ -7886,7 +8102,7 @@ function registerRoutes(scope, ctx) {
7886
8102
  scope.post("/policies/check", async (req, reply) => {
7887
8103
  const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
7888
8104
  if (!proj) return;
7889
- const parsed = import_types28.PoliciesCheckBodySchema.safeParse(req.body ?? {});
8105
+ const parsed = import_types29.PoliciesCheckBodySchema.safeParse(req.body ?? {});
7890
8106
  if (!parsed.success) {
7891
8107
  return reply.code(400).send({
7892
8108
  error: "invalid /policies/check body",
@@ -8148,8 +8364,8 @@ init_otel_grpc();
8148
8364
 
8149
8365
  // src/search.ts
8150
8366
  init_cjs_shims();
8151
- var import_node_fs24 = require("fs");
8152
- var import_node_path43 = __toESM(require("path"), 1);
8367
+ var import_node_fs25 = require("fs");
8368
+ var import_node_path44 = __toESM(require("path"), 1);
8153
8369
  var import_node_crypto3 = require("crypto");
8154
8370
  var DEFAULT_LIMIT = 10;
8155
8371
  var NOMIC_DIM = 768;
@@ -8196,6 +8412,18 @@ function embedText(node) {
8196
8412
  if (opType) parts.push(`operationType=${opType}`);
8197
8413
  break;
8198
8414
  }
8415
+ case "GrpcMethodNode": {
8416
+ const rpcService = node.rpcService;
8417
+ const rpcMethod = node.rpcMethod;
8418
+ if (rpcService) parts.push(`rpcService=${rpcService}`);
8419
+ if (rpcMethod) parts.push(`rpcMethod=${rpcMethod}`);
8420
+ break;
8421
+ }
8422
+ case "WebSocketChannelNode": {
8423
+ const channel = node.channel;
8424
+ if (channel) parts.push(`channel=${channel}`);
8425
+ break;
8426
+ }
8199
8427
  default:
8200
8428
  break;
8201
8429
  }
@@ -8291,7 +8519,7 @@ async function pickEmbedder() {
8291
8519
  }
8292
8520
  async function readCache(cachePath) {
8293
8521
  try {
8294
- const raw = await import_node_fs24.promises.readFile(cachePath, "utf8");
8522
+ const raw = await import_node_fs25.promises.readFile(cachePath, "utf8");
8295
8523
  const parsed = JSON.parse(raw);
8296
8524
  if (parsed.version !== 1) return null;
8297
8525
  return parsed;
@@ -8300,8 +8528,8 @@ async function readCache(cachePath) {
8300
8528
  }
8301
8529
  }
8302
8530
  async function writeCache(cachePath, cache) {
8303
- await import_node_fs24.promises.mkdir(import_node_path43.default.dirname(cachePath), { recursive: true });
8304
- await import_node_fs24.promises.writeFile(cachePath, JSON.stringify(cache));
8531
+ await import_node_fs25.promises.mkdir(import_node_path44.default.dirname(cachePath), { recursive: true });
8532
+ await import_node_fs25.promises.writeFile(cachePath, JSON.stringify(cache));
8305
8533
  }
8306
8534
  var VectorIndex = class {
8307
8535
  constructor(embedder, cachePath) {
@@ -8481,14 +8709,14 @@ async function bootProject(registry, name, scanPath, baseDir) {
8481
8709
  async function main() {
8482
8710
  const baseDirEnv = process.env.NEAT_OUT_DIR;
8483
8711
  const legacyOutPath = process.env.NEAT_OUT_PATH;
8484
- const baseDir = baseDirEnv ? import_node_path44.default.resolve(baseDirEnv) : legacyOutPath ? import_node_path44.default.resolve(import_node_path44.default.dirname(legacyOutPath)) : import_node_path44.default.resolve("./neat-out");
8485
- const defaultScanPath = import_node_path44.default.resolve(process.env.NEAT_SCAN_PATH ?? "./demo");
8712
+ const baseDir = baseDirEnv ? import_node_path45.default.resolve(baseDirEnv) : legacyOutPath ? import_node_path45.default.resolve(import_node_path45.default.dirname(legacyOutPath)) : import_node_path45.default.resolve("./neat-out");
8713
+ const defaultScanPath = import_node_path45.default.resolve(process.env.NEAT_SCAN_PATH ?? "./demo");
8486
8714
  const registry = new Projects();
8487
8715
  await bootProject(registry, DEFAULT_PROJECT, defaultScanPath, baseDir);
8488
8716
  for (const name of parseExtraProjects(process.env.NEAT_PROJECTS)) {
8489
8717
  const envKey = `NEAT_PROJECT_SCAN_PATH_${name.toUpperCase().replace(/[^A-Z0-9]/g, "_")}`;
8490
8718
  const projectScan = process.env[envKey];
8491
- await bootProject(registry, name, projectScan ? import_node_path44.default.resolve(projectScan) : void 0, baseDir);
8719
+ await bootProject(registry, name, projectScan ? import_node_path45.default.resolve(projectScan) : void 0, baseDir);
8492
8720
  }
8493
8721
  const host = process.env.HOST ?? "0.0.0.0";
8494
8722
  const port = Number(process.env.PORT ?? 8080);