@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.
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  mountBearerAuth,
3
3
  readAuthEnv
4
- } from "./chunk-2LNICOVU.js";
4
+ } from "./chunk-A3322JYS.js";
5
5
 
6
6
  // src/graph.ts
7
7
  import GraphDefault from "graphology";
@@ -456,19 +456,19 @@ function confidenceFromMix(edges, now = Date.now()) {
456
456
  function longestIncomingWalk(graph, start, maxDepth) {
457
457
  let best = { path: [start], edges: [] };
458
458
  const visited = /* @__PURE__ */ new Set([start]);
459
- function step(node, path41, edges) {
460
- if (path41.length > best.path.length) {
461
- best = { path: [...path41], edges: [...edges] };
459
+ function step(node, path42, edges) {
460
+ if (path42.length > best.path.length) {
461
+ best = { path: [...path42], edges: [...edges] };
462
462
  }
463
- if (path41.length - 1 >= maxDepth) return;
463
+ if (path42.length - 1 >= maxDepth) return;
464
464
  const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
465
465
  for (const [srcId, edge] of incoming) {
466
466
  if (visited.has(srcId)) continue;
467
467
  visited.add(srcId);
468
- path41.push(srcId);
468
+ path42.push(srcId);
469
469
  edges.push(edge);
470
- step(srcId, path41, edges);
471
- path41.pop();
470
+ step(srcId, path42, edges);
471
+ path42.pop();
472
472
  edges.pop();
473
473
  visited.delete(srcId);
474
474
  }
@@ -662,26 +662,26 @@ function dominantFailingCall(graph, serviceId4, visited) {
662
662
  return best;
663
663
  }
664
664
  function followFailingCallChain(graph, originServiceId, maxDepth) {
665
- const path41 = [originServiceId];
665
+ const path42 = [originServiceId];
666
666
  const edges = [];
667
667
  const visited = /* @__PURE__ */ new Set([originServiceId]);
668
668
  let current = originServiceId;
669
669
  for (let depth = 0; depth < maxDepth; depth++) {
670
670
  const hop = dominantFailingCall(graph, current, visited);
671
671
  if (!hop) break;
672
- path41.push(hop.nextService);
672
+ path42.push(hop.nextService);
673
673
  edges.push(hop.edge);
674
674
  visited.add(hop.nextService);
675
675
  current = hop.nextService;
676
676
  }
677
677
  if (edges.length === 0) return null;
678
- return { path: path41, edges, culprit: current };
678
+ return { path: path42, edges, culprit: current };
679
679
  }
680
680
  function crossServiceRootCause(graph, originId, incidents, errorEvent) {
681
681
  const chain = followFailingCallChain(graph, originId, ROOT_CAUSE_MAX_DEPTH);
682
682
  if (!chain) return null;
683
683
  const culprit = chain.culprit;
684
- const path41 = [...chain.path];
684
+ const path42 = [...chain.path];
685
685
  const edgeProvenances = chain.edges.map((e) => e.provenance);
686
686
  const baseConfidence = confidenceFromMix(chain.edges);
687
687
  const confidence = Math.max(0, Math.min(1, baseConfidence * INCIDENT_ROOT_CAUSE_CONFIDENCE));
@@ -689,14 +689,14 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
689
689
  if (loc) {
690
690
  let rootCauseNode = culprit;
691
691
  if (loc.fileNode) {
692
- path41.push(loc.fileNode);
692
+ path42.push(loc.fileNode);
693
693
  edgeProvenances.push(Provenance.OBSERVED);
694
694
  rootCauseNode = loc.fileNode;
695
695
  }
696
696
  return RootCauseResultSchema.parse({
697
697
  rootCauseNode,
698
698
  rootCauseReason: loc.rootCauseReason,
699
- traversalPath: path41,
699
+ traversalPath: path42,
700
700
  edgeProvenances,
701
701
  confidence,
702
702
  ...loc.fixRecommendation ? { fixRecommendation: loc.fixRecommendation } : {}
@@ -708,7 +708,7 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
708
708
  return RootCauseResultSchema.parse({
709
709
  rootCauseNode: culprit,
710
710
  rootCauseReason: `${culpritName} is failing downstream calls (${errs} observed error${errs === 1 ? "" : "s"})`,
711
- traversalPath: path41,
711
+ traversalPath: path42,
712
712
  edgeProvenances,
713
713
  confidence,
714
714
  fixRecommendation: `Inspect ${culpritName}'s failing handler`
@@ -1382,10 +1382,12 @@ import {
1382
1382
  fileId,
1383
1383
  frontierId,
1384
1384
  graphqlOperationId,
1385
+ grpcMethodId,
1385
1386
  inferredEdgeId,
1386
1387
  infraId,
1387
1388
  observedEdgeId,
1388
- serviceId
1389
+ serviceId,
1390
+ websocketChannelId
1389
1391
  } from "@neat.is/types";
1390
1392
  var HOUR_MS = 60 * 60 * 1e3;
1391
1393
  var DAY_MS = 24 * HOUR_MS;
@@ -1748,6 +1750,40 @@ function ensureGraphqlOperationNode(graph, serviceName, operationType, operation
1748
1750
  graph.addNode(id, node);
1749
1751
  return id;
1750
1752
  }
1753
+ function spanServesGrpcMethod(kind) {
1754
+ return kind !== WIRE_SPAN_KIND_CLIENT && kind !== WIRE_SPAN_KIND_PRODUCER && kind !== WIRE_SPAN_KIND_CONSUMER;
1755
+ }
1756
+ function ensureGrpcMethodNode(graph, rpcService, rpcMethod) {
1757
+ const id = grpcMethodId(rpcService, rpcMethod);
1758
+ if (graph.hasNode(id)) return id;
1759
+ const node = {
1760
+ id,
1761
+ type: NodeType3.GrpcMethodNode,
1762
+ name: `${rpcService}/${rpcMethod}`,
1763
+ rpcService,
1764
+ rpcMethod,
1765
+ discoveredVia: "otel"
1766
+ };
1767
+ graph.addNode(id, node);
1768
+ return id;
1769
+ }
1770
+ function spanServesWebsocketChannel(kind) {
1771
+ return kind !== WIRE_SPAN_KIND_CLIENT && kind !== WIRE_SPAN_KIND_PRODUCER && kind !== WIRE_SPAN_KIND_CONSUMER;
1772
+ }
1773
+ function ensureWebsocketChannelNode(graph, serviceName, channel) {
1774
+ const id = websocketChannelId(serviceName, channel);
1775
+ if (graph.hasNode(id)) return id;
1776
+ const node = {
1777
+ id,
1778
+ type: NodeType3.WebSocketChannelNode,
1779
+ name: channel,
1780
+ service: serviceName,
1781
+ channel,
1782
+ discoveredVia: "otel"
1783
+ };
1784
+ graph.addNode(id, node);
1785
+ return id;
1786
+ }
1751
1787
  function messagingDestinationKind(system) {
1752
1788
  return `${system}-topic`;
1753
1789
  }
@@ -2183,6 +2219,34 @@ async function handleSpan(ctx, span) {
2183
2219
  callSiteEvidence
2184
2220
  );
2185
2221
  if (result) affectedNode = targetId;
2222
+ } else if (span.rpcSystem === "grpc" && span.rpcService && span.rpcMethod && spanServesGrpcMethod(span.kind)) {
2223
+ const targetId = ensureGrpcMethodNode(ctx.graph, span.rpcService, span.rpcMethod);
2224
+ const result = upsertObservedEdge(
2225
+ ctx.graph,
2226
+ EdgeType3.CONTAINS,
2227
+ observedSource(),
2228
+ targetId,
2229
+ ts,
2230
+ isError,
2231
+ callSiteEvidence
2232
+ );
2233
+ if (result) affectedNode = targetId;
2234
+ } else if (span.websocketChannel && spanServesWebsocketChannel(span.kind)) {
2235
+ const targetId = ensureWebsocketChannelNode(
2236
+ ctx.graph,
2237
+ span.service,
2238
+ span.websocketChannel
2239
+ );
2240
+ const result = upsertObservedEdge(
2241
+ ctx.graph,
2242
+ EdgeType3.CONNECTS_TO,
2243
+ observedSource(),
2244
+ targetId,
2245
+ ts,
2246
+ isError,
2247
+ callSiteEvidence
2248
+ );
2249
+ if (result) affectedNode = targetId;
2186
2250
  } else {
2187
2251
  const host = pickAddress(span);
2188
2252
  let resolvedViaAddress = false;
@@ -4774,24 +4838,151 @@ async function addRoutes(graph, services) {
4774
4838
  return { nodesAdded, edgesAdded };
4775
4839
  }
4776
4840
 
4841
+ // src/extract/proto.ts
4842
+ import { promises as fs15 } from "fs";
4843
+ import path23 from "path";
4844
+ import {
4845
+ EdgeType as EdgeType9,
4846
+ NodeType as NodeType10,
4847
+ Provenance as Provenance8,
4848
+ confidenceForExtracted as confidenceForExtracted6,
4849
+ extractedEdgeId as extractedEdgeId6,
4850
+ grpcMethodId as grpcMethodId2
4851
+ } from "@neat.is/types";
4852
+ var PROTO_EXTENSION = ".proto";
4853
+ function packageOf(content) {
4854
+ const m = content.match(/^\s*package\s+([A-Za-z_][A-Za-z0-9_.]*)\s*;/m);
4855
+ return m ? m[1] : null;
4856
+ }
4857
+ function lineAt(content, offset) {
4858
+ return content.slice(0, offset).split("\n").length;
4859
+ }
4860
+ function grpcMethodsFromProto(content, fqPackage) {
4861
+ const out = [];
4862
+ const serviceRe = /\bservice\s+([A-Za-z_][A-Za-z0-9_]*)\s*\{/g;
4863
+ let sm;
4864
+ while ((sm = serviceRe.exec(content)) !== null) {
4865
+ const serviceName = sm[1];
4866
+ const rpcService = fqPackage ? `${fqPackage}.${serviceName}` : serviceName;
4867
+ const bodyStart = serviceRe.lastIndex;
4868
+ let depth = 1;
4869
+ let i = bodyStart;
4870
+ for (; i < content.length && depth > 0; i++) {
4871
+ const ch = content[i];
4872
+ if (ch === "{") depth++;
4873
+ else if (ch === "}") depth--;
4874
+ }
4875
+ const body = content.slice(bodyStart, i - 1);
4876
+ const rpcRe = /\brpc\s+([A-Za-z_][A-Za-z0-9_]*)\s*\(/g;
4877
+ let rm;
4878
+ while ((rm = rpcRe.exec(body)) !== null) {
4879
+ const rpcMethod = rm[1];
4880
+ const line = lineAt(content, bodyStart + rm.index);
4881
+ out.push({ rpcService, rpcMethod, line });
4882
+ }
4883
+ serviceRe.lastIndex = i;
4884
+ }
4885
+ return out;
4886
+ }
4887
+ async function walkProtoFiles(dir) {
4888
+ const out = [];
4889
+ async function walk3(current) {
4890
+ const entries = await fs15.readdir(current, { withFileTypes: true }).catch(() => []);
4891
+ for (const entry of entries) {
4892
+ const full = path23.join(current, entry.name);
4893
+ if (entry.isDirectory()) {
4894
+ if (IGNORED_DIRS.has(entry.name)) continue;
4895
+ if (await isPythonVenvDir(full)) continue;
4896
+ await walk3(full);
4897
+ } else if (entry.isFile() && path23.extname(entry.name) === PROTO_EXTENSION) {
4898
+ out.push(full);
4899
+ }
4900
+ }
4901
+ }
4902
+ await walk3(dir);
4903
+ return out;
4904
+ }
4905
+ async function addGrpcMethods(graph, services) {
4906
+ let nodesAdded = 0;
4907
+ let edgesAdded = 0;
4908
+ for (const service of services) {
4909
+ const protoPaths = await walkProtoFiles(service.dir);
4910
+ for (const protoPath of protoPaths) {
4911
+ if (isTestPath(protoPath)) continue;
4912
+ const relFile = toPosix2(path23.relative(service.dir, protoPath));
4913
+ let content;
4914
+ try {
4915
+ content = await fs15.readFile(protoPath, "utf8");
4916
+ } catch (err) {
4917
+ recordExtractionError("proto extraction", protoPath, err);
4918
+ continue;
4919
+ }
4920
+ let methods;
4921
+ try {
4922
+ methods = grpcMethodsFromProto(content, packageOf(content));
4923
+ } catch (err) {
4924
+ recordExtractionError("proto extraction", protoPath, err);
4925
+ continue;
4926
+ }
4927
+ if (methods.length === 0) continue;
4928
+ for (const method of methods) {
4929
+ const mid = grpcMethodId2(method.rpcService, method.rpcMethod);
4930
+ if (!graph.hasNode(mid)) {
4931
+ const node = {
4932
+ id: mid,
4933
+ type: NodeType10.GrpcMethodNode,
4934
+ name: `${method.rpcService}/${method.rpcMethod}`,
4935
+ rpcService: method.rpcService,
4936
+ rpcMethod: method.rpcMethod,
4937
+ path: relFile,
4938
+ line: method.line,
4939
+ discoveredVia: "static"
4940
+ };
4941
+ graph.addNode(mid, node);
4942
+ nodesAdded++;
4943
+ }
4944
+ const containsId = extractedEdgeId6(service.node.id, mid, EdgeType9.CONTAINS);
4945
+ if (!graph.hasEdge(containsId)) {
4946
+ const edge = {
4947
+ id: containsId,
4948
+ source: service.node.id,
4949
+ target: mid,
4950
+ type: EdgeType9.CONTAINS,
4951
+ provenance: Provenance8.EXTRACTED,
4952
+ confidence: confidenceForExtracted6("structural"),
4953
+ evidence: {
4954
+ file: relFile,
4955
+ line: method.line,
4956
+ snippet: snippet(content, method.line)
4957
+ }
4958
+ };
4959
+ graph.addEdgeWithKey(containsId, service.node.id, mid, edge);
4960
+ edgesAdded++;
4961
+ }
4962
+ }
4963
+ }
4964
+ }
4965
+ return { nodesAdded, edgesAdded };
4966
+ }
4967
+
4777
4968
  // src/extract/calls/index.ts
4778
4969
  import {
4779
- EdgeType as EdgeType11,
4780
- NodeType as NodeType11,
4781
- Provenance as Provenance10,
4782
- confidenceForExtracted as confidenceForExtracted8,
4970
+ EdgeType as EdgeType12,
4971
+ NodeType as NodeType12,
4972
+ Provenance as Provenance11,
4973
+ confidenceForExtracted as confidenceForExtracted9,
4783
4974
  passesExtractedFloor as passesExtractedFloor3
4784
4975
  } from "@neat.is/types";
4785
4976
 
4786
4977
  // src/extract/calls/http.ts
4787
- import path23 from "path";
4978
+ import path24 from "path";
4788
4979
  import Parser3 from "tree-sitter";
4789
4980
  import JavaScript3 from "tree-sitter-javascript";
4790
4981
  import Python2 from "tree-sitter-python";
4791
4982
  import {
4792
- EdgeType as EdgeType9,
4793
- Provenance as Provenance8,
4794
- confidenceForExtracted as confidenceForExtracted6,
4983
+ EdgeType as EdgeType10,
4984
+ Provenance as Provenance9,
4985
+ confidenceForExtracted as confidenceForExtracted7,
4795
4986
  passesExtractedFloor
4796
4987
  } from "@neat.is/types";
4797
4988
  var STRING_LITERAL_NODE_TYPES = /* @__PURE__ */ new Set(["string_fragment", "string_content"]);
@@ -4863,7 +5054,7 @@ async function addHttpCallEdges(graph, services) {
4863
5054
  const seen = /* @__PURE__ */ new Set();
4864
5055
  for (const file of files) {
4865
5056
  if (isTestPath(file.path)) continue;
4866
- const parser = path23.extname(file.path) === ".py" ? pyParser : jsParser;
5057
+ const parser = path24.extname(file.path) === ".py" ? pyParser : jsParser;
4867
5058
  let sites;
4868
5059
  try {
4869
5060
  sites = callsFromSource(file.content, parser, knownHosts);
@@ -4872,14 +5063,14 @@ async function addHttpCallEdges(graph, services) {
4872
5063
  continue;
4873
5064
  }
4874
5065
  if (sites.length === 0) continue;
4875
- const relFile = toPosix2(path23.relative(service.dir, file.path));
5066
+ const relFile = toPosix2(path24.relative(service.dir, file.path));
4876
5067
  for (const site of sites) {
4877
5068
  const targetId = hostToNodeId.get(site.host);
4878
5069
  if (!targetId || targetId === service.node.id) continue;
4879
5070
  const dedupKey = `${relFile}|${targetId}`;
4880
5071
  if (seen.has(dedupKey)) continue;
4881
5072
  seen.add(dedupKey);
4882
- const confidence = confidenceForExtracted6("url-literal-service-target");
5073
+ const confidence = confidenceForExtracted7("url-literal-service-target");
4883
5074
  const ev = {
4884
5075
  file: relFile,
4885
5076
  line: site.line,
@@ -4897,21 +5088,21 @@ async function addHttpCallEdges(graph, services) {
4897
5088
  noteExtractedDropped({
4898
5089
  source: fileNodeId,
4899
5090
  target: targetId,
4900
- type: EdgeType9.CALLS,
5091
+ type: EdgeType10.CALLS,
4901
5092
  confidence,
4902
5093
  confidenceKind: "url-literal-service-target",
4903
5094
  evidence: ev
4904
5095
  });
4905
5096
  continue;
4906
5097
  }
4907
- const edgeId = extractedEdgeId2(fileNodeId, targetId, EdgeType9.CALLS);
5098
+ const edgeId = extractedEdgeId2(fileNodeId, targetId, EdgeType10.CALLS);
4908
5099
  if (!graph.hasEdge(edgeId)) {
4909
5100
  const edge = {
4910
5101
  id: edgeId,
4911
5102
  source: fileNodeId,
4912
5103
  target: targetId,
4913
- type: EdgeType9.CALLS,
4914
- provenance: Provenance8.EXTRACTED,
5104
+ type: EdgeType10.CALLS,
5105
+ provenance: Provenance9.EXTRACTED,
4915
5106
  confidence,
4916
5107
  evidence: ev
4917
5108
  };
@@ -4925,14 +5116,14 @@ async function addHttpCallEdges(graph, services) {
4925
5116
  }
4926
5117
 
4927
5118
  // src/extract/calls/route-match.ts
4928
- import path24 from "path";
5119
+ import path25 from "path";
4929
5120
  import Parser4 from "tree-sitter";
4930
5121
  import JavaScript4 from "tree-sitter-javascript";
4931
5122
  import {
4932
- EdgeType as EdgeType10,
4933
- NodeType as NodeType10,
4934
- Provenance as Provenance9,
4935
- confidenceForExtracted as confidenceForExtracted7,
5123
+ EdgeType as EdgeType11,
5124
+ NodeType as NodeType11,
5125
+ Provenance as Provenance10,
5126
+ confidenceForExtracted as confidenceForExtracted8,
4936
5127
  passesExtractedFloor as passesExtractedFloor2,
4937
5128
  serviceId as serviceId3
4938
5129
  } from "@neat.is/types";
@@ -5100,7 +5291,7 @@ function buildRouteIndex(graph) {
5100
5291
  const index = /* @__PURE__ */ new Map();
5101
5292
  graph.forEachNode((_id, attrs) => {
5102
5293
  const node = attrs;
5103
- if (node.type !== NodeType10.RouteNode) return;
5294
+ if (node.type !== NodeType11.RouteNode) return;
5104
5295
  const route = attrs;
5105
5296
  const owner = serviceId3(route.service);
5106
5297
  const entry = {
@@ -5131,7 +5322,7 @@ async function addRouteCallEdges(graph, services) {
5131
5322
  const seen = /* @__PURE__ */ new Set();
5132
5323
  for (const file of files) {
5133
5324
  if (isTestPath(file.path)) continue;
5134
- if (!JS_CLIENT_EXTENSIONS.has(path24.extname(file.path))) continue;
5325
+ if (!JS_CLIENT_EXTENSIONS.has(path25.extname(file.path))) continue;
5135
5326
  let sites;
5136
5327
  try {
5137
5328
  sites = clientCallSitesFromSource(file.content, jsParser, knownHosts);
@@ -5140,7 +5331,7 @@ async function addRouteCallEdges(graph, services) {
5140
5331
  continue;
5141
5332
  }
5142
5333
  if (sites.length === 0) continue;
5143
- const relFile = toPosix2(path24.relative(service.dir, file.path));
5334
+ const relFile = toPosix2(path25.relative(service.dir, file.path));
5144
5335
  for (const site of sites) {
5145
5336
  const serverServiceId = hostToNodeId.get(site.host);
5146
5337
  if (!serverServiceId || serverServiceId === service.node.id) continue;
@@ -5160,7 +5351,7 @@ async function addRouteCallEdges(graph, services) {
5160
5351
  );
5161
5352
  nodesAdded += n;
5162
5353
  edgesAdded += e;
5163
- const confidence = confidenceForExtracted7("verified-call-site");
5354
+ const confidence = confidenceForExtracted8("verified-call-site");
5164
5355
  const ev = {
5165
5356
  file: relFile,
5166
5357
  line: site.line,
@@ -5172,21 +5363,21 @@ async function addRouteCallEdges(graph, services) {
5172
5363
  noteExtractedDropped({
5173
5364
  source: fileNodeId,
5174
5365
  target: match.routeNodeId,
5175
- type: EdgeType10.CALLS,
5366
+ type: EdgeType11.CALLS,
5176
5367
  confidence,
5177
5368
  confidenceKind: "verified-call-site",
5178
5369
  evidence: ev
5179
5370
  });
5180
5371
  continue;
5181
5372
  }
5182
- const edgeId = extractedEdgeId2(fileNodeId, match.routeNodeId, EdgeType10.CALLS);
5373
+ const edgeId = extractedEdgeId2(fileNodeId, match.routeNodeId, EdgeType11.CALLS);
5183
5374
  if (!graph.hasEdge(edgeId)) {
5184
5375
  const edge = {
5185
5376
  id: edgeId,
5186
5377
  source: fileNodeId,
5187
5378
  target: match.routeNodeId,
5188
- type: EdgeType10.CALLS,
5189
- provenance: Provenance9.EXTRACTED,
5379
+ type: EdgeType11.CALLS,
5380
+ provenance: Provenance10.EXTRACTED,
5190
5381
  confidence,
5191
5382
  evidence: ev
5192
5383
  };
@@ -5200,7 +5391,7 @@ async function addRouteCallEdges(graph, services) {
5200
5391
  }
5201
5392
 
5202
5393
  // src/extract/calls/kafka.ts
5203
- import path25 from "path";
5394
+ import path26 from "path";
5204
5395
  import { infraId as infraId2 } from "@neat.is/types";
5205
5396
  var PRODUCER_TOPIC_RE = /(?:producer|kafkaProducer)[\s\S]{0,40}?\.send\s*\(\s*\{[\s\S]{0,200}?topic\s*:\s*['"`]([^'"`]+)['"`]/g;
5206
5397
  var CONSUMER_TOPIC_RE = /(?:consumer|kafkaConsumer)[\s\S]{0,40}?\.(?:subscribe|run)\s*\(\s*\{[\s\S]{0,200}?topic[s]?\s*:\s*(?:\[\s*)?['"`]([^'"`]+)['"`]/g;
@@ -5231,7 +5422,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
5231
5422
  // tier (ADR-066).
5232
5423
  confidenceKind: "verified-call-site",
5233
5424
  evidence: {
5234
- file: path25.relative(serviceDir, file.path),
5425
+ file: path26.relative(serviceDir, file.path),
5235
5426
  line,
5236
5427
  snippet: snippet(file.content, line)
5237
5428
  }
@@ -5243,7 +5434,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
5243
5434
  }
5244
5435
 
5245
5436
  // src/extract/calls/redis.ts
5246
- import path26 from "path";
5437
+ import path27 from "path";
5247
5438
  import { infraId as infraId3 } from "@neat.is/types";
5248
5439
  var REDIS_URL_RE = /redis(?:s)?:\/\/(?:[^@'"`\s]+@)?([^:/'"`\s]+)(?::(\d+))?/g;
5249
5440
  function redisEndpointsFromFile(file, serviceDir) {
@@ -5266,7 +5457,7 @@ function redisEndpointsFromFile(file, serviceDir) {
5266
5457
  // support tier (ADR-066).
5267
5458
  confidenceKind: "url-with-structural-support",
5268
5459
  evidence: {
5269
- file: path26.relative(serviceDir, file.path),
5460
+ file: path27.relative(serviceDir, file.path),
5270
5461
  line,
5271
5462
  snippet: snippet(file.content, line)
5272
5463
  }
@@ -5276,7 +5467,7 @@ function redisEndpointsFromFile(file, serviceDir) {
5276
5467
  }
5277
5468
 
5278
5469
  // src/extract/calls/aws.ts
5279
- import path27 from "path";
5470
+ import path28 from "path";
5280
5471
  import { infraId as infraId4 } from "@neat.is/types";
5281
5472
  var S3_BUCKET_RE = /Bucket\s*:\s*['"`]([^'"`]+)['"`]/g;
5282
5473
  var DYNAMO_TABLE_RE = /TableName\s*:\s*['"`]([^'"`]+)['"`]/g;
@@ -5310,7 +5501,7 @@ function awsEndpointsFromFile(file, serviceDir) {
5310
5501
  // (ADR-066).
5311
5502
  confidenceKind: "verified-call-site",
5312
5503
  evidence: {
5313
- file: path27.relative(serviceDir, file.path),
5504
+ file: path28.relative(serviceDir, file.path),
5314
5505
  line,
5315
5506
  snippet: snippet(file.content, line)
5316
5507
  }
@@ -5334,7 +5525,7 @@ function awsEndpointsFromFile(file, serviceDir) {
5334
5525
  }
5335
5526
 
5336
5527
  // src/extract/calls/grpc.ts
5337
- import path28 from "path";
5528
+ import path29 from "path";
5338
5529
  import { infraId as infraId5 } from "@neat.is/types";
5339
5530
  var GRPC_CLIENT_RE = /new\s+([A-Z][A-Za-z0-9_]*)Client\s*\(\s*['"`]?([^,'"`)]+)?/g;
5340
5531
  var AWS_SDK_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@aws-sdk\/client-([a-z0-9-]+)['"`]/g;
@@ -5393,7 +5584,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
5393
5584
  // tier (ADR-066).
5394
5585
  confidenceKind: "verified-call-site",
5395
5586
  evidence: {
5396
- file: path28.relative(serviceDir, file.path),
5587
+ file: path29.relative(serviceDir, file.path),
5397
5588
  line,
5398
5589
  snippet: snippet(file.content, line)
5399
5590
  }
@@ -5403,7 +5594,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
5403
5594
  }
5404
5595
 
5405
5596
  // src/extract/calls/supabase.ts
5406
- import path29 from "path";
5597
+ import path30 from "path";
5407
5598
  import { infraId as infraId6 } from "@neat.is/types";
5408
5599
  var SUPABASE_JS_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@supabase\/supabase-js['"`]/;
5409
5600
  var SUPABASE_SSR_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@supabase\/ssr['"`]/;
@@ -5452,7 +5643,7 @@ function supabaseEndpointsFromFile(file, serviceDir) {
5452
5643
  // tier (ADR-066), the same grade aws.ts / grpc.ts emit at.
5453
5644
  confidenceKind: "verified-call-site",
5454
5645
  evidence: {
5455
- file: path29.relative(serviceDir, file.path),
5646
+ file: path30.relative(serviceDir, file.path),
5456
5647
  line,
5457
5648
  snippet: snippet(file.content, line)
5458
5649
  }
@@ -5465,11 +5656,11 @@ function supabaseEndpointsFromFile(file, serviceDir) {
5465
5656
  function edgeTypeFromEndpoint(ep) {
5466
5657
  switch (ep.edgeType) {
5467
5658
  case "PUBLISHES_TO":
5468
- return EdgeType11.PUBLISHES_TO;
5659
+ return EdgeType12.PUBLISHES_TO;
5469
5660
  case "CONSUMES_FROM":
5470
- return EdgeType11.CONSUMES_FROM;
5661
+ return EdgeType12.CONSUMES_FROM;
5471
5662
  default:
5472
- return EdgeType11.CALLS;
5663
+ return EdgeType12.CALLS;
5473
5664
  }
5474
5665
  }
5475
5666
  function isAwsKind(kind) {
@@ -5497,7 +5688,7 @@ async function addExternalEndpointEdges(graph, services) {
5497
5688
  if (!graph.hasNode(ep.infraId)) {
5498
5689
  const node = {
5499
5690
  id: ep.infraId,
5500
- type: NodeType11.InfraNode,
5691
+ type: NodeType12.InfraNode,
5501
5692
  name: ep.name,
5502
5693
  // #238 — `aws-*` covers AWS-SDK client kinds (aws-s3, aws-dynamodb,
5503
5694
  // aws-cognito-identity-provider, …); `s3-` / `dynamodb-` cover the
@@ -5509,7 +5700,7 @@ async function addExternalEndpointEdges(graph, services) {
5509
5700
  nodesAdded++;
5510
5701
  }
5511
5702
  const edgeType = edgeTypeFromEndpoint(ep);
5512
- const confidence = confidenceForExtracted8(ep.confidenceKind);
5703
+ const confidence = confidenceForExtracted9(ep.confidenceKind);
5513
5704
  const relFile = toPosix2(ep.evidence.file);
5514
5705
  const { fileNodeId, nodesAdded: n, edgesAdded: e } = ensureFileNode(
5515
5706
  graph,
@@ -5539,7 +5730,7 @@ async function addExternalEndpointEdges(graph, services) {
5539
5730
  source: fileNodeId,
5540
5731
  target: ep.infraId,
5541
5732
  type: edgeType,
5542
- provenance: Provenance10.EXTRACTED,
5733
+ provenance: Provenance11.EXTRACTED,
5543
5734
  confidence,
5544
5735
  evidence: ep.evidence
5545
5736
  };
@@ -5561,15 +5752,15 @@ async function addCallEdges(graph, services) {
5561
5752
  }
5562
5753
 
5563
5754
  // src/extract/infra/docker-compose.ts
5564
- import path30 from "path";
5565
- import { EdgeType as EdgeType12, Provenance as Provenance11, confidenceForExtracted as confidenceForExtracted9 } from "@neat.is/types";
5755
+ import path31 from "path";
5756
+ import { EdgeType as EdgeType13, Provenance as Provenance12, confidenceForExtracted as confidenceForExtracted10 } from "@neat.is/types";
5566
5757
 
5567
5758
  // src/extract/infra/shared.ts
5568
- import { NodeType as NodeType12, infraId as infraId7 } from "@neat.is/types";
5759
+ import { NodeType as NodeType13, infraId as infraId7 } from "@neat.is/types";
5569
5760
  function makeInfraNode(kind, name, provider = "self", extras) {
5570
5761
  return {
5571
5762
  id: infraId7(kind, name),
5572
- type: NodeType12.InfraNode,
5763
+ type: NodeType13.InfraNode,
5573
5764
  name,
5574
5765
  provider,
5575
5766
  kind,
@@ -5598,7 +5789,7 @@ function dependsOnList(value) {
5598
5789
  }
5599
5790
  function serviceNameToServiceNode(name, services) {
5600
5791
  for (const s of services) {
5601
- if (s.node.name === name || path30.basename(s.dir) === name) return s.node.id;
5792
+ if (s.node.name === name || path31.basename(s.dir) === name) return s.node.id;
5602
5793
  }
5603
5794
  return null;
5604
5795
  }
@@ -5607,7 +5798,7 @@ async function addComposeInfra(graph, scanPath, services) {
5607
5798
  let edgesAdded = 0;
5608
5799
  let composePath = null;
5609
5800
  for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
5610
- const abs = path30.join(scanPath, name);
5801
+ const abs = path31.join(scanPath, name);
5611
5802
  if (await exists(abs)) {
5612
5803
  composePath = abs;
5613
5804
  break;
@@ -5620,13 +5811,13 @@ async function addComposeInfra(graph, scanPath, services) {
5620
5811
  } catch (err) {
5621
5812
  recordExtractionError(
5622
5813
  "infra docker-compose",
5623
- path30.relative(scanPath, composePath),
5814
+ path31.relative(scanPath, composePath),
5624
5815
  err
5625
5816
  );
5626
5817
  return { nodesAdded, edgesAdded };
5627
5818
  }
5628
5819
  if (!compose?.services) return { nodesAdded, edgesAdded };
5629
- const evidenceFile = path30.relative(scanPath, composePath).split(path30.sep).join("/");
5820
+ const evidenceFile = path31.relative(scanPath, composePath).split(path31.sep).join("/");
5630
5821
  const composeNameToNodeId = /* @__PURE__ */ new Map();
5631
5822
  for (const [composeName, svc] of Object.entries(compose.services)) {
5632
5823
  const matchedServiceId = serviceNameToServiceNode(composeName, services);
@@ -5648,15 +5839,15 @@ async function addComposeInfra(graph, scanPath, services) {
5648
5839
  for (const dep of dependsOnList(svc.depends_on)) {
5649
5840
  const targetId = composeNameToNodeId.get(dep);
5650
5841
  if (!targetId) continue;
5651
- const edgeId = extractedEdgeId2(sourceId, targetId, EdgeType12.DEPENDS_ON);
5842
+ const edgeId = extractedEdgeId2(sourceId, targetId, EdgeType13.DEPENDS_ON);
5652
5843
  if (graph.hasEdge(edgeId)) continue;
5653
5844
  const edge = {
5654
5845
  id: edgeId,
5655
5846
  source: sourceId,
5656
5847
  target: targetId,
5657
- type: EdgeType12.DEPENDS_ON,
5658
- provenance: Provenance11.EXTRACTED,
5659
- confidence: confidenceForExtracted9("structural"),
5848
+ type: EdgeType13.DEPENDS_ON,
5849
+ provenance: Provenance12.EXTRACTED,
5850
+ confidence: confidenceForExtracted10("structural"),
5660
5851
  evidence: { file: evidenceFile }
5661
5852
  };
5662
5853
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -5667,9 +5858,9 @@ async function addComposeInfra(graph, scanPath, services) {
5667
5858
  }
5668
5859
 
5669
5860
  // src/extract/infra/dockerfile.ts
5670
- import path31 from "path";
5671
- import { promises as fs15 } from "fs";
5672
- import { EdgeType as EdgeType13, Provenance as Provenance12, confidenceForExtracted as confidenceForExtracted10 } from "@neat.is/types";
5861
+ import path32 from "path";
5862
+ import { promises as fs16 } from "fs";
5863
+ import { EdgeType as EdgeType14, Provenance as Provenance13, confidenceForExtracted as confidenceForExtracted11 } from "@neat.is/types";
5673
5864
  function readDockerfile(content) {
5674
5865
  let image = null;
5675
5866
  const ports = [];
@@ -5698,15 +5889,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
5698
5889
  let nodesAdded = 0;
5699
5890
  let edgesAdded = 0;
5700
5891
  for (const service of services) {
5701
- const dockerfilePath = path31.join(service.dir, "Dockerfile");
5892
+ const dockerfilePath = path32.join(service.dir, "Dockerfile");
5702
5893
  if (!await exists(dockerfilePath)) continue;
5703
5894
  let content;
5704
5895
  try {
5705
- content = await fs15.readFile(dockerfilePath, "utf8");
5896
+ content = await fs16.readFile(dockerfilePath, "utf8");
5706
5897
  } catch (err) {
5707
5898
  recordExtractionError(
5708
5899
  "infra dockerfile",
5709
- path31.relative(scanPath, dockerfilePath),
5900
+ path32.relative(scanPath, dockerfilePath),
5710
5901
  err
5711
5902
  );
5712
5903
  continue;
@@ -5718,8 +5909,8 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
5718
5909
  graph.addNode(node.id, node);
5719
5910
  nodesAdded++;
5720
5911
  }
5721
- const relDockerfile = toPosix2(path31.relative(service.dir, dockerfilePath));
5722
- const evidenceFile = toPosix2(path31.relative(scanPath, dockerfilePath));
5912
+ const relDockerfile = toPosix2(path32.relative(service.dir, dockerfilePath));
5913
+ const evidenceFile = toPosix2(path32.relative(scanPath, dockerfilePath));
5723
5914
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
5724
5915
  graph,
5725
5916
  service.pkg.name,
@@ -5728,15 +5919,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
5728
5919
  );
5729
5920
  nodesAdded += fn;
5730
5921
  edgesAdded += fe;
5731
- const edgeId = extractedEdgeId2(fileNodeId, node.id, EdgeType13.RUNS_ON);
5922
+ const edgeId = extractedEdgeId2(fileNodeId, node.id, EdgeType14.RUNS_ON);
5732
5923
  if (!graph.hasEdge(edgeId)) {
5733
5924
  const edge = {
5734
5925
  id: edgeId,
5735
5926
  source: fileNodeId,
5736
5927
  target: node.id,
5737
- type: EdgeType13.RUNS_ON,
5738
- provenance: Provenance12.EXTRACTED,
5739
- confidence: confidenceForExtracted10("structural"),
5928
+ type: EdgeType14.RUNS_ON,
5929
+ provenance: Provenance13.EXTRACTED,
5930
+ confidence: confidenceForExtracted11("structural"),
5740
5931
  evidence: {
5741
5932
  file: evidenceFile,
5742
5933
  ...facts.entrypoint ? { snippet: facts.entrypoint.slice(0, 120) } : {}
@@ -5751,15 +5942,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
5751
5942
  graph.addNode(portNode.id, portNode);
5752
5943
  nodesAdded++;
5753
5944
  }
5754
- const portEdgeId = extractedEdgeId2(fileNodeId, portNode.id, EdgeType13.CONNECTS_TO);
5945
+ const portEdgeId = extractedEdgeId2(fileNodeId, portNode.id, EdgeType14.CONNECTS_TO);
5755
5946
  if (graph.hasEdge(portEdgeId)) continue;
5756
5947
  const portEdge = {
5757
5948
  id: portEdgeId,
5758
5949
  source: fileNodeId,
5759
5950
  target: portNode.id,
5760
- type: EdgeType13.CONNECTS_TO,
5761
- provenance: Provenance12.EXTRACTED,
5762
- confidence: confidenceForExtracted10("structural"),
5951
+ type: EdgeType14.CONNECTS_TO,
5952
+ provenance: Provenance13.EXTRACTED,
5953
+ confidence: confidenceForExtracted11("structural"),
5763
5954
  evidence: { file: evidenceFile, snippet: `EXPOSE ${port}` }
5764
5955
  };
5765
5956
  graph.addEdgeWithKey(portEdgeId, portEdge.source, portEdge.target, portEdge);
@@ -5770,23 +5961,23 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
5770
5961
  }
5771
5962
 
5772
5963
  // src/extract/infra/terraform.ts
5773
- import { promises as fs16 } from "fs";
5774
- import path32 from "path";
5775
- import { EdgeType as EdgeType14, Provenance as Provenance13, confidenceForExtracted as confidenceForExtracted11 } from "@neat.is/types";
5964
+ import { promises as fs17 } from "fs";
5965
+ import path33 from "path";
5966
+ import { EdgeType as EdgeType15, Provenance as Provenance14, confidenceForExtracted as confidenceForExtracted12 } from "@neat.is/types";
5776
5967
  var RESOURCE_RE = /resource\s+"(aws_[A-Za-z0-9_]+)"\s+"([A-Za-z0-9_-]+)"/g;
5777
5968
  var REFERENCE_RE = /(?<![\w.])(aws_[A-Za-z0-9_]+)\.([A-Za-z0-9_-]+)/g;
5778
5969
  async function walkTfFiles(start, depth = 0, max = 5) {
5779
5970
  if (depth > max) return [];
5780
5971
  const out = [];
5781
- const entries = await fs16.readdir(start, { withFileTypes: true }).catch(() => []);
5972
+ const entries = await fs17.readdir(start, { withFileTypes: true }).catch(() => []);
5782
5973
  for (const entry of entries) {
5783
5974
  if (entry.isDirectory()) {
5784
5975
  if (IGNORED_DIRS.has(entry.name) || entry.name === ".terraform") continue;
5785
- const child = path32.join(start, entry.name);
5976
+ const child = path33.join(start, entry.name);
5786
5977
  if (await isPythonVenvDir(child)) continue;
5787
5978
  out.push(...await walkTfFiles(child, depth + 1, max));
5788
5979
  } else if (entry.isFile() && entry.name.endsWith(".tf")) {
5789
- out.push(path32.join(start, entry.name));
5980
+ out.push(path33.join(start, entry.name));
5790
5981
  }
5791
5982
  }
5792
5983
  return out;
@@ -5805,7 +5996,7 @@ function blockBody(content, from) {
5805
5996
  }
5806
5997
  return null;
5807
5998
  }
5808
- function lineAt(content, index) {
5999
+ function lineAt2(content, index) {
5809
6000
  let line = 1;
5810
6001
  for (let i = 0; i < index && i < content.length; i++) {
5811
6002
  if (content[i] === "\n") line++;
@@ -5817,8 +6008,8 @@ async function addTerraformResources(graph, scanPath) {
5817
6008
  let edgesAdded = 0;
5818
6009
  const files = await walkTfFiles(scanPath);
5819
6010
  for (const file of files) {
5820
- const content = await fs16.readFile(file, "utf8");
5821
- const evidenceFile = toPosix2(path32.relative(scanPath, file));
6011
+ const content = await fs17.readFile(file, "utf8");
6012
+ const evidenceFile = toPosix2(path33.relative(scanPath, file));
5822
6013
  const resources = [];
5823
6014
  const byKey = /* @__PURE__ */ new Map();
5824
6015
  RESOURCE_RE.lastIndex = 0;
@@ -5853,16 +6044,16 @@ async function addTerraformResources(graph, scanPath) {
5853
6044
  if (!target) continue;
5854
6045
  if (seen.has(target.nodeId)) continue;
5855
6046
  seen.add(target.nodeId);
5856
- const edgeId = extractedEdgeId2(resource.nodeId, target.nodeId, EdgeType14.DEPENDS_ON);
6047
+ const edgeId = extractedEdgeId2(resource.nodeId, target.nodeId, EdgeType15.DEPENDS_ON);
5857
6048
  if (graph.hasEdge(edgeId)) continue;
5858
- const line = lineAt(content, resource.bodyOffset + ref.index);
6049
+ const line = lineAt2(content, resource.bodyOffset + ref.index);
5859
6050
  const edge = {
5860
6051
  id: edgeId,
5861
6052
  source: resource.nodeId,
5862
6053
  target: target.nodeId,
5863
- type: EdgeType14.DEPENDS_ON,
5864
- provenance: Provenance13.EXTRACTED,
5865
- confidence: confidenceForExtracted11("structural"),
6054
+ type: EdgeType15.DEPENDS_ON,
6055
+ provenance: Provenance14.EXTRACTED,
6056
+ confidence: confidenceForExtracted12("structural"),
5866
6057
  evidence: { file: evidenceFile, line, snippet: key }
5867
6058
  };
5868
6059
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -5874,8 +6065,8 @@ async function addTerraformResources(graph, scanPath) {
5874
6065
  }
5875
6066
 
5876
6067
  // src/extract/infra/k8s.ts
5877
- import { promises as fs17 } from "fs";
5878
- import path33 from "path";
6068
+ import { promises as fs18 } from "fs";
6069
+ import path34 from "path";
5879
6070
  import { parseAllDocuments as parseAllDocuments2 } from "yaml";
5880
6071
  var K8S_KIND_TO_INFRA_KIND = {
5881
6072
  Service: "k8s-service",
@@ -5889,15 +6080,15 @@ var K8S_KIND_TO_INFRA_KIND = {
5889
6080
  async function walkYamlFiles2(start, depth = 0, max = 5) {
5890
6081
  if (depth > max) return [];
5891
6082
  const out = [];
5892
- const entries = await fs17.readdir(start, { withFileTypes: true }).catch(() => []);
6083
+ const entries = await fs18.readdir(start, { withFileTypes: true }).catch(() => []);
5893
6084
  for (const entry of entries) {
5894
6085
  if (entry.isDirectory()) {
5895
6086
  if (IGNORED_DIRS.has(entry.name)) continue;
5896
- const child = path33.join(start, entry.name);
6087
+ const child = path34.join(start, entry.name);
5897
6088
  if (await isPythonVenvDir(child)) continue;
5898
6089
  out.push(...await walkYamlFiles2(child, depth + 1, max));
5899
- } else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(path33.extname(entry.name))) {
5900
- out.push(path33.join(start, entry.name));
6090
+ } else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(path34.extname(entry.name))) {
6091
+ out.push(path34.join(start, entry.name));
5901
6092
  }
5902
6093
  }
5903
6094
  return out;
@@ -5906,7 +6097,7 @@ async function addK8sResources(graph, scanPath) {
5906
6097
  let nodesAdded = 0;
5907
6098
  const files = await walkYamlFiles2(scanPath);
5908
6099
  for (const file of files) {
5909
- const content = await fs17.readFile(file, "utf8");
6100
+ const content = await fs18.readFile(file, "utf8");
5910
6101
  let docs;
5911
6102
  try {
5912
6103
  docs = parseAllDocuments2(content).map((d) => d.toJSON());
@@ -5941,16 +6132,16 @@ async function addInfra(graph, scanPath, services) {
5941
6132
  }
5942
6133
 
5943
6134
  // src/extract/index.ts
5944
- import path35 from "path";
6135
+ import path36 from "path";
5945
6136
 
5946
6137
  // src/extract/retire.ts
5947
6138
  import { existsSync as existsSync2 } from "fs";
5948
- import path34 from "path";
5949
- import { NodeType as NodeType13, Provenance as Provenance14 } from "@neat.is/types";
6139
+ import path35 from "path";
6140
+ import { NodeType as NodeType14, Provenance as Provenance15 } from "@neat.is/types";
5950
6141
  function dropOrphanedFileNodes(graph) {
5951
6142
  const orphans = [];
5952
6143
  graph.forEachNode((id, attrs) => {
5953
- if (attrs.type !== NodeType13.FileNode) return;
6144
+ if (attrs.type !== NodeType14.FileNode) return;
5954
6145
  if (graph.inboundEdges(id).length === 0 && graph.outboundEdges(id).length === 0) {
5955
6146
  orphans.push(id);
5956
6147
  }
@@ -5963,7 +6154,7 @@ function retireEdgesByFile(graph, file) {
5963
6154
  const toDrop = [];
5964
6155
  graph.forEachEdge((id, attrs) => {
5965
6156
  const edge = attrs;
5966
- if (edge.provenance !== Provenance14.EXTRACTED) return;
6157
+ if (edge.provenance !== Provenance15.EXTRACTED) return;
5967
6158
  if (!edge.evidence?.file) return;
5968
6159
  if (edge.evidence.file === normalized) toDrop.push(id);
5969
6160
  });
@@ -5976,14 +6167,14 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
5976
6167
  const bases = [scanPath, ...serviceDirs];
5977
6168
  graph.forEachEdge((id, attrs) => {
5978
6169
  const edge = attrs;
5979
- if (edge.provenance !== Provenance14.EXTRACTED) return;
6170
+ if (edge.provenance !== Provenance15.EXTRACTED) return;
5980
6171
  const evidenceFile = edge.evidence?.file;
5981
6172
  if (!evidenceFile) return;
5982
- if (path34.isAbsolute(evidenceFile)) {
6173
+ if (path35.isAbsolute(evidenceFile)) {
5983
6174
  if (!existsSync2(evidenceFile)) toDrop.push(id);
5984
6175
  return;
5985
6176
  }
5986
- const found = bases.some((base) => existsSync2(path34.join(base, evidenceFile)));
6177
+ const found = bases.some((base) => existsSync2(path35.join(base, evidenceFile)));
5987
6178
  if (!found) toDrop.push(id);
5988
6179
  });
5989
6180
  for (const id of toDrop) graph.dropEdge(id);
@@ -6003,6 +6194,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
6003
6194
  const phase2 = await addDatabasesAndCompat(graph, services, scanPath);
6004
6195
  const phase3 = await addConfigNodes(graph, services, scanPath);
6005
6196
  const routePhase = await addRoutes(graph, services);
6197
+ const grpcPhase = await addGrpcMethods(graph, services);
6006
6198
  const phase4 = await addCallEdges(graph, services);
6007
6199
  const phase5 = await addInfra(graph, scanPath, services);
6008
6200
  const ghostsRetired = retireExtractedEdgesByMissingFile(
@@ -6024,7 +6216,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
6024
6216
  }
6025
6217
  const droppedEntries = drainDroppedExtracted();
6026
6218
  if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
6027
- const rejectedPath = path35.join(path35.dirname(opts.errorsPath), "rejected.ndjson");
6219
+ const rejectedPath = path36.join(path36.dirname(opts.errorsPath), "rejected.ndjson");
6028
6220
  try {
6029
6221
  await writeRejectedExtracted(droppedEntries, rejectedPath);
6030
6222
  } catch (err) {
@@ -6034,8 +6226,8 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
6034
6226
  }
6035
6227
  }
6036
6228
  const result = {
6037
- nodesAdded: phase1Nodes + fileEnum.nodesAdded + importGraph.nodesAdded + phase2.nodesAdded + phase3.nodesAdded + routePhase.nodesAdded + phase4.nodesAdded + phase5.nodesAdded,
6038
- edgesAdded: fileEnum.edgesAdded + importGraph.edgesAdded + phase2.edgesAdded + phase3.edgesAdded + routePhase.edgesAdded + phase4.edgesAdded + phase5.edgesAdded,
6229
+ nodesAdded: phase1Nodes + fileEnum.nodesAdded + importGraph.nodesAdded + phase2.nodesAdded + phase3.nodesAdded + routePhase.nodesAdded + grpcPhase.nodesAdded + phase4.nodesAdded + phase5.nodesAdded,
6230
+ edgesAdded: fileEnum.edgesAdded + importGraph.edgesAdded + phase2.edgesAdded + phase3.edgesAdded + routePhase.edgesAdded + grpcPhase.edgesAdded + phase4.edgesAdded + phase5.edgesAdded,
6039
6231
  frontiersPromoted,
6040
6232
  extractionErrors: errorEntries.length,
6041
6233
  errorEntries,
@@ -6060,10 +6252,10 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
6060
6252
  import {
6061
6253
  databaseId as databaseId3,
6062
6254
  DivergenceResultSchema,
6063
- EdgeType as EdgeType15,
6064
- NodeType as NodeType14,
6255
+ EdgeType as EdgeType16,
6256
+ NodeType as NodeType15,
6065
6257
  parseEdgeId,
6066
- Provenance as Provenance15
6258
+ Provenance as Provenance16
6067
6259
  } from "@neat.is/types";
6068
6260
  function bucketKey(source, target, type) {
6069
6261
  return `${type}|${source}|${target}`;
@@ -6077,17 +6269,17 @@ function bucketEdges(graph) {
6077
6269
  const key = bucketKey(e.source, e.target, e.type);
6078
6270
  const cur = buckets.get(key) ?? { source: e.source, target: e.target, type: e.type };
6079
6271
  switch (provenance) {
6080
- case Provenance15.EXTRACTED:
6272
+ case Provenance16.EXTRACTED:
6081
6273
  cur.extracted = e;
6082
6274
  break;
6083
- case Provenance15.OBSERVED:
6275
+ case Provenance16.OBSERVED:
6084
6276
  cur.observed = e;
6085
6277
  break;
6086
- case Provenance15.INFERRED:
6278
+ case Provenance16.INFERRED:
6087
6279
  cur.inferred = e;
6088
6280
  break;
6089
6281
  default:
6090
- if (e.provenance === Provenance15.STALE) cur.stale = e;
6282
+ if (e.provenance === Provenance16.STALE) cur.stale = e;
6091
6283
  }
6092
6284
  buckets.set(key, cur);
6093
6285
  });
@@ -6096,7 +6288,12 @@ function bucketEdges(graph) {
6096
6288
  function nodeIsFrontier(graph, nodeId) {
6097
6289
  if (!graph.hasNode(nodeId)) return false;
6098
6290
  const attrs = graph.getNodeAttributes(nodeId);
6099
- return attrs.type === NodeType14.FrontierNode;
6291
+ return attrs.type === NodeType15.FrontierNode;
6292
+ }
6293
+ function nodeIsWebsocketChannel(graph, nodeId) {
6294
+ if (!graph.hasNode(nodeId)) return false;
6295
+ const attrs = graph.getNodeAttributes(nodeId);
6296
+ return attrs.type === NodeType15.WebSocketChannelNode;
6100
6297
  }
6101
6298
  function clampConfidence(n) {
6102
6299
  if (!Number.isFinite(n)) return 0;
@@ -6116,14 +6313,14 @@ function gradedConfidence(edge) {
6116
6313
  return clampConfidence(confidenceForEdge(edge));
6117
6314
  }
6118
6315
  var OBSERVABLE_EDGE_TYPES = /* @__PURE__ */ new Set([
6119
- EdgeType15.CALLS,
6120
- EdgeType15.CONNECTS_TO,
6121
- EdgeType15.PUBLISHES_TO,
6122
- EdgeType15.CONSUMES_FROM
6316
+ EdgeType16.CALLS,
6317
+ EdgeType16.CONNECTS_TO,
6318
+ EdgeType16.PUBLISHES_TO,
6319
+ EdgeType16.CONSUMES_FROM
6123
6320
  ]);
6124
6321
  function detectMissingDivergences(graph, bucket) {
6125
6322
  const out = [];
6126
- if (bucket.type === EdgeType15.CONTAINS) return out;
6323
+ if (bucket.type === EdgeType16.CONTAINS) return out;
6127
6324
  if (bucket.extracted && !bucket.observed && OBSERVABLE_EDGE_TYPES.has(bucket.type)) {
6128
6325
  if (!nodeIsFrontier(graph, bucket.target)) {
6129
6326
  out.push({
@@ -6138,7 +6335,7 @@ function detectMissingDivergences(graph, bucket) {
6138
6335
  });
6139
6336
  }
6140
6337
  }
6141
- if (bucket.observed && !bucket.extracted) {
6338
+ if (bucket.observed && !bucket.extracted && !nodeIsWebsocketChannel(graph, bucket.target)) {
6142
6339
  out.push({
6143
6340
  type: "missing-extracted",
6144
6341
  source: bucket.source,
@@ -6164,7 +6361,7 @@ function declaredHostFor(svc) {
6164
6361
  function hasExtractedConfiguredBy(graph, svcId) {
6165
6362
  for (const edgeId of graph.outboundEdges(svcId)) {
6166
6363
  const e = graph.getEdgeAttributes(edgeId);
6167
- if (e.type === EdgeType15.CONFIGURED_BY && e.provenance === Provenance15.EXTRACTED) {
6364
+ if (e.type === EdgeType16.CONFIGURED_BY && e.provenance === Provenance16.EXTRACTED) {
6168
6365
  return true;
6169
6366
  }
6170
6367
  }
@@ -6177,10 +6374,10 @@ function detectHostMismatch(graph, svcId, svc) {
6177
6374
  const out = [];
6178
6375
  for (const edgeId of graph.outboundEdges(svcId)) {
6179
6376
  const edge = graph.getEdgeAttributes(edgeId);
6180
- if (edge.type !== EdgeType15.CONNECTS_TO) continue;
6181
- if (edge.provenance !== Provenance15.OBSERVED) continue;
6377
+ if (edge.type !== EdgeType16.CONNECTS_TO) continue;
6378
+ if (edge.provenance !== Provenance16.OBSERVED) continue;
6182
6379
  const target = graph.getNodeAttributes(edge.target);
6183
- if (target.type !== NodeType14.DatabaseNode) continue;
6380
+ if (target.type !== NodeType15.DatabaseNode) continue;
6184
6381
  const observedHost = target.host?.trim();
6185
6382
  if (!observedHost) continue;
6186
6383
  if (observedHost === declaredHost) continue;
@@ -6202,10 +6399,10 @@ function detectCompatDivergences(graph, svcId, svc) {
6202
6399
  const deps = svc.dependencies ?? {};
6203
6400
  for (const edgeId of graph.outboundEdges(svcId)) {
6204
6401
  const edge = graph.getEdgeAttributes(edgeId);
6205
- if (edge.type !== EdgeType15.CONNECTS_TO) continue;
6206
- if (edge.provenance !== Provenance15.OBSERVED) continue;
6402
+ if (edge.type !== EdgeType16.CONNECTS_TO) continue;
6403
+ if (edge.provenance !== Provenance16.OBSERVED) continue;
6207
6404
  const target = graph.getNodeAttributes(edge.target);
6208
- if (target.type !== NodeType14.DatabaseNode) continue;
6405
+ if (target.type !== NodeType15.DatabaseNode) continue;
6209
6406
  for (const pair of compatPairs()) {
6210
6407
  if (pair.engine !== target.engine) continue;
6211
6408
  const declared = deps[pair.driver];
@@ -6283,7 +6480,7 @@ function computeDivergences(graph, opts = {}) {
6283
6480
  }
6284
6481
  graph.forEachNode((nodeId, attrs) => {
6285
6482
  const n = attrs;
6286
- if (n.type !== NodeType14.ServiceNode) return;
6483
+ if (n.type !== NodeType15.ServiceNode) return;
6287
6484
  const svc = n;
6288
6485
  for (const d of detectHostMismatch(graph, nodeId, svc)) all.push(d);
6289
6486
  for (const d of detectCompatDivergences(graph, nodeId, svc)) all.push(d);
@@ -6325,9 +6522,9 @@ function computeDivergences(graph, opts = {}) {
6325
6522
  }
6326
6523
 
6327
6524
  // src/persist.ts
6328
- import { promises as fs18 } from "fs";
6329
- import path36 from "path";
6330
- import { Provenance as Provenance16, observedEdgeId as observedEdgeId2 } from "@neat.is/types";
6525
+ import { promises as fs19 } from "fs";
6526
+ import path37 from "path";
6527
+ import { Provenance as Provenance17, observedEdgeId as observedEdgeId2 } from "@neat.is/types";
6331
6528
  var SCHEMA_VERSION = 4;
6332
6529
  function migrateV1ToV2(payload) {
6333
6530
  const nodes = payload.graph.nodes;
@@ -6349,7 +6546,7 @@ function migrateV2ToV3(payload) {
6349
6546
  for (const edge of edges) {
6350
6547
  const attrs = edge.attributes;
6351
6548
  if (!attrs || attrs.provenance !== "FRONTIER") continue;
6352
- attrs.provenance = Provenance16.OBSERVED;
6549
+ attrs.provenance = Provenance17.OBSERVED;
6353
6550
  const type = typeof attrs.type === "string" ? attrs.type : void 0;
6354
6551
  const source = typeof attrs.source === "string" ? attrs.source : void 0;
6355
6552
  const target = typeof attrs.target === "string" ? attrs.target : void 0;
@@ -6363,7 +6560,7 @@ function migrateV2ToV3(payload) {
6363
6560
  return { ...payload, schemaVersion: 3 };
6364
6561
  }
6365
6562
  async function ensureDir(filePath) {
6366
- await fs18.mkdir(path36.dirname(filePath), { recursive: true });
6563
+ await fs19.mkdir(path37.dirname(filePath), { recursive: true });
6367
6564
  }
6368
6565
  async function saveGraphToDisk(graph, outPath) {
6369
6566
  await ensureDir(outPath);
@@ -6373,13 +6570,13 @@ async function saveGraphToDisk(graph, outPath) {
6373
6570
  graph: graph.export()
6374
6571
  };
6375
6572
  const tmp = `${outPath}.tmp`;
6376
- await fs18.writeFile(tmp, JSON.stringify(payload), "utf8");
6377
- await fs18.rename(tmp, outPath);
6573
+ await fs19.writeFile(tmp, JSON.stringify(payload), "utf8");
6574
+ await fs19.rename(tmp, outPath);
6378
6575
  }
6379
6576
  async function loadGraphFromDisk(graph, outPath) {
6380
6577
  let raw;
6381
6578
  try {
6382
- raw = await fs18.readFile(outPath, "utf8");
6579
+ raw = await fs19.readFile(outPath, "utf8");
6383
6580
  } catch (err) {
6384
6581
  if (err.code === "ENOENT") return;
6385
6582
  throw err;
@@ -6443,7 +6640,7 @@ function startPersistLoop(graph, outPath, opts = {}) {
6443
6640
  }
6444
6641
 
6445
6642
  // src/diff.ts
6446
- import { promises as fs19 } from "fs";
6643
+ import { promises as fs20 } from "fs";
6447
6644
  async function loadSnapshotForDiff(target) {
6448
6645
  if (/^https?:\/\//i.test(target)) {
6449
6646
  const res = await fetch(target);
@@ -6452,7 +6649,7 @@ async function loadSnapshotForDiff(target) {
6452
6649
  }
6453
6650
  return await res.json();
6454
6651
  }
6455
- const raw = await fs19.readFile(target, "utf8");
6652
+ const raw = await fs20.readFile(target, "utf8");
6456
6653
  return JSON.parse(raw);
6457
6654
  }
6458
6655
  function indexEntries(entries) {
@@ -6519,23 +6716,23 @@ function canonicalJson(value) {
6519
6716
  }
6520
6717
 
6521
6718
  // src/projects.ts
6522
- import path37 from "path";
6719
+ import path38 from "path";
6523
6720
  function pathsForProject(project, baseDir) {
6524
6721
  if (project === DEFAULT_PROJECT) {
6525
6722
  return {
6526
- snapshotPath: path37.join(baseDir, "graph.json"),
6527
- errorsPath: path37.join(baseDir, "errors.ndjson"),
6528
- staleEventsPath: path37.join(baseDir, "stale-events.ndjson"),
6529
- embeddingsCachePath: path37.join(baseDir, "embeddings.json"),
6530
- policyViolationsPath: path37.join(baseDir, "policy-violations.ndjson")
6723
+ snapshotPath: path38.join(baseDir, "graph.json"),
6724
+ errorsPath: path38.join(baseDir, "errors.ndjson"),
6725
+ staleEventsPath: path38.join(baseDir, "stale-events.ndjson"),
6726
+ embeddingsCachePath: path38.join(baseDir, "embeddings.json"),
6727
+ policyViolationsPath: path38.join(baseDir, "policy-violations.ndjson")
6531
6728
  };
6532
6729
  }
6533
6730
  return {
6534
- snapshotPath: path37.join(baseDir, `${project}.json`),
6535
- errorsPath: path37.join(baseDir, `errors.${project}.ndjson`),
6536
- staleEventsPath: path37.join(baseDir, `stale-events.${project}.ndjson`),
6537
- embeddingsCachePath: path37.join(baseDir, `embeddings.${project}.json`),
6538
- policyViolationsPath: path37.join(baseDir, `policy-violations.${project}.ndjson`)
6731
+ snapshotPath: path38.join(baseDir, `${project}.json`),
6732
+ errorsPath: path38.join(baseDir, `errors.${project}.ndjson`),
6733
+ staleEventsPath: path38.join(baseDir, `stale-events.${project}.ndjson`),
6734
+ embeddingsCachePath: path38.join(baseDir, `embeddings.${project}.json`),
6735
+ policyViolationsPath: path38.join(baseDir, `policy-violations.${project}.ndjson`)
6539
6736
  };
6540
6737
  }
6541
6738
  var Projects = class {
@@ -6574,9 +6771,9 @@ function parseExtraProjects(raw) {
6574
6771
  }
6575
6772
 
6576
6773
  // src/registry.ts
6577
- import { promises as fs20 } from "fs";
6774
+ import { promises as fs21 } from "fs";
6578
6775
  import os2 from "os";
6579
- import path38 from "path";
6776
+ import path39 from "path";
6580
6777
  import {
6581
6778
  RegistryFileSchema
6582
6779
  } from "@neat.is/types";
@@ -6584,20 +6781,20 @@ var LOCK_TIMEOUT_MS = 5e3;
6584
6781
  var LOCK_RETRY_MS = 50;
6585
6782
  function neatHome() {
6586
6783
  const override = process.env.NEAT_HOME;
6587
- if (override && override.length > 0) return path38.resolve(override);
6588
- return path38.join(os2.homedir(), ".neat");
6784
+ if (override && override.length > 0) return path39.resolve(override);
6785
+ return path39.join(os2.homedir(), ".neat");
6589
6786
  }
6590
6787
  function registryPath() {
6591
- return path38.join(neatHome(), "projects.json");
6788
+ return path39.join(neatHome(), "projects.json");
6592
6789
  }
6593
6790
  function registryLockPath() {
6594
- return path38.join(neatHome(), "projects.json.lock");
6791
+ return path39.join(neatHome(), "projects.json.lock");
6595
6792
  }
6596
6793
  function daemonPidPath() {
6597
- return path38.join(neatHome(), "neatd.pid");
6794
+ return path39.join(neatHome(), "neatd.pid");
6598
6795
  }
6599
6796
  function daemonsDir() {
6600
- return path38.join(neatHome(), "daemons");
6797
+ return path39.join(neatHome(), "daemons");
6601
6798
  }
6602
6799
  function isFiniteInt(v) {
6603
6800
  return typeof v === "number" && Number.isFinite(v);
@@ -6630,7 +6827,7 @@ async function discoverDaemons(probe = defaultDiscoveryProbe) {
6630
6827
  const dir = daemonsDir();
6631
6828
  let names;
6632
6829
  try {
6633
- names = await fs20.readdir(dir);
6830
+ names = await fs21.readdir(dir);
6634
6831
  } catch (err) {
6635
6832
  if (err.code === "ENOENT") return [];
6636
6833
  throw err;
@@ -6638,10 +6835,10 @@ async function discoverDaemons(probe = defaultDiscoveryProbe) {
6638
6835
  const out = [];
6639
6836
  for (const name of names) {
6640
6837
  if (!name.endsWith(".json")) continue;
6641
- const file = path38.join(dir, name);
6838
+ const file = path39.join(dir, name);
6642
6839
  let raw;
6643
6840
  try {
6644
- raw = await fs20.readFile(file, "utf8");
6841
+ raw = await fs21.readFile(file, "utf8");
6645
6842
  } catch {
6646
6843
  continue;
6647
6844
  }
@@ -6654,7 +6851,7 @@ async function discoverDaemons(probe = defaultDiscoveryProbe) {
6654
6851
  return out;
6655
6852
  }
6656
6853
  async function removeDaemonRecord(source) {
6657
- await fs20.unlink(source).catch(() => {
6854
+ await fs21.unlink(source).catch(() => {
6658
6855
  });
6659
6856
  }
6660
6857
  async function listMachineProjects(probe = defaultDiscoveryProbe) {
@@ -6711,7 +6908,7 @@ function isPidAliveDefault(pid) {
6711
6908
  }
6712
6909
  async function readPidFile(file) {
6713
6910
  try {
6714
- const raw = await fs20.readFile(file, "utf8");
6911
+ const raw = await fs21.readFile(file, "utf8");
6715
6912
  const pid = Number.parseInt(raw.trim(), 10);
6716
6913
  return Number.isInteger(pid) && pid > 0 ? pid : void 0;
6717
6914
  } catch {
@@ -6759,32 +6956,32 @@ function lockHolderMessage(holder, lockPath, timeoutMs) {
6759
6956
  }
6760
6957
  }
6761
6958
  async function normalizeProjectPath(input) {
6762
- const resolved = path38.resolve(input);
6959
+ const resolved = path39.resolve(input);
6763
6960
  try {
6764
- return await fs20.realpath(resolved);
6961
+ return await fs21.realpath(resolved);
6765
6962
  } catch {
6766
6963
  return resolved;
6767
6964
  }
6768
6965
  }
6769
6966
  async function writeAtomically(target, contents) {
6770
- await fs20.mkdir(path38.dirname(target), { recursive: true });
6967
+ await fs21.mkdir(path39.dirname(target), { recursive: true });
6771
6968
  const tmp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
6772
- const fd = await fs20.open(tmp, "w");
6969
+ const fd = await fs21.open(tmp, "w");
6773
6970
  try {
6774
6971
  await fd.writeFile(contents, "utf8");
6775
6972
  await fd.sync();
6776
6973
  } finally {
6777
6974
  await fd.close();
6778
6975
  }
6779
- await fs20.rename(tmp, target);
6976
+ await fs21.rename(tmp, target);
6780
6977
  }
6781
6978
  async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaultLockHolderProbe) {
6782
6979
  const deadline = Date.now() + timeoutMs;
6783
- await fs20.mkdir(path38.dirname(lockPath), { recursive: true });
6980
+ await fs21.mkdir(path39.dirname(lockPath), { recursive: true });
6784
6981
  let probedHolder = false;
6785
6982
  while (true) {
6786
6983
  try {
6787
- const fd = await fs20.open(lockPath, "wx");
6984
+ const fd = await fs21.open(lockPath, "wx");
6788
6985
  try {
6789
6986
  await fd.writeFile(`${process.pid}
6790
6987
  `, "utf8");
@@ -6809,7 +7006,7 @@ async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaul
6809
7006
  }
6810
7007
  }
6811
7008
  async function releaseLock(lockPath) {
6812
- await fs20.unlink(lockPath).catch(() => {
7009
+ await fs21.unlink(lockPath).catch(() => {
6813
7010
  });
6814
7011
  }
6815
7012
  async function withLock(fn) {
@@ -6825,7 +7022,7 @@ async function readRegistry() {
6825
7022
  const file = registryPath();
6826
7023
  let raw;
6827
7024
  try {
6828
- raw = await fs20.readFile(file, "utf8");
7025
+ raw = await fs21.readFile(file, "utf8");
6829
7026
  } catch (err) {
6830
7027
  if (err.code === "ENOENT") {
6831
7028
  return { version: 1, projects: [] };
@@ -6930,7 +7127,7 @@ function pruneTtlMs() {
6930
7127
  }
6931
7128
  async function statPathStatus(p) {
6932
7129
  try {
6933
- const stat = await fs20.stat(p);
7130
+ const stat = await fs21.stat(p);
6934
7131
  return stat.isDirectory() ? "present" : "unknown";
6935
7132
  } catch (err) {
6936
7133
  return err.code === "ENOENT" ? "gone" : "unknown";
@@ -6975,14 +7172,14 @@ import cors from "@fastify/cors";
6975
7172
  import { DivergenceTypeSchema, PoliciesCheckBodySchema, PolicySeveritySchema } from "@neat.is/types";
6976
7173
 
6977
7174
  // src/extend/index.ts
6978
- import { promises as fs22 } from "fs";
6979
- import path40 from "path";
7175
+ import { promises as fs23 } from "fs";
7176
+ import path41 from "path";
6980
7177
  import os3 from "os";
6981
7178
  import { resolve as registryResolve, list as registryList } from "@neat.is/instrumentation-registry";
6982
7179
 
6983
7180
  // src/installers/package-manager.ts
6984
- import { promises as fs21 } from "fs";
6985
- import path39 from "path";
7181
+ import { promises as fs22 } from "fs";
7182
+ import path40 from "path";
6986
7183
  import { spawn } from "child_process";
6987
7184
  var LOCKFILE_PRIORITY = [
6988
7185
  { lockfile: "bun.lockb", pm: "bun", args: ["install", "--no-summary"] },
@@ -6997,29 +7194,29 @@ var LOCKFILE_PRIORITY = [
6997
7194
  var NPM_FALLBACK_ARGS = ["install", "--no-audit", "--no-fund", "--prefer-offline"];
6998
7195
  async function exists2(p) {
6999
7196
  try {
7000
- await fs21.access(p);
7197
+ await fs22.access(p);
7001
7198
  return true;
7002
7199
  } catch {
7003
7200
  return false;
7004
7201
  }
7005
7202
  }
7006
7203
  async function detectPackageManager(serviceDir) {
7007
- let dir = path39.resolve(serviceDir);
7204
+ let dir = path40.resolve(serviceDir);
7008
7205
  const stops = /* @__PURE__ */ new Set();
7009
7206
  for (let i = 0; i < 64; i++) {
7010
7207
  if (stops.has(dir)) break;
7011
7208
  stops.add(dir);
7012
7209
  for (const candidate of LOCKFILE_PRIORITY) {
7013
- const lockPath = path39.join(dir, candidate.lockfile);
7210
+ const lockPath = path40.join(dir, candidate.lockfile);
7014
7211
  if (await exists2(lockPath)) {
7015
7212
  return { pm: candidate.pm, cwd: dir, args: [...candidate.args] };
7016
7213
  }
7017
7214
  }
7018
- const parent = path39.dirname(dir);
7215
+ const parent = path40.dirname(dir);
7019
7216
  if (parent === dir) break;
7020
7217
  dir = parent;
7021
7218
  }
7022
- return { pm: "npm", cwd: path39.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
7219
+ return { pm: "npm", cwd: path40.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
7023
7220
  }
7024
7221
  async function runPackageManagerInstall(cmd) {
7025
7222
  return new Promise((resolve) => {
@@ -7061,30 +7258,30 @@ ${err.message}`
7061
7258
  // src/extend/index.ts
7062
7259
  async function fileExists2(p) {
7063
7260
  try {
7064
- await fs22.access(p);
7261
+ await fs23.access(p);
7065
7262
  return true;
7066
7263
  } catch {
7067
7264
  return false;
7068
7265
  }
7069
7266
  }
7070
7267
  async function readPackageJson(scanPath) {
7071
- const pkgPath = path40.join(scanPath, "package.json");
7072
- const raw = await fs22.readFile(pkgPath, "utf8");
7268
+ const pkgPath = path41.join(scanPath, "package.json");
7269
+ const raw = await fs23.readFile(pkgPath, "utf8");
7073
7270
  return JSON.parse(raw);
7074
7271
  }
7075
7272
  async function findHookFiles(scanPath) {
7076
- const entries = await fs22.readdir(scanPath);
7273
+ const entries = await fs23.readdir(scanPath);
7077
7274
  return entries.filter(
7078
7275
  (e) => (e.startsWith("instrumentation") || e.startsWith("otel-init")) && /\.(ts|js)$/.test(e)
7079
7276
  ).sort();
7080
7277
  }
7081
7278
  function extendLogPath() {
7082
- return process.env.NEAT_EXTEND_LOG ?? path40.join(os3.homedir(), ".neat", "extend-log.ndjson");
7279
+ return process.env.NEAT_EXTEND_LOG ?? path41.join(os3.homedir(), ".neat", "extend-log.ndjson");
7083
7280
  }
7084
7281
  async function appendExtendLog(entry) {
7085
7282
  const logPath = extendLogPath();
7086
- await fs22.mkdir(path40.dirname(logPath), { recursive: true });
7087
- await fs22.appendFile(logPath, JSON.stringify(entry) + "\n", "utf8");
7283
+ await fs23.mkdir(path41.dirname(logPath), { recursive: true });
7284
+ await fs23.appendFile(logPath, JSON.stringify(entry) + "\n", "utf8");
7088
7285
  }
7089
7286
  function splicedContent(fileContent, snippet2) {
7090
7287
  if (fileContent.includes("__INSTRUMENTATION_BLOCK__")) {
@@ -7142,7 +7339,7 @@ function lookupInstrumentation(library, installedVersion) {
7142
7339
  }
7143
7340
  async function describeProjectInstrumentation(ctx) {
7144
7341
  const hookFiles = await findHookFiles(ctx.scanPath);
7145
- const envNeat = await fileExists2(path40.join(ctx.scanPath, ".env.neat"));
7342
+ const envNeat = await fileExists2(path41.join(ctx.scanPath, ".env.neat"));
7146
7343
  const registryInstrPackages = new Set(
7147
7344
  registryList().map((e) => e.instrumentation_package).filter((p) => !!p)
7148
7345
  );
@@ -7164,31 +7361,31 @@ async function applyExtension(ctx, args, options) {
7164
7361
  );
7165
7362
  }
7166
7363
  for (const file of hookFiles) {
7167
- const content = await fs22.readFile(path40.join(ctx.scanPath, file), "utf8");
7364
+ const content = await fs23.readFile(path41.join(ctx.scanPath, file), "utf8");
7168
7365
  if (content.includes(args.registration_snippet)) {
7169
7366
  return { library: args.library, filesTouched: [], depsAdded: [], installOutput: "", alreadyApplied: true };
7170
7367
  }
7171
7368
  }
7172
7369
  const primaryFile = hookFiles[0];
7173
- const primaryPath = path40.join(ctx.scanPath, primaryFile);
7370
+ const primaryPath = path41.join(ctx.scanPath, primaryFile);
7174
7371
  const filesTouched = [];
7175
7372
  const depsAdded = [];
7176
- const pkgPath = path40.join(ctx.scanPath, "package.json");
7373
+ const pkgPath = path41.join(ctx.scanPath, "package.json");
7177
7374
  const pkg = await readPackageJson(ctx.scanPath);
7178
7375
  if (!(pkg.dependencies ?? {})[args.instrumentation_package]) {
7179
7376
  pkg.dependencies = { ...pkg.dependencies ?? {}, [args.instrumentation_package]: args.version };
7180
- await fs22.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
7377
+ await fs23.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
7181
7378
  filesTouched.push("package.json");
7182
7379
  depsAdded.push(`${args.instrumentation_package}@${args.version}`);
7183
7380
  }
7184
- const hookContent = await fs22.readFile(primaryPath, "utf8");
7381
+ const hookContent = await fs23.readFile(primaryPath, "utf8");
7185
7382
  const patched = splicedContent(hookContent, args.registration_snippet);
7186
7383
  if (!patched) {
7187
7384
  throw new Error(
7188
7385
  `Could not find instrumentation insertion point in ${primaryFile}. Expected __INSTRUMENTATION_BLOCK__, instrumentations.push(, or new NodeSDK(.`
7189
7386
  );
7190
7387
  }
7191
- await fs22.writeFile(primaryPath, patched, "utf8");
7388
+ await fs23.writeFile(primaryPath, patched, "utf8");
7192
7389
  filesTouched.push(primaryFile);
7193
7390
  const cmd = await detectPackageManager(ctx.scanPath);
7194
7391
  const installer = options?.runInstall ?? runPackageManagerInstall;
@@ -7219,7 +7416,7 @@ async function dryRunExtension(ctx, args) {
7219
7416
  };
7220
7417
  }
7221
7418
  for (const file of hookFiles) {
7222
- const content = await fs22.readFile(path40.join(ctx.scanPath, file), "utf8");
7419
+ const content = await fs23.readFile(path41.join(ctx.scanPath, file), "utf8");
7223
7420
  if (content.includes(args.registration_snippet)) {
7224
7421
  return {
7225
7422
  library: args.library,
@@ -7241,7 +7438,7 @@ async function dryRunExtension(ctx, args) {
7241
7438
  depsToAdd.push(`${args.instrumentation_package}@${args.version}`);
7242
7439
  filesTouched.push("package.json");
7243
7440
  }
7244
- const hookContent = await fs22.readFile(path40.join(ctx.scanPath, primaryFile), "utf8");
7441
+ const hookContent = await fs23.readFile(path41.join(ctx.scanPath, primaryFile), "utf8");
7245
7442
  const patched = splicedContent(hookContent, args.registration_snippet);
7246
7443
  if (patched) {
7247
7444
  filesTouched.push(primaryFile);
@@ -7256,28 +7453,28 @@ async function rollbackExtension(ctx, args) {
7256
7453
  if (!await fileExists2(logPath)) {
7257
7454
  return { undone: false, message: "no apply found for library" };
7258
7455
  }
7259
- const raw = await fs22.readFile(logPath, "utf8");
7456
+ const raw = await fs23.readFile(logPath, "utf8");
7260
7457
  const entries = raw.trim().split("\n").filter(Boolean).map((line) => JSON.parse(line));
7261
7458
  const match = [...entries].reverse().find((e) => e.project === ctx.project && e.library === args.library);
7262
7459
  if (!match) {
7263
7460
  return { undone: false, message: "no apply found for library" };
7264
7461
  }
7265
- const pkgPath = path40.join(ctx.scanPath, "package.json");
7462
+ const pkgPath = path41.join(ctx.scanPath, "package.json");
7266
7463
  if (await fileExists2(pkgPath)) {
7267
7464
  const pkg = await readPackageJson(ctx.scanPath);
7268
7465
  if (pkg.dependencies?.[match.instrumentation_package]) {
7269
7466
  const { [match.instrumentation_package]: _removed, ...rest } = pkg.dependencies;
7270
7467
  pkg.dependencies = rest;
7271
- await fs22.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
7468
+ await fs23.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
7272
7469
  }
7273
7470
  }
7274
7471
  const hookFiles = await findHookFiles(ctx.scanPath);
7275
7472
  for (const file of hookFiles) {
7276
- const filePath = path40.join(ctx.scanPath, file);
7277
- const content = await fs22.readFile(filePath, "utf8");
7473
+ const filePath = path41.join(ctx.scanPath, file);
7474
+ const content = await fs23.readFile(filePath, "utf8");
7278
7475
  if (content.includes(match.registration_snippet)) {
7279
7476
  const filtered = content.split("\n").filter((line) => !line.includes(match.registration_snippet)).join("\n");
7280
- await fs22.writeFile(filePath, filtered, "utf8");
7477
+ await fs23.writeFile(filePath, filtered, "utf8");
7281
7478
  break;
7282
7479
  }
7283
7480
  }
@@ -8027,6 +8224,10 @@ export {
8027
8224
  loadPolicyFile,
8028
8225
  PolicyViolationsLog,
8029
8226
  thresholdForEdgeType,
8227
+ reconcileObservedRelPath,
8228
+ ensureObservedFileNode,
8229
+ ensureServiceNode,
8230
+ upsertObservedEdge,
8030
8231
  stitchTrace,
8031
8232
  makeErrorSpanWriter,
8032
8233
  handleSpan,
@@ -8081,4 +8282,4 @@ export {
8081
8282
  pruneRegistry,
8082
8283
  buildApi
8083
8284
  };
8084
- //# sourceMappingURL=chunk-WZYH5DVG.js.map
8285
+ //# sourceMappingURL=chunk-QM6BMPVJ.js.map