@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/index.cjs CHANGED
@@ -57,9 +57,9 @@ function mountBearerAuth(app, opts) {
57
57
  const suffixes = [...DEFAULT_UNAUTH_SUFFIXES, ...opts.extraUnauthenticatedSuffixes ?? []];
58
58
  const publicRead = opts.publicRead === true;
59
59
  app.addHook("preHandler", (req, reply, done) => {
60
- const path45 = (req.url.split("?")[0] ?? "").replace(/\/+$/, "");
60
+ const path46 = (req.url.split("?")[0] ?? "").replace(/\/+$/, "");
61
61
  for (const suffix of suffixes) {
62
- if (path45 === suffix || path45.endsWith(suffix)) {
62
+ if (path46 === suffix || path46.endsWith(suffix)) {
63
63
  done();
64
64
  return;
65
65
  }
@@ -189,8 +189,8 @@ function reshapeGrpcRequest(req) {
189
189
  };
190
190
  }
191
191
  function resolveProtoRoot() {
192
- const here = import_node_path41.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
193
- return import_node_path41.default.resolve(here, "..", "proto");
192
+ const here = import_node_path42.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
193
+ return import_node_path42.default.resolve(here, "..", "proto");
194
194
  }
195
195
  function loadTraceService() {
196
196
  const protoRoot = resolveProtoRoot();
@@ -258,13 +258,13 @@ async function startOtelGrpcReceiver(opts) {
258
258
  })
259
259
  };
260
260
  }
261
- var import_node_url, import_node_path41, import_node_crypto2, grpc, protoLoader;
261
+ var import_node_url, import_node_path42, import_node_crypto2, grpc, protoLoader;
262
262
  var init_otel_grpc = __esm({
263
263
  "src/otel-grpc.ts"() {
264
264
  "use strict";
265
265
  init_cjs_shims();
266
266
  import_node_url = require("url");
267
- import_node_path41 = __toESM(require("path"), 1);
267
+ import_node_path42 = __toESM(require("path"), 1);
268
268
  import_node_crypto2 = require("crypto");
269
269
  grpc = __toESM(require("@grpc/grpc-js"), 1);
270
270
  protoLoader = __toESM(require("@grpc/proto-loader"), 1);
@@ -344,6 +344,29 @@ function messagingDestinationOf(attrs) {
344
344
  }
345
345
  return void 0;
346
346
  }
347
+ function hasWebsocketUpgradeHeader(attrs) {
348
+ const v = attrs["http.request.header.upgrade"];
349
+ const matches = (s) => typeof s === "string" && s.trim().toLowerCase() === "websocket";
350
+ if (Array.isArray(v)) return v.some(matches);
351
+ return matches(v);
352
+ }
353
+ function websocketChannelPathOf(attrs) {
354
+ const route = attrs["http.route"];
355
+ if (typeof route === "string" && route.length > 0) return route;
356
+ for (const key of ["url.path", "http.target"]) {
357
+ const v = attrs[key];
358
+ if (typeof v === "string" && v.length > 0) {
359
+ const q = v.indexOf("?");
360
+ const path46 = q === -1 ? v : v.slice(0, q);
361
+ if (path46.length > 0) return path46;
362
+ }
363
+ }
364
+ return void 0;
365
+ }
366
+ function websocketChannelOf(attrs) {
367
+ if (!hasWebsocketUpgradeHeader(attrs)) return void 0;
368
+ return websocketChannelPathOf(attrs);
369
+ }
347
370
  function parseOtlpRequest(body) {
348
371
  const out = [];
349
372
  for (const rs of body.resourceSpans ?? []) {
@@ -374,6 +397,10 @@ function parseOtlpRequest(body) {
374
397
  messagingDestination: messagingDestinationOf(attrs),
375
398
  graphqlOperationName: typeof attrs["graphql.operation.name"] === "string" && attrs["graphql.operation.name"].length > 0 ? attrs["graphql.operation.name"] : void 0,
376
399
  graphqlOperationType: typeof attrs["graphql.operation.type"] === "string" && attrs["graphql.operation.type"].length > 0 ? attrs["graphql.operation.type"] : void 0,
400
+ rpcSystem: typeof attrs["rpc.system"] === "string" && attrs["rpc.system"].length > 0 ? attrs["rpc.system"] : void 0,
401
+ rpcService: typeof attrs["rpc.service"] === "string" && attrs["rpc.service"].length > 0 ? attrs["rpc.service"] : void 0,
402
+ rpcMethod: typeof attrs["rpc.method"] === "string" && attrs["rpc.method"].length > 0 ? attrs["rpc.method"] : void 0,
403
+ websocketChannel: websocketChannelOf(attrs),
377
404
  statusCode: span.status?.code,
378
405
  errorMessage: span.status?.message,
379
406
  exception: extractExceptionFromEvents(span.events)
@@ -385,10 +412,10 @@ function parseOtlpRequest(body) {
385
412
  return out;
386
413
  }
387
414
  function loadProtoRoot() {
388
- const here = import_node_path42.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
389
- const protoRoot = import_node_path42.default.resolve(here, "..", "proto");
415
+ const here = import_node_path43.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
416
+ const protoRoot = import_node_path43.default.resolve(here, "..", "proto");
390
417
  const root = new import_protobufjs.default.Root();
391
- root.resolvePath = (_origin, target) => import_node_path42.default.resolve(protoRoot, target);
418
+ root.resolvePath = (_origin, target) => import_node_path43.default.resolve(protoRoot, target);
392
419
  root.loadSync(
393
420
  "opentelemetry/proto/collector/trace/v1/trace_service.proto",
394
421
  { keepCase: true }
@@ -624,12 +651,12 @@ function logSpanHandler(span) {
624
651
  `otel: ${span.service} ${span.name} parent=${parent} status=${status2}${db}`
625
652
  );
626
653
  }
627
- var import_node_path42, import_node_url2, import_fastify2, import_protobufjs, ENV_ATTR_CANONICAL, ENV_ATTR_COMPAT, ENV_FALLBACK, exportTraceServiceRequestType, exportTraceServiceResponseType, cachedProtobufResponseBody, OTLP_STEP_ATTEMPTS, OTLP_STEP_STRIDE;
654
+ var import_node_path43, import_node_url2, import_fastify2, import_protobufjs, ENV_ATTR_CANONICAL, ENV_ATTR_COMPAT, ENV_FALLBACK, exportTraceServiceRequestType, exportTraceServiceResponseType, cachedProtobufResponseBody, OTLP_STEP_ATTEMPTS, OTLP_STEP_STRIDE;
628
655
  var init_otel = __esm({
629
656
  "src/otel.ts"() {
630
657
  "use strict";
631
658
  init_cjs_shims();
632
- import_node_path42 = __toESM(require("path"), 1);
659
+ import_node_path43 = __toESM(require("path"), 1);
633
660
  import_node_url2 = require("url");
634
661
  import_fastify2 = __toESM(require("fastify"), 1);
635
662
  import_protobufjs = __toESM(require("protobufjs"), 1);
@@ -1219,19 +1246,19 @@ function confidenceFromMix(edges, now = Date.now()) {
1219
1246
  function longestIncomingWalk(graph, start, maxDepth) {
1220
1247
  let best = { path: [start], edges: [] };
1221
1248
  const visited = /* @__PURE__ */ new Set([start]);
1222
- function step(node, path45, edges) {
1223
- if (path45.length > best.path.length) {
1224
- best = { path: [...path45], edges: [...edges] };
1249
+ function step(node, path46, edges) {
1250
+ if (path46.length > best.path.length) {
1251
+ best = { path: [...path46], edges: [...edges] };
1225
1252
  }
1226
- if (path45.length - 1 >= maxDepth) return;
1253
+ if (path46.length - 1 >= maxDepth) return;
1227
1254
  const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
1228
1255
  for (const [srcId, edge] of incoming) {
1229
1256
  if (visited.has(srcId)) continue;
1230
1257
  visited.add(srcId);
1231
- path45.push(srcId);
1258
+ path46.push(srcId);
1232
1259
  edges.push(edge);
1233
- step(srcId, path45, edges);
1234
- path45.pop();
1260
+ step(srcId, path46, edges);
1261
+ path46.pop();
1235
1262
  edges.pop();
1236
1263
  visited.delete(srcId);
1237
1264
  }
@@ -1425,26 +1452,26 @@ function dominantFailingCall(graph, serviceId4, visited) {
1425
1452
  return best;
1426
1453
  }
1427
1454
  function followFailingCallChain(graph, originServiceId, maxDepth) {
1428
- const path45 = [originServiceId];
1455
+ const path46 = [originServiceId];
1429
1456
  const edges = [];
1430
1457
  const visited = /* @__PURE__ */ new Set([originServiceId]);
1431
1458
  let current = originServiceId;
1432
1459
  for (let depth = 0; depth < maxDepth; depth++) {
1433
1460
  const hop = dominantFailingCall(graph, current, visited);
1434
1461
  if (!hop) break;
1435
- path45.push(hop.nextService);
1462
+ path46.push(hop.nextService);
1436
1463
  edges.push(hop.edge);
1437
1464
  visited.add(hop.nextService);
1438
1465
  current = hop.nextService;
1439
1466
  }
1440
1467
  if (edges.length === 0) return null;
1441
- return { path: path45, edges, culprit: current };
1468
+ return { path: path46, edges, culprit: current };
1442
1469
  }
1443
1470
  function crossServiceRootCause(graph, originId, incidents, errorEvent) {
1444
1471
  const chain = followFailingCallChain(graph, originId, ROOT_CAUSE_MAX_DEPTH);
1445
1472
  if (!chain) return null;
1446
1473
  const culprit = chain.culprit;
1447
- const path45 = [...chain.path];
1474
+ const path46 = [...chain.path];
1448
1475
  const edgeProvenances = chain.edges.map((e) => e.provenance);
1449
1476
  const baseConfidence = confidenceFromMix(chain.edges);
1450
1477
  const confidence = Math.max(0, Math.min(1, baseConfidence * INCIDENT_ROOT_CAUSE_CONFIDENCE));
@@ -1452,14 +1479,14 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
1452
1479
  if (loc) {
1453
1480
  let rootCauseNode = culprit;
1454
1481
  if (loc.fileNode) {
1455
- path45.push(loc.fileNode);
1482
+ path46.push(loc.fileNode);
1456
1483
  edgeProvenances.push(import_types.Provenance.OBSERVED);
1457
1484
  rootCauseNode = loc.fileNode;
1458
1485
  }
1459
1486
  return import_types.RootCauseResultSchema.parse({
1460
1487
  rootCauseNode,
1461
1488
  rootCauseReason: loc.rootCauseReason,
1462
- traversalPath: path45,
1489
+ traversalPath: path46,
1463
1490
  edgeProvenances,
1464
1491
  confidence,
1465
1492
  ...loc.fixRecommendation ? { fixRecommendation: loc.fixRecommendation } : {}
@@ -1471,7 +1498,7 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
1471
1498
  return import_types.RootCauseResultSchema.parse({
1472
1499
  rootCauseNode: culprit,
1473
1500
  rootCauseReason: `${culpritName} is failing downstream calls (${errs} observed error${errs === 1 ? "" : "s"})`,
1474
- traversalPath: path45,
1501
+ traversalPath: path46,
1475
1502
  edgeProvenances,
1476
1503
  confidence,
1477
1504
  fixRecommendation: `Inspect ${culpritName}'s failing handler`
@@ -2421,6 +2448,40 @@ function ensureGraphqlOperationNode(graph, serviceName, operationType, operation
2421
2448
  graph.addNode(id, node);
2422
2449
  return id;
2423
2450
  }
2451
+ function spanServesGrpcMethod(kind) {
2452
+ return kind !== WIRE_SPAN_KIND_CLIENT && kind !== WIRE_SPAN_KIND_PRODUCER && kind !== WIRE_SPAN_KIND_CONSUMER;
2453
+ }
2454
+ function ensureGrpcMethodNode(graph, rpcService, rpcMethod) {
2455
+ const id = (0, import_types3.grpcMethodId)(rpcService, rpcMethod);
2456
+ if (graph.hasNode(id)) return id;
2457
+ const node = {
2458
+ id,
2459
+ type: import_types3.NodeType.GrpcMethodNode,
2460
+ name: `${rpcService}/${rpcMethod}`,
2461
+ rpcService,
2462
+ rpcMethod,
2463
+ discoveredVia: "otel"
2464
+ };
2465
+ graph.addNode(id, node);
2466
+ return id;
2467
+ }
2468
+ function spanServesWebsocketChannel(kind) {
2469
+ return kind !== WIRE_SPAN_KIND_CLIENT && kind !== WIRE_SPAN_KIND_PRODUCER && kind !== WIRE_SPAN_KIND_CONSUMER;
2470
+ }
2471
+ function ensureWebsocketChannelNode(graph, serviceName, channel) {
2472
+ const id = (0, import_types3.websocketChannelId)(serviceName, channel);
2473
+ if (graph.hasNode(id)) return id;
2474
+ const node = {
2475
+ id,
2476
+ type: import_types3.NodeType.WebSocketChannelNode,
2477
+ name: channel,
2478
+ service: serviceName,
2479
+ channel,
2480
+ discoveredVia: "otel"
2481
+ };
2482
+ graph.addNode(id, node);
2483
+ return id;
2484
+ }
2424
2485
  function messagingDestinationKind(system) {
2425
2486
  return `${system}-topic`;
2426
2487
  }
@@ -2856,6 +2917,34 @@ async function handleSpan(ctx, span) {
2856
2917
  callSiteEvidence
2857
2918
  );
2858
2919
  if (result) affectedNode = targetId;
2920
+ } else if (span.rpcSystem === "grpc" && span.rpcService && span.rpcMethod && spanServesGrpcMethod(span.kind)) {
2921
+ const targetId = ensureGrpcMethodNode(ctx.graph, span.rpcService, span.rpcMethod);
2922
+ const result = upsertObservedEdge(
2923
+ ctx.graph,
2924
+ import_types3.EdgeType.CONTAINS,
2925
+ observedSource(),
2926
+ targetId,
2927
+ ts,
2928
+ isError,
2929
+ callSiteEvidence
2930
+ );
2931
+ if (result) affectedNode = targetId;
2932
+ } else if (span.websocketChannel && spanServesWebsocketChannel(span.kind)) {
2933
+ const targetId = ensureWebsocketChannelNode(
2934
+ ctx.graph,
2935
+ span.service,
2936
+ span.websocketChannel
2937
+ );
2938
+ const result = upsertObservedEdge(
2939
+ ctx.graph,
2940
+ import_types3.EdgeType.CONNECTS_TO,
2941
+ observedSource(),
2942
+ targetId,
2943
+ ts,
2944
+ isError,
2945
+ callSiteEvidence
2946
+ );
2947
+ if (result) affectedNode = targetId;
2859
2948
  } else {
2860
2949
  const host = pickAddress(span);
2861
2950
  let resolvedViaAddress = false;
@@ -5426,17 +5515,138 @@ async function addRoutes(graph, services) {
5426
5515
  return { nodesAdded, edgesAdded };
5427
5516
  }
5428
5517
 
5518
+ // src/extract/proto.ts
5519
+ init_cjs_shims();
5520
+ var import_node_fs15 = require("fs");
5521
+ var import_node_path23 = __toESM(require("path"), 1);
5522
+ var import_types12 = require("@neat.is/types");
5523
+ var PROTO_EXTENSION = ".proto";
5524
+ function packageOf(content) {
5525
+ const m = content.match(/^\s*package\s+([A-Za-z_][A-Za-z0-9_.]*)\s*;/m);
5526
+ return m ? m[1] : null;
5527
+ }
5528
+ function lineAt(content, offset) {
5529
+ return content.slice(0, offset).split("\n").length;
5530
+ }
5531
+ function grpcMethodsFromProto(content, fqPackage) {
5532
+ const out = [];
5533
+ const serviceRe = /\bservice\s+([A-Za-z_][A-Za-z0-9_]*)\s*\{/g;
5534
+ let sm;
5535
+ while ((sm = serviceRe.exec(content)) !== null) {
5536
+ const serviceName = sm[1];
5537
+ const rpcService = fqPackage ? `${fqPackage}.${serviceName}` : serviceName;
5538
+ const bodyStart = serviceRe.lastIndex;
5539
+ let depth = 1;
5540
+ let i = bodyStart;
5541
+ for (; i < content.length && depth > 0; i++) {
5542
+ const ch = content[i];
5543
+ if (ch === "{") depth++;
5544
+ else if (ch === "}") depth--;
5545
+ }
5546
+ const body = content.slice(bodyStart, i - 1);
5547
+ const rpcRe = /\brpc\s+([A-Za-z_][A-Za-z0-9_]*)\s*\(/g;
5548
+ let rm;
5549
+ while ((rm = rpcRe.exec(body)) !== null) {
5550
+ const rpcMethod = rm[1];
5551
+ const line = lineAt(content, bodyStart + rm.index);
5552
+ out.push({ rpcService, rpcMethod, line });
5553
+ }
5554
+ serviceRe.lastIndex = i;
5555
+ }
5556
+ return out;
5557
+ }
5558
+ async function walkProtoFiles(dir) {
5559
+ const out = [];
5560
+ async function walk3(current) {
5561
+ const entries = await import_node_fs15.promises.readdir(current, { withFileTypes: true }).catch(() => []);
5562
+ for (const entry of entries) {
5563
+ const full = import_node_path23.default.join(current, entry.name);
5564
+ if (entry.isDirectory()) {
5565
+ if (IGNORED_DIRS.has(entry.name)) continue;
5566
+ if (await isPythonVenvDir(full)) continue;
5567
+ await walk3(full);
5568
+ } else if (entry.isFile() && import_node_path23.default.extname(entry.name) === PROTO_EXTENSION) {
5569
+ out.push(full);
5570
+ }
5571
+ }
5572
+ }
5573
+ await walk3(dir);
5574
+ return out;
5575
+ }
5576
+ async function addGrpcMethods(graph, services) {
5577
+ let nodesAdded = 0;
5578
+ let edgesAdded = 0;
5579
+ for (const service of services) {
5580
+ const protoPaths = await walkProtoFiles(service.dir);
5581
+ for (const protoPath of protoPaths) {
5582
+ if (isTestPath(protoPath)) continue;
5583
+ const relFile = toPosix2(import_node_path23.default.relative(service.dir, protoPath));
5584
+ let content;
5585
+ try {
5586
+ content = await import_node_fs15.promises.readFile(protoPath, "utf8");
5587
+ } catch (err) {
5588
+ recordExtractionError("proto extraction", protoPath, err);
5589
+ continue;
5590
+ }
5591
+ let methods;
5592
+ try {
5593
+ methods = grpcMethodsFromProto(content, packageOf(content));
5594
+ } catch (err) {
5595
+ recordExtractionError("proto extraction", protoPath, err);
5596
+ continue;
5597
+ }
5598
+ if (methods.length === 0) continue;
5599
+ for (const method of methods) {
5600
+ const mid = (0, import_types12.grpcMethodId)(method.rpcService, method.rpcMethod);
5601
+ if (!graph.hasNode(mid)) {
5602
+ const node = {
5603
+ id: mid,
5604
+ type: import_types12.NodeType.GrpcMethodNode,
5605
+ name: `${method.rpcService}/${method.rpcMethod}`,
5606
+ rpcService: method.rpcService,
5607
+ rpcMethod: method.rpcMethod,
5608
+ path: relFile,
5609
+ line: method.line,
5610
+ discoveredVia: "static"
5611
+ };
5612
+ graph.addNode(mid, node);
5613
+ nodesAdded++;
5614
+ }
5615
+ const containsId = (0, import_types12.extractedEdgeId)(service.node.id, mid, import_types12.EdgeType.CONTAINS);
5616
+ if (!graph.hasEdge(containsId)) {
5617
+ const edge = {
5618
+ id: containsId,
5619
+ source: service.node.id,
5620
+ target: mid,
5621
+ type: import_types12.EdgeType.CONTAINS,
5622
+ provenance: import_types12.Provenance.EXTRACTED,
5623
+ confidence: (0, import_types12.confidenceForExtracted)("structural"),
5624
+ evidence: {
5625
+ file: relFile,
5626
+ line: method.line,
5627
+ snippet: snippet(content, method.line)
5628
+ }
5629
+ };
5630
+ graph.addEdgeWithKey(containsId, service.node.id, mid, edge);
5631
+ edgesAdded++;
5632
+ }
5633
+ }
5634
+ }
5635
+ }
5636
+ return { nodesAdded, edgesAdded };
5637
+ }
5638
+
5429
5639
  // src/extract/calls/index.ts
5430
5640
  init_cjs_shims();
5431
- var import_types19 = require("@neat.is/types");
5641
+ var import_types20 = require("@neat.is/types");
5432
5642
 
5433
5643
  // src/extract/calls/http.ts
5434
5644
  init_cjs_shims();
5435
- var import_node_path23 = __toESM(require("path"), 1);
5645
+ var import_node_path24 = __toESM(require("path"), 1);
5436
5646
  var import_tree_sitter3 = __toESM(require("tree-sitter"), 1);
5437
5647
  var import_tree_sitter_javascript3 = __toESM(require("tree-sitter-javascript"), 1);
5438
5648
  var import_tree_sitter_python2 = __toESM(require("tree-sitter-python"), 1);
5439
- var import_types12 = require("@neat.is/types");
5649
+ var import_types13 = require("@neat.is/types");
5440
5650
  var STRING_LITERAL_NODE_TYPES = /* @__PURE__ */ new Set(["string_fragment", "string_content"]);
5441
5651
  var JSX_EXTERNAL_LINK_TAGS = /* @__PURE__ */ new Set(["a", "Link", "NavLink", "ExternalLink", "Anchor"]);
5442
5652
  function isInsideJsxExternalLink(node) {
@@ -5506,7 +5716,7 @@ async function addHttpCallEdges(graph, services) {
5506
5716
  const seen = /* @__PURE__ */ new Set();
5507
5717
  for (const file of files) {
5508
5718
  if (isTestPath(file.path)) continue;
5509
- const parser = import_node_path23.default.extname(file.path) === ".py" ? pyParser : jsParser;
5719
+ const parser = import_node_path24.default.extname(file.path) === ".py" ? pyParser : jsParser;
5510
5720
  let sites;
5511
5721
  try {
5512
5722
  sites = callsFromSource(file.content, parser, knownHosts);
@@ -5515,14 +5725,14 @@ async function addHttpCallEdges(graph, services) {
5515
5725
  continue;
5516
5726
  }
5517
5727
  if (sites.length === 0) continue;
5518
- const relFile = toPosix2(import_node_path23.default.relative(service.dir, file.path));
5728
+ const relFile = toPosix2(import_node_path24.default.relative(service.dir, file.path));
5519
5729
  for (const site of sites) {
5520
5730
  const targetId = hostToNodeId.get(site.host);
5521
5731
  if (!targetId || targetId === service.node.id) continue;
5522
5732
  const dedupKey = `${relFile}|${targetId}`;
5523
5733
  if (seen.has(dedupKey)) continue;
5524
5734
  seen.add(dedupKey);
5525
- const confidence = (0, import_types12.confidenceForExtracted)("url-literal-service-target");
5735
+ const confidence = (0, import_types13.confidenceForExtracted)("url-literal-service-target");
5526
5736
  const ev = {
5527
5737
  file: relFile,
5528
5738
  line: site.line,
@@ -5536,25 +5746,25 @@ async function addHttpCallEdges(graph, services) {
5536
5746
  );
5537
5747
  nodesAdded += n;
5538
5748
  edgesAdded += e;
5539
- if (!(0, import_types12.passesExtractedFloor)(confidence)) {
5749
+ if (!(0, import_types13.passesExtractedFloor)(confidence)) {
5540
5750
  noteExtractedDropped({
5541
5751
  source: fileNodeId,
5542
5752
  target: targetId,
5543
- type: import_types12.EdgeType.CALLS,
5753
+ type: import_types13.EdgeType.CALLS,
5544
5754
  confidence,
5545
5755
  confidenceKind: "url-literal-service-target",
5546
5756
  evidence: ev
5547
5757
  });
5548
5758
  continue;
5549
5759
  }
5550
- const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, targetId, import_types12.EdgeType.CALLS);
5760
+ const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, targetId, import_types13.EdgeType.CALLS);
5551
5761
  if (!graph.hasEdge(edgeId)) {
5552
5762
  const edge = {
5553
5763
  id: edgeId,
5554
5764
  source: fileNodeId,
5555
5765
  target: targetId,
5556
- type: import_types12.EdgeType.CALLS,
5557
- provenance: import_types12.Provenance.EXTRACTED,
5766
+ type: import_types13.EdgeType.CALLS,
5767
+ provenance: import_types13.Provenance.EXTRACTED,
5558
5768
  confidence,
5559
5769
  evidence: ev
5560
5770
  };
@@ -5569,10 +5779,10 @@ async function addHttpCallEdges(graph, services) {
5569
5779
 
5570
5780
  // src/extract/calls/route-match.ts
5571
5781
  init_cjs_shims();
5572
- var import_node_path24 = __toESM(require("path"), 1);
5782
+ var import_node_path25 = __toESM(require("path"), 1);
5573
5783
  var import_tree_sitter4 = __toESM(require("tree-sitter"), 1);
5574
5784
  var import_tree_sitter_javascript4 = __toESM(require("tree-sitter-javascript"), 1);
5575
- var import_types13 = require("@neat.is/types");
5785
+ var import_types14 = require("@neat.is/types");
5576
5786
  var PARSE_CHUNK4 = 16384;
5577
5787
  function parseSource4(parser, source) {
5578
5788
  return parser.parse(
@@ -5737,9 +5947,9 @@ function buildRouteIndex(graph) {
5737
5947
  const index = /* @__PURE__ */ new Map();
5738
5948
  graph.forEachNode((_id, attrs) => {
5739
5949
  const node = attrs;
5740
- if (node.type !== import_types13.NodeType.RouteNode) return;
5950
+ if (node.type !== import_types14.NodeType.RouteNode) return;
5741
5951
  const route = attrs;
5742
- const owner = (0, import_types13.serviceId)(route.service);
5952
+ const owner = (0, import_types14.serviceId)(route.service);
5743
5953
  const entry = {
5744
5954
  method: route.method.toUpperCase(),
5745
5955
  normalizedPath: normalizePathTemplate(route.pathTemplate),
@@ -5768,7 +5978,7 @@ async function addRouteCallEdges(graph, services) {
5768
5978
  const seen = /* @__PURE__ */ new Set();
5769
5979
  for (const file of files) {
5770
5980
  if (isTestPath(file.path)) continue;
5771
- if (!JS_CLIENT_EXTENSIONS.has(import_node_path24.default.extname(file.path))) continue;
5981
+ if (!JS_CLIENT_EXTENSIONS.has(import_node_path25.default.extname(file.path))) continue;
5772
5982
  let sites;
5773
5983
  try {
5774
5984
  sites = clientCallSitesFromSource(file.content, jsParser, knownHosts);
@@ -5777,7 +5987,7 @@ async function addRouteCallEdges(graph, services) {
5777
5987
  continue;
5778
5988
  }
5779
5989
  if (sites.length === 0) continue;
5780
- const relFile = toPosix2(import_node_path24.default.relative(service.dir, file.path));
5990
+ const relFile = toPosix2(import_node_path25.default.relative(service.dir, file.path));
5781
5991
  for (const site of sites) {
5782
5992
  const serverServiceId = hostToNodeId.get(site.host);
5783
5993
  if (!serverServiceId || serverServiceId === service.node.id) continue;
@@ -5797,7 +6007,7 @@ async function addRouteCallEdges(graph, services) {
5797
6007
  );
5798
6008
  nodesAdded += n;
5799
6009
  edgesAdded += e;
5800
- const confidence = (0, import_types13.confidenceForExtracted)("verified-call-site");
6010
+ const confidence = (0, import_types14.confidenceForExtracted)("verified-call-site");
5801
6011
  const ev = {
5802
6012
  file: relFile,
5803
6013
  line: site.line,
@@ -5805,25 +6015,25 @@ async function addRouteCallEdges(graph, services) {
5805
6015
  method: site.method ?? match.method,
5806
6016
  pathTemplate: site.pathTemplate
5807
6017
  };
5808
- if (!(0, import_types13.passesExtractedFloor)(confidence)) {
6018
+ if (!(0, import_types14.passesExtractedFloor)(confidence)) {
5809
6019
  noteExtractedDropped({
5810
6020
  source: fileNodeId,
5811
6021
  target: match.routeNodeId,
5812
- type: import_types13.EdgeType.CALLS,
6022
+ type: import_types14.EdgeType.CALLS,
5813
6023
  confidence,
5814
6024
  confidenceKind: "verified-call-site",
5815
6025
  evidence: ev
5816
6026
  });
5817
6027
  continue;
5818
6028
  }
5819
- const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, match.routeNodeId, import_types13.EdgeType.CALLS);
6029
+ const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, match.routeNodeId, import_types14.EdgeType.CALLS);
5820
6030
  if (!graph.hasEdge(edgeId)) {
5821
6031
  const edge = {
5822
6032
  id: edgeId,
5823
6033
  source: fileNodeId,
5824
6034
  target: match.routeNodeId,
5825
- type: import_types13.EdgeType.CALLS,
5826
- provenance: import_types13.Provenance.EXTRACTED,
6035
+ type: import_types14.EdgeType.CALLS,
6036
+ provenance: import_types14.Provenance.EXTRACTED,
5827
6037
  confidence,
5828
6038
  evidence: ev
5829
6039
  };
@@ -5838,8 +6048,8 @@ async function addRouteCallEdges(graph, services) {
5838
6048
 
5839
6049
  // src/extract/calls/kafka.ts
5840
6050
  init_cjs_shims();
5841
- var import_node_path25 = __toESM(require("path"), 1);
5842
- var import_types14 = require("@neat.is/types");
6051
+ var import_node_path26 = __toESM(require("path"), 1);
6052
+ var import_types15 = require("@neat.is/types");
5843
6053
  var PRODUCER_TOPIC_RE = /(?:producer|kafkaProducer)[\s\S]{0,40}?\.send\s*\(\s*\{[\s\S]{0,200}?topic\s*:\s*['"`]([^'"`]+)['"`]/g;
5844
6054
  var CONSUMER_TOPIC_RE = /(?:consumer|kafkaConsumer)[\s\S]{0,40}?\.(?:subscribe|run)\s*\(\s*\{[\s\S]{0,200}?topic[s]?\s*:\s*(?:\[\s*)?['"`]([^'"`]+)['"`]/g;
5845
6055
  function findAll(re, text) {
@@ -5860,7 +6070,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
5860
6070
  seen.add(key);
5861
6071
  const line = lineOf(file.content, topic);
5862
6072
  out.push({
5863
- infraId: (0, import_types14.infraId)("kafka-topic", topic),
6073
+ infraId: (0, import_types15.infraId)("kafka-topic", topic),
5864
6074
  name: topic,
5865
6075
  kind: "kafka-topic",
5866
6076
  edgeType,
@@ -5869,7 +6079,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
5869
6079
  // tier (ADR-066).
5870
6080
  confidenceKind: "verified-call-site",
5871
6081
  evidence: {
5872
- file: import_node_path25.default.relative(serviceDir, file.path),
6082
+ file: import_node_path26.default.relative(serviceDir, file.path),
5873
6083
  line,
5874
6084
  snippet: snippet(file.content, line)
5875
6085
  }
@@ -5882,8 +6092,8 @@ function kafkaEndpointsFromFile(file, serviceDir) {
5882
6092
 
5883
6093
  // src/extract/calls/redis.ts
5884
6094
  init_cjs_shims();
5885
- var import_node_path26 = __toESM(require("path"), 1);
5886
- var import_types15 = require("@neat.is/types");
6095
+ var import_node_path27 = __toESM(require("path"), 1);
6096
+ var import_types16 = require("@neat.is/types");
5887
6097
  var REDIS_URL_RE = /redis(?:s)?:\/\/(?:[^@'"`\s]+@)?([^:/'"`\s]+)(?::(\d+))?/g;
5888
6098
  function redisEndpointsFromFile(file, serviceDir) {
5889
6099
  const out = [];
@@ -5896,7 +6106,7 @@ function redisEndpointsFromFile(file, serviceDir) {
5896
6106
  seen.add(host);
5897
6107
  const line = lineOf(file.content, host);
5898
6108
  out.push({
5899
- infraId: (0, import_types15.infraId)("redis", host),
6109
+ infraId: (0, import_types16.infraId)("redis", host),
5900
6110
  name: host,
5901
6111
  kind: "redis",
5902
6112
  edgeType: "CALLS",
@@ -5905,7 +6115,7 @@ function redisEndpointsFromFile(file, serviceDir) {
5905
6115
  // support tier (ADR-066).
5906
6116
  confidenceKind: "url-with-structural-support",
5907
6117
  evidence: {
5908
- file: import_node_path26.default.relative(serviceDir, file.path),
6118
+ file: import_node_path27.default.relative(serviceDir, file.path),
5909
6119
  line,
5910
6120
  snippet: snippet(file.content, line)
5911
6121
  }
@@ -5916,8 +6126,8 @@ function redisEndpointsFromFile(file, serviceDir) {
5916
6126
 
5917
6127
  // src/extract/calls/aws.ts
5918
6128
  init_cjs_shims();
5919
- var import_node_path27 = __toESM(require("path"), 1);
5920
- var import_types16 = require("@neat.is/types");
6129
+ var import_node_path28 = __toESM(require("path"), 1);
6130
+ var import_types17 = require("@neat.is/types");
5921
6131
  var S3_BUCKET_RE = /Bucket\s*:\s*['"`]([^'"`]+)['"`]/g;
5922
6132
  var DYNAMO_TABLE_RE = /TableName\s*:\s*['"`]([^'"`]+)['"`]/g;
5923
6133
  function hasMarker(text, markers) {
@@ -5941,7 +6151,7 @@ function awsEndpointsFromFile(file, serviceDir) {
5941
6151
  seen.add(key);
5942
6152
  const line = lineOf(file.content, name);
5943
6153
  out.push({
5944
- infraId: (0, import_types16.infraId)(kind, name),
6154
+ infraId: (0, import_types17.infraId)(kind, name),
5945
6155
  name,
5946
6156
  kind,
5947
6157
  edgeType: "CALLS",
@@ -5950,7 +6160,7 @@ function awsEndpointsFromFile(file, serviceDir) {
5950
6160
  // (ADR-066).
5951
6161
  confidenceKind: "verified-call-site",
5952
6162
  evidence: {
5953
- file: import_node_path27.default.relative(serviceDir, file.path),
6163
+ file: import_node_path28.default.relative(serviceDir, file.path),
5954
6164
  line,
5955
6165
  snippet: snippet(file.content, line)
5956
6166
  }
@@ -5975,8 +6185,8 @@ function awsEndpointsFromFile(file, serviceDir) {
5975
6185
 
5976
6186
  // src/extract/calls/grpc.ts
5977
6187
  init_cjs_shims();
5978
- var import_node_path28 = __toESM(require("path"), 1);
5979
- var import_types17 = require("@neat.is/types");
6188
+ var import_node_path29 = __toESM(require("path"), 1);
6189
+ var import_types18 = require("@neat.is/types");
5980
6190
  var GRPC_CLIENT_RE = /new\s+([A-Z][A-Za-z0-9_]*)Client\s*\(\s*['"`]?([^,'"`)]+)?/g;
5981
6191
  var AWS_SDK_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@aws-sdk\/client-([a-z0-9-]+)['"`]/g;
5982
6192
  var GRPC_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@grpc\/grpc-js['"`]|_grpc_pb['"`]/;
@@ -6025,7 +6235,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
6025
6235
  const { kind } = classified;
6026
6236
  const line = lineOf(file.content, m[0]);
6027
6237
  out.push({
6028
- infraId: (0, import_types17.infraId)(kind, name),
6238
+ infraId: (0, import_types18.infraId)(kind, name),
6029
6239
  name,
6030
6240
  kind,
6031
6241
  edgeType: "CALLS",
@@ -6034,7 +6244,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
6034
6244
  // tier (ADR-066).
6035
6245
  confidenceKind: "verified-call-site",
6036
6246
  evidence: {
6037
- file: import_node_path28.default.relative(serviceDir, file.path),
6247
+ file: import_node_path29.default.relative(serviceDir, file.path),
6038
6248
  line,
6039
6249
  snippet: snippet(file.content, line)
6040
6250
  }
@@ -6045,8 +6255,8 @@ function grpcEndpointsFromFile(file, serviceDir) {
6045
6255
 
6046
6256
  // src/extract/calls/supabase.ts
6047
6257
  init_cjs_shims();
6048
- var import_node_path29 = __toESM(require("path"), 1);
6049
- var import_types18 = require("@neat.is/types");
6258
+ var import_node_path30 = __toESM(require("path"), 1);
6259
+ var import_types19 = require("@neat.is/types");
6050
6260
  var SUPABASE_JS_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@supabase\/supabase-js['"`]/;
6051
6261
  var SUPABASE_SSR_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@supabase\/ssr['"`]/;
6052
6262
  var SUPABASE_CLIENT_RE = /\b(createClient|createServerClient|createBrowserClient)\s*\(\s*(?:['"`]([^'"`]*)['"`])?/g;
@@ -6084,7 +6294,7 @@ function supabaseEndpointsFromFile(file, serviceDir) {
6084
6294
  seen.add(name);
6085
6295
  const line = lineOf(file.content, m[0]);
6086
6296
  out.push({
6087
- infraId: (0, import_types18.infraId)("supabase", name),
6297
+ infraId: (0, import_types19.infraId)("supabase", name),
6088
6298
  name,
6089
6299
  kind: "supabase",
6090
6300
  edgeType: "CALLS",
@@ -6094,7 +6304,7 @@ function supabaseEndpointsFromFile(file, serviceDir) {
6094
6304
  // tier (ADR-066), the same grade aws.ts / grpc.ts emit at.
6095
6305
  confidenceKind: "verified-call-site",
6096
6306
  evidence: {
6097
- file: import_node_path29.default.relative(serviceDir, file.path),
6307
+ file: import_node_path30.default.relative(serviceDir, file.path),
6098
6308
  line,
6099
6309
  snippet: snippet(file.content, line)
6100
6310
  }
@@ -6107,11 +6317,11 @@ function supabaseEndpointsFromFile(file, serviceDir) {
6107
6317
  function edgeTypeFromEndpoint(ep) {
6108
6318
  switch (ep.edgeType) {
6109
6319
  case "PUBLISHES_TO":
6110
- return import_types19.EdgeType.PUBLISHES_TO;
6320
+ return import_types20.EdgeType.PUBLISHES_TO;
6111
6321
  case "CONSUMES_FROM":
6112
- return import_types19.EdgeType.CONSUMES_FROM;
6322
+ return import_types20.EdgeType.CONSUMES_FROM;
6113
6323
  default:
6114
- return import_types19.EdgeType.CALLS;
6324
+ return import_types20.EdgeType.CALLS;
6115
6325
  }
6116
6326
  }
6117
6327
  function isAwsKind(kind) {
@@ -6139,7 +6349,7 @@ async function addExternalEndpointEdges(graph, services) {
6139
6349
  if (!graph.hasNode(ep.infraId)) {
6140
6350
  const node = {
6141
6351
  id: ep.infraId,
6142
- type: import_types19.NodeType.InfraNode,
6352
+ type: import_types20.NodeType.InfraNode,
6143
6353
  name: ep.name,
6144
6354
  // #238 — `aws-*` covers AWS-SDK client kinds (aws-s3, aws-dynamodb,
6145
6355
  // aws-cognito-identity-provider, …); `s3-` / `dynamodb-` cover the
@@ -6151,7 +6361,7 @@ async function addExternalEndpointEdges(graph, services) {
6151
6361
  nodesAdded++;
6152
6362
  }
6153
6363
  const edgeType = edgeTypeFromEndpoint(ep);
6154
- const confidence = (0, import_types19.confidenceForExtracted)(ep.confidenceKind);
6364
+ const confidence = (0, import_types20.confidenceForExtracted)(ep.confidenceKind);
6155
6365
  const relFile = toPosix2(ep.evidence.file);
6156
6366
  const { fileNodeId, nodesAdded: n, edgesAdded: e } = ensureFileNode(
6157
6367
  graph,
@@ -6161,7 +6371,7 @@ async function addExternalEndpointEdges(graph, services) {
6161
6371
  );
6162
6372
  nodesAdded += n;
6163
6373
  edgesAdded += e;
6164
- if (!(0, import_types19.passesExtractedFloor)(confidence)) {
6374
+ if (!(0, import_types20.passesExtractedFloor)(confidence)) {
6165
6375
  noteExtractedDropped({
6166
6376
  source: fileNodeId,
6167
6377
  target: ep.infraId,
@@ -6181,7 +6391,7 @@ async function addExternalEndpointEdges(graph, services) {
6181
6391
  source: fileNodeId,
6182
6392
  target: ep.infraId,
6183
6393
  type: edgeType,
6184
- provenance: import_types19.Provenance.EXTRACTED,
6394
+ provenance: import_types20.Provenance.EXTRACTED,
6185
6395
  confidence,
6186
6396
  evidence: ep.evidence
6187
6397
  };
@@ -6207,16 +6417,16 @@ init_cjs_shims();
6207
6417
 
6208
6418
  // src/extract/infra/docker-compose.ts
6209
6419
  init_cjs_shims();
6210
- var import_node_path30 = __toESM(require("path"), 1);
6211
- var import_types21 = require("@neat.is/types");
6420
+ var import_node_path31 = __toESM(require("path"), 1);
6421
+ var import_types22 = require("@neat.is/types");
6212
6422
 
6213
6423
  // src/extract/infra/shared.ts
6214
6424
  init_cjs_shims();
6215
- var import_types20 = require("@neat.is/types");
6425
+ var import_types21 = require("@neat.is/types");
6216
6426
  function makeInfraNode(kind, name, provider = "self", extras) {
6217
6427
  return {
6218
- id: (0, import_types20.infraId)(kind, name),
6219
- type: import_types20.NodeType.InfraNode,
6428
+ id: (0, import_types21.infraId)(kind, name),
6429
+ type: import_types21.NodeType.InfraNode,
6220
6430
  name,
6221
6431
  provider,
6222
6432
  kind,
@@ -6245,7 +6455,7 @@ function dependsOnList(value) {
6245
6455
  }
6246
6456
  function serviceNameToServiceNode(name, services) {
6247
6457
  for (const s of services) {
6248
- if (s.node.name === name || import_node_path30.default.basename(s.dir) === name) return s.node.id;
6458
+ if (s.node.name === name || import_node_path31.default.basename(s.dir) === name) return s.node.id;
6249
6459
  }
6250
6460
  return null;
6251
6461
  }
@@ -6254,7 +6464,7 @@ async function addComposeInfra(graph, scanPath, services) {
6254
6464
  let edgesAdded = 0;
6255
6465
  let composePath = null;
6256
6466
  for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
6257
- const abs = import_node_path30.default.join(scanPath, name);
6467
+ const abs = import_node_path31.default.join(scanPath, name);
6258
6468
  if (await exists(abs)) {
6259
6469
  composePath = abs;
6260
6470
  break;
@@ -6267,13 +6477,13 @@ async function addComposeInfra(graph, scanPath, services) {
6267
6477
  } catch (err) {
6268
6478
  recordExtractionError(
6269
6479
  "infra docker-compose",
6270
- import_node_path30.default.relative(scanPath, composePath),
6480
+ import_node_path31.default.relative(scanPath, composePath),
6271
6481
  err
6272
6482
  );
6273
6483
  return { nodesAdded, edgesAdded };
6274
6484
  }
6275
6485
  if (!compose?.services) return { nodesAdded, edgesAdded };
6276
- const evidenceFile = import_node_path30.default.relative(scanPath, composePath).split(import_node_path30.default.sep).join("/");
6486
+ const evidenceFile = import_node_path31.default.relative(scanPath, composePath).split(import_node_path31.default.sep).join("/");
6277
6487
  const composeNameToNodeId = /* @__PURE__ */ new Map();
6278
6488
  for (const [composeName, svc] of Object.entries(compose.services)) {
6279
6489
  const matchedServiceId = serviceNameToServiceNode(composeName, services);
@@ -6295,15 +6505,15 @@ async function addComposeInfra(graph, scanPath, services) {
6295
6505
  for (const dep of dependsOnList(svc.depends_on)) {
6296
6506
  const targetId = composeNameToNodeId.get(dep);
6297
6507
  if (!targetId) continue;
6298
- const edgeId = (0, import_types4.extractedEdgeId)(sourceId, targetId, import_types21.EdgeType.DEPENDS_ON);
6508
+ const edgeId = (0, import_types4.extractedEdgeId)(sourceId, targetId, import_types22.EdgeType.DEPENDS_ON);
6299
6509
  if (graph.hasEdge(edgeId)) continue;
6300
6510
  const edge = {
6301
6511
  id: edgeId,
6302
6512
  source: sourceId,
6303
6513
  target: targetId,
6304
- type: import_types21.EdgeType.DEPENDS_ON,
6305
- provenance: import_types21.Provenance.EXTRACTED,
6306
- confidence: (0, import_types21.confidenceForExtracted)("structural"),
6514
+ type: import_types22.EdgeType.DEPENDS_ON,
6515
+ provenance: import_types22.Provenance.EXTRACTED,
6516
+ confidence: (0, import_types22.confidenceForExtracted)("structural"),
6307
6517
  evidence: { file: evidenceFile }
6308
6518
  };
6309
6519
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -6315,9 +6525,9 @@ async function addComposeInfra(graph, scanPath, services) {
6315
6525
 
6316
6526
  // src/extract/infra/dockerfile.ts
6317
6527
  init_cjs_shims();
6318
- var import_node_path31 = __toESM(require("path"), 1);
6319
- var import_node_fs15 = require("fs");
6320
- var import_types22 = require("@neat.is/types");
6528
+ var import_node_path32 = __toESM(require("path"), 1);
6529
+ var import_node_fs16 = require("fs");
6530
+ var import_types23 = require("@neat.is/types");
6321
6531
  function readDockerfile(content) {
6322
6532
  let image = null;
6323
6533
  const ports = [];
@@ -6346,15 +6556,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
6346
6556
  let nodesAdded = 0;
6347
6557
  let edgesAdded = 0;
6348
6558
  for (const service of services) {
6349
- const dockerfilePath = import_node_path31.default.join(service.dir, "Dockerfile");
6559
+ const dockerfilePath = import_node_path32.default.join(service.dir, "Dockerfile");
6350
6560
  if (!await exists(dockerfilePath)) continue;
6351
6561
  let content;
6352
6562
  try {
6353
- content = await import_node_fs15.promises.readFile(dockerfilePath, "utf8");
6563
+ content = await import_node_fs16.promises.readFile(dockerfilePath, "utf8");
6354
6564
  } catch (err) {
6355
6565
  recordExtractionError(
6356
6566
  "infra dockerfile",
6357
- import_node_path31.default.relative(scanPath, dockerfilePath),
6567
+ import_node_path32.default.relative(scanPath, dockerfilePath),
6358
6568
  err
6359
6569
  );
6360
6570
  continue;
@@ -6366,8 +6576,8 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
6366
6576
  graph.addNode(node.id, node);
6367
6577
  nodesAdded++;
6368
6578
  }
6369
- const relDockerfile = toPosix2(import_node_path31.default.relative(service.dir, dockerfilePath));
6370
- const evidenceFile = toPosix2(import_node_path31.default.relative(scanPath, dockerfilePath));
6579
+ const relDockerfile = toPosix2(import_node_path32.default.relative(service.dir, dockerfilePath));
6580
+ const evidenceFile = toPosix2(import_node_path32.default.relative(scanPath, dockerfilePath));
6371
6581
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
6372
6582
  graph,
6373
6583
  service.pkg.name,
@@ -6376,15 +6586,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
6376
6586
  );
6377
6587
  nodesAdded += fn;
6378
6588
  edgesAdded += fe;
6379
- const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, node.id, import_types22.EdgeType.RUNS_ON);
6589
+ const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, node.id, import_types23.EdgeType.RUNS_ON);
6380
6590
  if (!graph.hasEdge(edgeId)) {
6381
6591
  const edge = {
6382
6592
  id: edgeId,
6383
6593
  source: fileNodeId,
6384
6594
  target: node.id,
6385
- type: import_types22.EdgeType.RUNS_ON,
6386
- provenance: import_types22.Provenance.EXTRACTED,
6387
- confidence: (0, import_types22.confidenceForExtracted)("structural"),
6595
+ type: import_types23.EdgeType.RUNS_ON,
6596
+ provenance: import_types23.Provenance.EXTRACTED,
6597
+ confidence: (0, import_types23.confidenceForExtracted)("structural"),
6388
6598
  evidence: {
6389
6599
  file: evidenceFile,
6390
6600
  ...facts.entrypoint ? { snippet: facts.entrypoint.slice(0, 120) } : {}
@@ -6399,15 +6609,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
6399
6609
  graph.addNode(portNode.id, portNode);
6400
6610
  nodesAdded++;
6401
6611
  }
6402
- const portEdgeId = (0, import_types4.extractedEdgeId)(fileNodeId, portNode.id, import_types22.EdgeType.CONNECTS_TO);
6612
+ const portEdgeId = (0, import_types4.extractedEdgeId)(fileNodeId, portNode.id, import_types23.EdgeType.CONNECTS_TO);
6403
6613
  if (graph.hasEdge(portEdgeId)) continue;
6404
6614
  const portEdge = {
6405
6615
  id: portEdgeId,
6406
6616
  source: fileNodeId,
6407
6617
  target: portNode.id,
6408
- type: import_types22.EdgeType.CONNECTS_TO,
6409
- provenance: import_types22.Provenance.EXTRACTED,
6410
- confidence: (0, import_types22.confidenceForExtracted)("structural"),
6618
+ type: import_types23.EdgeType.CONNECTS_TO,
6619
+ provenance: import_types23.Provenance.EXTRACTED,
6620
+ confidence: (0, import_types23.confidenceForExtracted)("structural"),
6411
6621
  evidence: { file: evidenceFile, snippet: `EXPOSE ${port}` }
6412
6622
  };
6413
6623
  graph.addEdgeWithKey(portEdgeId, portEdge.source, portEdge.target, portEdge);
@@ -6419,23 +6629,23 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
6419
6629
 
6420
6630
  // src/extract/infra/terraform.ts
6421
6631
  init_cjs_shims();
6422
- var import_node_fs16 = require("fs");
6423
- var import_node_path32 = __toESM(require("path"), 1);
6424
- var import_types23 = require("@neat.is/types");
6632
+ var import_node_fs17 = require("fs");
6633
+ var import_node_path33 = __toESM(require("path"), 1);
6634
+ var import_types24 = require("@neat.is/types");
6425
6635
  var RESOURCE_RE = /resource\s+"(aws_[A-Za-z0-9_]+)"\s+"([A-Za-z0-9_-]+)"/g;
6426
6636
  var REFERENCE_RE = /(?<![\w.])(aws_[A-Za-z0-9_]+)\.([A-Za-z0-9_-]+)/g;
6427
6637
  async function walkTfFiles(start, depth = 0, max = 5) {
6428
6638
  if (depth > max) return [];
6429
6639
  const out = [];
6430
- const entries = await import_node_fs16.promises.readdir(start, { withFileTypes: true }).catch(() => []);
6640
+ const entries = await import_node_fs17.promises.readdir(start, { withFileTypes: true }).catch(() => []);
6431
6641
  for (const entry of entries) {
6432
6642
  if (entry.isDirectory()) {
6433
6643
  if (IGNORED_DIRS.has(entry.name) || entry.name === ".terraform") continue;
6434
- const child = import_node_path32.default.join(start, entry.name);
6644
+ const child = import_node_path33.default.join(start, entry.name);
6435
6645
  if (await isPythonVenvDir(child)) continue;
6436
6646
  out.push(...await walkTfFiles(child, depth + 1, max));
6437
6647
  } else if (entry.isFile() && entry.name.endsWith(".tf")) {
6438
- out.push(import_node_path32.default.join(start, entry.name));
6648
+ out.push(import_node_path33.default.join(start, entry.name));
6439
6649
  }
6440
6650
  }
6441
6651
  return out;
@@ -6454,7 +6664,7 @@ function blockBody(content, from) {
6454
6664
  }
6455
6665
  return null;
6456
6666
  }
6457
- function lineAt(content, index) {
6667
+ function lineAt2(content, index) {
6458
6668
  let line = 1;
6459
6669
  for (let i = 0; i < index && i < content.length; i++) {
6460
6670
  if (content[i] === "\n") line++;
@@ -6466,8 +6676,8 @@ async function addTerraformResources(graph, scanPath) {
6466
6676
  let edgesAdded = 0;
6467
6677
  const files = await walkTfFiles(scanPath);
6468
6678
  for (const file of files) {
6469
- const content = await import_node_fs16.promises.readFile(file, "utf8");
6470
- const evidenceFile = toPosix2(import_node_path32.default.relative(scanPath, file));
6679
+ const content = await import_node_fs17.promises.readFile(file, "utf8");
6680
+ const evidenceFile = toPosix2(import_node_path33.default.relative(scanPath, file));
6471
6681
  const resources = [];
6472
6682
  const byKey = /* @__PURE__ */ new Map();
6473
6683
  RESOURCE_RE.lastIndex = 0;
@@ -6502,16 +6712,16 @@ async function addTerraformResources(graph, scanPath) {
6502
6712
  if (!target) continue;
6503
6713
  if (seen.has(target.nodeId)) continue;
6504
6714
  seen.add(target.nodeId);
6505
- const edgeId = (0, import_types4.extractedEdgeId)(resource.nodeId, target.nodeId, import_types23.EdgeType.DEPENDS_ON);
6715
+ const edgeId = (0, import_types4.extractedEdgeId)(resource.nodeId, target.nodeId, import_types24.EdgeType.DEPENDS_ON);
6506
6716
  if (graph.hasEdge(edgeId)) continue;
6507
- const line = lineAt(content, resource.bodyOffset + ref.index);
6717
+ const line = lineAt2(content, resource.bodyOffset + ref.index);
6508
6718
  const edge = {
6509
6719
  id: edgeId,
6510
6720
  source: resource.nodeId,
6511
6721
  target: target.nodeId,
6512
- type: import_types23.EdgeType.DEPENDS_ON,
6513
- provenance: import_types23.Provenance.EXTRACTED,
6514
- confidence: (0, import_types23.confidenceForExtracted)("structural"),
6722
+ type: import_types24.EdgeType.DEPENDS_ON,
6723
+ provenance: import_types24.Provenance.EXTRACTED,
6724
+ confidence: (0, import_types24.confidenceForExtracted)("structural"),
6515
6725
  evidence: { file: evidenceFile, line, snippet: key }
6516
6726
  };
6517
6727
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -6524,8 +6734,8 @@ async function addTerraformResources(graph, scanPath) {
6524
6734
 
6525
6735
  // src/extract/infra/k8s.ts
6526
6736
  init_cjs_shims();
6527
- var import_node_fs17 = require("fs");
6528
- var import_node_path33 = __toESM(require("path"), 1);
6737
+ var import_node_fs18 = require("fs");
6738
+ var import_node_path34 = __toESM(require("path"), 1);
6529
6739
  var import_yaml3 = require("yaml");
6530
6740
  var K8S_KIND_TO_INFRA_KIND = {
6531
6741
  Service: "k8s-service",
@@ -6539,15 +6749,15 @@ var K8S_KIND_TO_INFRA_KIND = {
6539
6749
  async function walkYamlFiles2(start, depth = 0, max = 5) {
6540
6750
  if (depth > max) return [];
6541
6751
  const out = [];
6542
- const entries = await import_node_fs17.promises.readdir(start, { withFileTypes: true }).catch(() => []);
6752
+ const entries = await import_node_fs18.promises.readdir(start, { withFileTypes: true }).catch(() => []);
6543
6753
  for (const entry of entries) {
6544
6754
  if (entry.isDirectory()) {
6545
6755
  if (IGNORED_DIRS.has(entry.name)) continue;
6546
- const child = import_node_path33.default.join(start, entry.name);
6756
+ const child = import_node_path34.default.join(start, entry.name);
6547
6757
  if (await isPythonVenvDir(child)) continue;
6548
6758
  out.push(...await walkYamlFiles2(child, depth + 1, max));
6549
- } else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path33.default.extname(entry.name))) {
6550
- out.push(import_node_path33.default.join(start, entry.name));
6759
+ } else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path34.default.extname(entry.name))) {
6760
+ out.push(import_node_path34.default.join(start, entry.name));
6551
6761
  }
6552
6762
  }
6553
6763
  return out;
@@ -6556,7 +6766,7 @@ async function addK8sResources(graph, scanPath) {
6556
6766
  let nodesAdded = 0;
6557
6767
  const files = await walkYamlFiles2(scanPath);
6558
6768
  for (const file of files) {
6559
- const content = await import_node_fs17.promises.readFile(file, "utf8");
6769
+ const content = await import_node_fs18.promises.readFile(file, "utf8");
6560
6770
  let docs;
6561
6771
  try {
6562
6772
  docs = (0, import_yaml3.parseAllDocuments)(content).map((d) => d.toJSON());
@@ -6591,17 +6801,17 @@ async function addInfra(graph, scanPath, services) {
6591
6801
  }
6592
6802
 
6593
6803
  // src/extract/index.ts
6594
- var import_node_path35 = __toESM(require("path"), 1);
6804
+ var import_node_path36 = __toESM(require("path"), 1);
6595
6805
 
6596
6806
  // src/extract/retire.ts
6597
6807
  init_cjs_shims();
6598
- var import_node_fs18 = require("fs");
6599
- var import_node_path34 = __toESM(require("path"), 1);
6600
- var import_types24 = require("@neat.is/types");
6808
+ var import_node_fs19 = require("fs");
6809
+ var import_node_path35 = __toESM(require("path"), 1);
6810
+ var import_types25 = require("@neat.is/types");
6601
6811
  function dropOrphanedFileNodes(graph) {
6602
6812
  const orphans = [];
6603
6813
  graph.forEachNode((id, attrs) => {
6604
- if (attrs.type !== import_types24.NodeType.FileNode) return;
6814
+ if (attrs.type !== import_types25.NodeType.FileNode) return;
6605
6815
  if (graph.inboundEdges(id).length === 0 && graph.outboundEdges(id).length === 0) {
6606
6816
  orphans.push(id);
6607
6817
  }
@@ -6614,14 +6824,14 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
6614
6824
  const bases = [scanPath, ...serviceDirs];
6615
6825
  graph.forEachEdge((id, attrs) => {
6616
6826
  const edge = attrs;
6617
- if (edge.provenance !== import_types24.Provenance.EXTRACTED) return;
6827
+ if (edge.provenance !== import_types25.Provenance.EXTRACTED) return;
6618
6828
  const evidenceFile = edge.evidence?.file;
6619
6829
  if (!evidenceFile) return;
6620
- if (import_node_path34.default.isAbsolute(evidenceFile)) {
6621
- if (!(0, import_node_fs18.existsSync)(evidenceFile)) toDrop.push(id);
6830
+ if (import_node_path35.default.isAbsolute(evidenceFile)) {
6831
+ if (!(0, import_node_fs19.existsSync)(evidenceFile)) toDrop.push(id);
6622
6832
  return;
6623
6833
  }
6624
- const found = bases.some((base) => (0, import_node_fs18.existsSync)(import_node_path34.default.join(base, evidenceFile)));
6834
+ const found = bases.some((base) => (0, import_node_fs19.existsSync)(import_node_path35.default.join(base, evidenceFile)));
6625
6835
  if (!found) toDrop.push(id);
6626
6836
  });
6627
6837
  for (const id of toDrop) graph.dropEdge(id);
@@ -6641,6 +6851,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
6641
6851
  const phase2 = await addDatabasesAndCompat(graph, services, scanPath);
6642
6852
  const phase3 = await addConfigNodes(graph, services, scanPath);
6643
6853
  const routePhase = await addRoutes(graph, services);
6854
+ const grpcPhase = await addGrpcMethods(graph, services);
6644
6855
  const phase4 = await addCallEdges(graph, services);
6645
6856
  const phase5 = await addInfra(graph, scanPath, services);
6646
6857
  const ghostsRetired = retireExtractedEdgesByMissingFile(
@@ -6662,7 +6873,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
6662
6873
  }
6663
6874
  const droppedEntries = drainDroppedExtracted();
6664
6875
  if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
6665
- const rejectedPath = import_node_path35.default.join(import_node_path35.default.dirname(opts.errorsPath), "rejected.ndjson");
6876
+ const rejectedPath = import_node_path36.default.join(import_node_path36.default.dirname(opts.errorsPath), "rejected.ndjson");
6666
6877
  try {
6667
6878
  await writeRejectedExtracted(droppedEntries, rejectedPath);
6668
6879
  } catch (err) {
@@ -6672,8 +6883,8 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
6672
6883
  }
6673
6884
  }
6674
6885
  const result = {
6675
- nodesAdded: phase1Nodes + fileEnum.nodesAdded + importGraph.nodesAdded + phase2.nodesAdded + phase3.nodesAdded + routePhase.nodesAdded + phase4.nodesAdded + phase5.nodesAdded,
6676
- edgesAdded: fileEnum.edgesAdded + importGraph.edgesAdded + phase2.edgesAdded + phase3.edgesAdded + routePhase.edgesAdded + phase4.edgesAdded + phase5.edgesAdded,
6886
+ nodesAdded: phase1Nodes + fileEnum.nodesAdded + importGraph.nodesAdded + phase2.nodesAdded + phase3.nodesAdded + routePhase.nodesAdded + grpcPhase.nodesAdded + phase4.nodesAdded + phase5.nodesAdded,
6887
+ edgesAdded: fileEnum.edgesAdded + importGraph.edgesAdded + phase2.edgesAdded + phase3.edgesAdded + routePhase.edgesAdded + grpcPhase.edgesAdded + phase4.edgesAdded + phase5.edgesAdded,
6677
6888
  frontiersPromoted,
6678
6889
  extractionErrors: errorEntries.length,
6679
6890
  errorEntries,
@@ -6696,9 +6907,9 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
6696
6907
 
6697
6908
  // src/persist.ts
6698
6909
  init_cjs_shims();
6699
- var import_node_fs19 = require("fs");
6700
- var import_node_path36 = __toESM(require("path"), 1);
6701
- var import_types25 = require("@neat.is/types");
6910
+ var import_node_fs20 = require("fs");
6911
+ var import_node_path37 = __toESM(require("path"), 1);
6912
+ var import_types26 = require("@neat.is/types");
6702
6913
  var SCHEMA_VERSION = 4;
6703
6914
  function migrateV1ToV2(payload) {
6704
6915
  const nodes = payload.graph.nodes;
@@ -6720,12 +6931,12 @@ function migrateV2ToV3(payload) {
6720
6931
  for (const edge of edges) {
6721
6932
  const attrs = edge.attributes;
6722
6933
  if (!attrs || attrs.provenance !== "FRONTIER") continue;
6723
- attrs.provenance = import_types25.Provenance.OBSERVED;
6934
+ attrs.provenance = import_types26.Provenance.OBSERVED;
6724
6935
  const type = typeof attrs.type === "string" ? attrs.type : void 0;
6725
6936
  const source = typeof attrs.source === "string" ? attrs.source : void 0;
6726
6937
  const target = typeof attrs.target === "string" ? attrs.target : void 0;
6727
6938
  if (type && source && target) {
6728
- const newId = (0, import_types25.observedEdgeId)(source, target, type);
6939
+ const newId = (0, import_types26.observedEdgeId)(source, target, type);
6729
6940
  attrs.id = newId;
6730
6941
  if (edge.key) edge.key = newId;
6731
6942
  }
@@ -6734,7 +6945,7 @@ function migrateV2ToV3(payload) {
6734
6945
  return { ...payload, schemaVersion: 3 };
6735
6946
  }
6736
6947
  async function ensureDir(filePath) {
6737
- await import_node_fs19.promises.mkdir(import_node_path36.default.dirname(filePath), { recursive: true });
6948
+ await import_node_fs20.promises.mkdir(import_node_path37.default.dirname(filePath), { recursive: true });
6738
6949
  }
6739
6950
  async function saveGraphToDisk(graph, outPath) {
6740
6951
  await ensureDir(outPath);
@@ -6744,13 +6955,13 @@ async function saveGraphToDisk(graph, outPath) {
6744
6955
  graph: graph.export()
6745
6956
  };
6746
6957
  const tmp = `${outPath}.tmp`;
6747
- await import_node_fs19.promises.writeFile(tmp, JSON.stringify(payload), "utf8");
6748
- await import_node_fs19.promises.rename(tmp, outPath);
6958
+ await import_node_fs20.promises.writeFile(tmp, JSON.stringify(payload), "utf8");
6959
+ await import_node_fs20.promises.rename(tmp, outPath);
6749
6960
  }
6750
6961
  async function loadGraphFromDisk(graph, outPath) {
6751
6962
  let raw;
6752
6963
  try {
6753
- raw = await import_node_fs19.promises.readFile(outPath, "utf8");
6964
+ raw = await import_node_fs20.promises.readFile(outPath, "utf8");
6754
6965
  } catch (err) {
6755
6966
  if (err.code === "ENOENT") return;
6756
6967
  throw err;
@@ -6817,19 +7028,19 @@ function startPersistLoop(graph, outPath, opts = {}) {
6817
7028
  init_cjs_shims();
6818
7029
  var import_fastify = __toESM(require("fastify"), 1);
6819
7030
  var import_cors = __toESM(require("@fastify/cors"), 1);
6820
- var import_types28 = require("@neat.is/types");
7031
+ var import_types29 = require("@neat.is/types");
6821
7032
 
6822
7033
  // src/extend/index.ts
6823
7034
  init_cjs_shims();
6824
- var import_node_fs21 = require("fs");
6825
- var import_node_path38 = __toESM(require("path"), 1);
7035
+ var import_node_fs22 = require("fs");
7036
+ var import_node_path39 = __toESM(require("path"), 1);
6826
7037
  var import_node_os2 = __toESM(require("os"), 1);
6827
7038
  var import_instrumentation_registry = require("@neat.is/instrumentation-registry");
6828
7039
 
6829
7040
  // src/installers/package-manager.ts
6830
7041
  init_cjs_shims();
6831
- var import_node_fs20 = require("fs");
6832
- var import_node_path37 = __toESM(require("path"), 1);
7042
+ var import_node_fs21 = require("fs");
7043
+ var import_node_path38 = __toESM(require("path"), 1);
6833
7044
  var import_node_child_process = require("child_process");
6834
7045
  var LOCKFILE_PRIORITY = [
6835
7046
  { lockfile: "bun.lockb", pm: "bun", args: ["install", "--no-summary"] },
@@ -6844,29 +7055,29 @@ var LOCKFILE_PRIORITY = [
6844
7055
  var NPM_FALLBACK_ARGS = ["install", "--no-audit", "--no-fund", "--prefer-offline"];
6845
7056
  async function exists2(p) {
6846
7057
  try {
6847
- await import_node_fs20.promises.access(p);
7058
+ await import_node_fs21.promises.access(p);
6848
7059
  return true;
6849
7060
  } catch {
6850
7061
  return false;
6851
7062
  }
6852
7063
  }
6853
7064
  async function detectPackageManager(serviceDir) {
6854
- let dir = import_node_path37.default.resolve(serviceDir);
7065
+ let dir = import_node_path38.default.resolve(serviceDir);
6855
7066
  const stops = /* @__PURE__ */ new Set();
6856
7067
  for (let i = 0; i < 64; i++) {
6857
7068
  if (stops.has(dir)) break;
6858
7069
  stops.add(dir);
6859
7070
  for (const candidate of LOCKFILE_PRIORITY) {
6860
- const lockPath = import_node_path37.default.join(dir, candidate.lockfile);
7071
+ const lockPath = import_node_path38.default.join(dir, candidate.lockfile);
6861
7072
  if (await exists2(lockPath)) {
6862
7073
  return { pm: candidate.pm, cwd: dir, args: [...candidate.args] };
6863
7074
  }
6864
7075
  }
6865
- const parent = import_node_path37.default.dirname(dir);
7076
+ const parent = import_node_path38.default.dirname(dir);
6866
7077
  if (parent === dir) break;
6867
7078
  dir = parent;
6868
7079
  }
6869
- return { pm: "npm", cwd: import_node_path37.default.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
7080
+ return { pm: "npm", cwd: import_node_path38.default.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
6870
7081
  }
6871
7082
  async function runPackageManagerInstall(cmd) {
6872
7083
  return new Promise((resolve) => {
@@ -6908,30 +7119,30 @@ ${err.message}`
6908
7119
  // src/extend/index.ts
6909
7120
  async function fileExists2(p) {
6910
7121
  try {
6911
- await import_node_fs21.promises.access(p);
7122
+ await import_node_fs22.promises.access(p);
6912
7123
  return true;
6913
7124
  } catch {
6914
7125
  return false;
6915
7126
  }
6916
7127
  }
6917
7128
  async function readPackageJson(scanPath) {
6918
- const pkgPath = import_node_path38.default.join(scanPath, "package.json");
6919
- const raw = await import_node_fs21.promises.readFile(pkgPath, "utf8");
7129
+ const pkgPath = import_node_path39.default.join(scanPath, "package.json");
7130
+ const raw = await import_node_fs22.promises.readFile(pkgPath, "utf8");
6920
7131
  return JSON.parse(raw);
6921
7132
  }
6922
7133
  async function findHookFiles(scanPath) {
6923
- const entries = await import_node_fs21.promises.readdir(scanPath);
7134
+ const entries = await import_node_fs22.promises.readdir(scanPath);
6924
7135
  return entries.filter(
6925
7136
  (e) => (e.startsWith("instrumentation") || e.startsWith("otel-init")) && /\.(ts|js)$/.test(e)
6926
7137
  ).sort();
6927
7138
  }
6928
7139
  function extendLogPath() {
6929
- return process.env.NEAT_EXTEND_LOG ?? import_node_path38.default.join(import_node_os2.default.homedir(), ".neat", "extend-log.ndjson");
7140
+ return process.env.NEAT_EXTEND_LOG ?? import_node_path39.default.join(import_node_os2.default.homedir(), ".neat", "extend-log.ndjson");
6930
7141
  }
6931
7142
  async function appendExtendLog(entry) {
6932
7143
  const logPath = extendLogPath();
6933
- await import_node_fs21.promises.mkdir(import_node_path38.default.dirname(logPath), { recursive: true });
6934
- await import_node_fs21.promises.appendFile(logPath, JSON.stringify(entry) + "\n", "utf8");
7144
+ await import_node_fs22.promises.mkdir(import_node_path39.default.dirname(logPath), { recursive: true });
7145
+ await import_node_fs22.promises.appendFile(logPath, JSON.stringify(entry) + "\n", "utf8");
6935
7146
  }
6936
7147
  function splicedContent(fileContent, snippet2) {
6937
7148
  if (fileContent.includes("__INSTRUMENTATION_BLOCK__")) {
@@ -6989,7 +7200,7 @@ function lookupInstrumentation(library, installedVersion) {
6989
7200
  }
6990
7201
  async function describeProjectInstrumentation(ctx) {
6991
7202
  const hookFiles = await findHookFiles(ctx.scanPath);
6992
- const envNeat = await fileExists2(import_node_path38.default.join(ctx.scanPath, ".env.neat"));
7203
+ const envNeat = await fileExists2(import_node_path39.default.join(ctx.scanPath, ".env.neat"));
6993
7204
  const registryInstrPackages = new Set(
6994
7205
  (0, import_instrumentation_registry.list)().map((e) => e.instrumentation_package).filter((p) => !!p)
6995
7206
  );
@@ -7011,31 +7222,31 @@ async function applyExtension(ctx, args, options) {
7011
7222
  );
7012
7223
  }
7013
7224
  for (const file of hookFiles) {
7014
- const content = await import_node_fs21.promises.readFile(import_node_path38.default.join(ctx.scanPath, file), "utf8");
7225
+ const content = await import_node_fs22.promises.readFile(import_node_path39.default.join(ctx.scanPath, file), "utf8");
7015
7226
  if (content.includes(args.registration_snippet)) {
7016
7227
  return { library: args.library, filesTouched: [], depsAdded: [], installOutput: "", alreadyApplied: true };
7017
7228
  }
7018
7229
  }
7019
7230
  const primaryFile = hookFiles[0];
7020
- const primaryPath = import_node_path38.default.join(ctx.scanPath, primaryFile);
7231
+ const primaryPath = import_node_path39.default.join(ctx.scanPath, primaryFile);
7021
7232
  const filesTouched = [];
7022
7233
  const depsAdded = [];
7023
- const pkgPath = import_node_path38.default.join(ctx.scanPath, "package.json");
7234
+ const pkgPath = import_node_path39.default.join(ctx.scanPath, "package.json");
7024
7235
  const pkg = await readPackageJson(ctx.scanPath);
7025
7236
  if (!(pkg.dependencies ?? {})[args.instrumentation_package]) {
7026
7237
  pkg.dependencies = { ...pkg.dependencies ?? {}, [args.instrumentation_package]: args.version };
7027
- await import_node_fs21.promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
7238
+ await import_node_fs22.promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
7028
7239
  filesTouched.push("package.json");
7029
7240
  depsAdded.push(`${args.instrumentation_package}@${args.version}`);
7030
7241
  }
7031
- const hookContent = await import_node_fs21.promises.readFile(primaryPath, "utf8");
7242
+ const hookContent = await import_node_fs22.promises.readFile(primaryPath, "utf8");
7032
7243
  const patched = splicedContent(hookContent, args.registration_snippet);
7033
7244
  if (!patched) {
7034
7245
  throw new Error(
7035
7246
  `Could not find instrumentation insertion point in ${primaryFile}. Expected __INSTRUMENTATION_BLOCK__, instrumentations.push(, or new NodeSDK(.`
7036
7247
  );
7037
7248
  }
7038
- await import_node_fs21.promises.writeFile(primaryPath, patched, "utf8");
7249
+ await import_node_fs22.promises.writeFile(primaryPath, patched, "utf8");
7039
7250
  filesTouched.push(primaryFile);
7040
7251
  const cmd = await detectPackageManager(ctx.scanPath);
7041
7252
  const installer = options?.runInstall ?? runPackageManagerInstall;
@@ -7066,7 +7277,7 @@ async function dryRunExtension(ctx, args) {
7066
7277
  };
7067
7278
  }
7068
7279
  for (const file of hookFiles) {
7069
- const content = await import_node_fs21.promises.readFile(import_node_path38.default.join(ctx.scanPath, file), "utf8");
7280
+ const content = await import_node_fs22.promises.readFile(import_node_path39.default.join(ctx.scanPath, file), "utf8");
7070
7281
  if (content.includes(args.registration_snippet)) {
7071
7282
  return {
7072
7283
  library: args.library,
@@ -7088,7 +7299,7 @@ async function dryRunExtension(ctx, args) {
7088
7299
  depsToAdd.push(`${args.instrumentation_package}@${args.version}`);
7089
7300
  filesTouched.push("package.json");
7090
7301
  }
7091
- const hookContent = await import_node_fs21.promises.readFile(import_node_path38.default.join(ctx.scanPath, primaryFile), "utf8");
7302
+ const hookContent = await import_node_fs22.promises.readFile(import_node_path39.default.join(ctx.scanPath, primaryFile), "utf8");
7092
7303
  const patched = splicedContent(hookContent, args.registration_snippet);
7093
7304
  if (patched) {
7094
7305
  filesTouched.push(primaryFile);
@@ -7103,28 +7314,28 @@ async function rollbackExtension(ctx, args) {
7103
7314
  if (!await fileExists2(logPath)) {
7104
7315
  return { undone: false, message: "no apply found for library" };
7105
7316
  }
7106
- const raw = await import_node_fs21.promises.readFile(logPath, "utf8");
7317
+ const raw = await import_node_fs22.promises.readFile(logPath, "utf8");
7107
7318
  const entries = raw.trim().split("\n").filter(Boolean).map((line) => JSON.parse(line));
7108
7319
  const match = [...entries].reverse().find((e) => e.project === ctx.project && e.library === args.library);
7109
7320
  if (!match) {
7110
7321
  return { undone: false, message: "no apply found for library" };
7111
7322
  }
7112
- const pkgPath = import_node_path38.default.join(ctx.scanPath, "package.json");
7323
+ const pkgPath = import_node_path39.default.join(ctx.scanPath, "package.json");
7113
7324
  if (await fileExists2(pkgPath)) {
7114
7325
  const pkg = await readPackageJson(ctx.scanPath);
7115
7326
  if (pkg.dependencies?.[match.instrumentation_package]) {
7116
7327
  const { [match.instrumentation_package]: _removed, ...rest } = pkg.dependencies;
7117
7328
  pkg.dependencies = rest;
7118
- await import_node_fs21.promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
7329
+ await import_node_fs22.promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
7119
7330
  }
7120
7331
  }
7121
7332
  const hookFiles = await findHookFiles(ctx.scanPath);
7122
7333
  for (const file of hookFiles) {
7123
- const filePath = import_node_path38.default.join(ctx.scanPath, file);
7124
- const content = await import_node_fs21.promises.readFile(filePath, "utf8");
7334
+ const filePath = import_node_path39.default.join(ctx.scanPath, file);
7335
+ const content = await import_node_fs22.promises.readFile(filePath, "utf8");
7125
7336
  if (content.includes(match.registration_snippet)) {
7126
7337
  const filtered = content.split("\n").filter((line) => !line.includes(match.registration_snippet)).join("\n");
7127
- await import_node_fs21.promises.writeFile(filePath, filtered, "utf8");
7338
+ await import_node_fs22.promises.writeFile(filePath, filtered, "utf8");
7128
7339
  break;
7129
7340
  }
7130
7341
  }
@@ -7136,7 +7347,7 @@ async function rollbackExtension(ctx, args) {
7136
7347
 
7137
7348
  // src/divergences.ts
7138
7349
  init_cjs_shims();
7139
- var import_types26 = require("@neat.is/types");
7350
+ var import_types27 = require("@neat.is/types");
7140
7351
  function bucketKey(source, target, type) {
7141
7352
  return `${type}|${source}|${target}`;
7142
7353
  }
@@ -7144,22 +7355,22 @@ function bucketEdges(graph) {
7144
7355
  const buckets = /* @__PURE__ */ new Map();
7145
7356
  graph.forEachEdge((id, attrs) => {
7146
7357
  const e = attrs;
7147
- const parsed = (0, import_types26.parseEdgeId)(id);
7358
+ const parsed = (0, import_types27.parseEdgeId)(id);
7148
7359
  const provenance = parsed?.provenance ?? e.provenance;
7149
7360
  const key = bucketKey(e.source, e.target, e.type);
7150
7361
  const cur = buckets.get(key) ?? { source: e.source, target: e.target, type: e.type };
7151
7362
  switch (provenance) {
7152
- case import_types26.Provenance.EXTRACTED:
7363
+ case import_types27.Provenance.EXTRACTED:
7153
7364
  cur.extracted = e;
7154
7365
  break;
7155
- case import_types26.Provenance.OBSERVED:
7366
+ case import_types27.Provenance.OBSERVED:
7156
7367
  cur.observed = e;
7157
7368
  break;
7158
- case import_types26.Provenance.INFERRED:
7369
+ case import_types27.Provenance.INFERRED:
7159
7370
  cur.inferred = e;
7160
7371
  break;
7161
7372
  default:
7162
- if (e.provenance === import_types26.Provenance.STALE) cur.stale = e;
7373
+ if (e.provenance === import_types27.Provenance.STALE) cur.stale = e;
7163
7374
  }
7164
7375
  buckets.set(key, cur);
7165
7376
  });
@@ -7168,7 +7379,12 @@ function bucketEdges(graph) {
7168
7379
  function nodeIsFrontier(graph, nodeId) {
7169
7380
  if (!graph.hasNode(nodeId)) return false;
7170
7381
  const attrs = graph.getNodeAttributes(nodeId);
7171
- return attrs.type === import_types26.NodeType.FrontierNode;
7382
+ return attrs.type === import_types27.NodeType.FrontierNode;
7383
+ }
7384
+ function nodeIsWebsocketChannel(graph, nodeId) {
7385
+ if (!graph.hasNode(nodeId)) return false;
7386
+ const attrs = graph.getNodeAttributes(nodeId);
7387
+ return attrs.type === import_types27.NodeType.WebSocketChannelNode;
7172
7388
  }
7173
7389
  function clampConfidence(n) {
7174
7390
  if (!Number.isFinite(n)) return 0;
@@ -7188,14 +7404,14 @@ function gradedConfidence(edge) {
7188
7404
  return clampConfidence(confidenceForEdge(edge));
7189
7405
  }
7190
7406
  var OBSERVABLE_EDGE_TYPES = /* @__PURE__ */ new Set([
7191
- import_types26.EdgeType.CALLS,
7192
- import_types26.EdgeType.CONNECTS_TO,
7193
- import_types26.EdgeType.PUBLISHES_TO,
7194
- import_types26.EdgeType.CONSUMES_FROM
7407
+ import_types27.EdgeType.CALLS,
7408
+ import_types27.EdgeType.CONNECTS_TO,
7409
+ import_types27.EdgeType.PUBLISHES_TO,
7410
+ import_types27.EdgeType.CONSUMES_FROM
7195
7411
  ]);
7196
7412
  function detectMissingDivergences(graph, bucket) {
7197
7413
  const out = [];
7198
- if (bucket.type === import_types26.EdgeType.CONTAINS) return out;
7414
+ if (bucket.type === import_types27.EdgeType.CONTAINS) return out;
7199
7415
  if (bucket.extracted && !bucket.observed && OBSERVABLE_EDGE_TYPES.has(bucket.type)) {
7200
7416
  if (!nodeIsFrontier(graph, bucket.target)) {
7201
7417
  out.push({
@@ -7210,7 +7426,7 @@ function detectMissingDivergences(graph, bucket) {
7210
7426
  });
7211
7427
  }
7212
7428
  }
7213
- if (bucket.observed && !bucket.extracted) {
7429
+ if (bucket.observed && !bucket.extracted && !nodeIsWebsocketChannel(graph, bucket.target)) {
7214
7430
  out.push({
7215
7431
  type: "missing-extracted",
7216
7432
  source: bucket.source,
@@ -7236,7 +7452,7 @@ function declaredHostFor(svc) {
7236
7452
  function hasExtractedConfiguredBy(graph, svcId) {
7237
7453
  for (const edgeId of graph.outboundEdges(svcId)) {
7238
7454
  const e = graph.getEdgeAttributes(edgeId);
7239
- if (e.type === import_types26.EdgeType.CONFIGURED_BY && e.provenance === import_types26.Provenance.EXTRACTED) {
7455
+ if (e.type === import_types27.EdgeType.CONFIGURED_BY && e.provenance === import_types27.Provenance.EXTRACTED) {
7240
7456
  return true;
7241
7457
  }
7242
7458
  }
@@ -7249,10 +7465,10 @@ function detectHostMismatch(graph, svcId, svc) {
7249
7465
  const out = [];
7250
7466
  for (const edgeId of graph.outboundEdges(svcId)) {
7251
7467
  const edge = graph.getEdgeAttributes(edgeId);
7252
- if (edge.type !== import_types26.EdgeType.CONNECTS_TO) continue;
7253
- if (edge.provenance !== import_types26.Provenance.OBSERVED) continue;
7468
+ if (edge.type !== import_types27.EdgeType.CONNECTS_TO) continue;
7469
+ if (edge.provenance !== import_types27.Provenance.OBSERVED) continue;
7254
7470
  const target = graph.getNodeAttributes(edge.target);
7255
- if (target.type !== import_types26.NodeType.DatabaseNode) continue;
7471
+ if (target.type !== import_types27.NodeType.DatabaseNode) continue;
7256
7472
  const observedHost = target.host?.trim();
7257
7473
  if (!observedHost) continue;
7258
7474
  if (observedHost === declaredHost) continue;
@@ -7274,10 +7490,10 @@ function detectCompatDivergences(graph, svcId, svc) {
7274
7490
  const deps = svc.dependencies ?? {};
7275
7491
  for (const edgeId of graph.outboundEdges(svcId)) {
7276
7492
  const edge = graph.getEdgeAttributes(edgeId);
7277
- if (edge.type !== import_types26.EdgeType.CONNECTS_TO) continue;
7278
- if (edge.provenance !== import_types26.Provenance.OBSERVED) continue;
7493
+ if (edge.type !== import_types27.EdgeType.CONNECTS_TO) continue;
7494
+ if (edge.provenance !== import_types27.Provenance.OBSERVED) continue;
7279
7495
  const target = graph.getNodeAttributes(edge.target);
7280
- if (target.type !== import_types26.NodeType.DatabaseNode) continue;
7496
+ if (target.type !== import_types27.NodeType.DatabaseNode) continue;
7281
7497
  for (const pair of compatPairs()) {
7282
7498
  if (pair.engine !== target.engine) continue;
7283
7499
  const declared = deps[pair.driver];
@@ -7336,7 +7552,7 @@ function suppressHostMismatchHalves(all) {
7336
7552
  for (const d of all) {
7337
7553
  if (d.type !== "host-mismatch") continue;
7338
7554
  observedHalf.add(`${d.source}->${d.target}`);
7339
- declaredHalf.add((0, import_types26.databaseId)(d.extractedHost));
7555
+ declaredHalf.add((0, import_types27.databaseId)(d.extractedHost));
7340
7556
  }
7341
7557
  if (observedHalf.size === 0) return all;
7342
7558
  return all.filter((d) => {
@@ -7355,7 +7571,7 @@ function computeDivergences(graph, opts = {}) {
7355
7571
  }
7356
7572
  graph.forEachNode((nodeId, attrs) => {
7357
7573
  const n = attrs;
7358
- if (n.type !== import_types26.NodeType.ServiceNode) return;
7574
+ if (n.type !== import_types27.NodeType.ServiceNode) return;
7359
7575
  const svc = n;
7360
7576
  for (const d of detectHostMismatch(graph, nodeId, svc)) all.push(d);
7361
7577
  for (const d of detectCompatDivergences(graph, nodeId, svc)) all.push(d);
@@ -7389,7 +7605,7 @@ function computeDivergences(graph, opts = {}) {
7389
7605
  if (a.source !== b.source) return a.source.localeCompare(b.source);
7390
7606
  return a.target.localeCompare(b.target);
7391
7607
  });
7392
- return import_types26.DivergenceResultSchema.parse({
7608
+ return import_types27.DivergenceResultSchema.parse({
7393
7609
  divergences: filtered,
7394
7610
  totalAffected: filtered.length,
7395
7611
  computedAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -7398,7 +7614,7 @@ function computeDivergences(graph, opts = {}) {
7398
7614
 
7399
7615
  // src/diff.ts
7400
7616
  init_cjs_shims();
7401
- var import_node_fs22 = require("fs");
7617
+ var import_node_fs23 = require("fs");
7402
7618
  async function loadSnapshotForDiff(target) {
7403
7619
  if (/^https?:\/\//i.test(target)) {
7404
7620
  const res = await fetch(target);
@@ -7407,7 +7623,7 @@ async function loadSnapshotForDiff(target) {
7407
7623
  }
7408
7624
  return await res.json();
7409
7625
  }
7410
- const raw = await import_node_fs22.promises.readFile(target, "utf8");
7626
+ const raw = await import_node_fs23.promises.readFile(target, "utf8");
7411
7627
  return JSON.parse(raw);
7412
7628
  }
7413
7629
  function indexEntries(entries) {
@@ -7475,23 +7691,23 @@ function canonicalJson(value) {
7475
7691
 
7476
7692
  // src/projects.ts
7477
7693
  init_cjs_shims();
7478
- var import_node_path39 = __toESM(require("path"), 1);
7694
+ var import_node_path40 = __toESM(require("path"), 1);
7479
7695
  function pathsForProject(project, baseDir) {
7480
7696
  if (project === DEFAULT_PROJECT) {
7481
7697
  return {
7482
- snapshotPath: import_node_path39.default.join(baseDir, "graph.json"),
7483
- errorsPath: import_node_path39.default.join(baseDir, "errors.ndjson"),
7484
- staleEventsPath: import_node_path39.default.join(baseDir, "stale-events.ndjson"),
7485
- embeddingsCachePath: import_node_path39.default.join(baseDir, "embeddings.json"),
7486
- policyViolationsPath: import_node_path39.default.join(baseDir, "policy-violations.ndjson")
7698
+ snapshotPath: import_node_path40.default.join(baseDir, "graph.json"),
7699
+ errorsPath: import_node_path40.default.join(baseDir, "errors.ndjson"),
7700
+ staleEventsPath: import_node_path40.default.join(baseDir, "stale-events.ndjson"),
7701
+ embeddingsCachePath: import_node_path40.default.join(baseDir, "embeddings.json"),
7702
+ policyViolationsPath: import_node_path40.default.join(baseDir, "policy-violations.ndjson")
7487
7703
  };
7488
7704
  }
7489
7705
  return {
7490
- snapshotPath: import_node_path39.default.join(baseDir, `${project}.json`),
7491
- errorsPath: import_node_path39.default.join(baseDir, `errors.${project}.ndjson`),
7492
- staleEventsPath: import_node_path39.default.join(baseDir, `stale-events.${project}.ndjson`),
7493
- embeddingsCachePath: import_node_path39.default.join(baseDir, `embeddings.${project}.json`),
7494
- policyViolationsPath: import_node_path39.default.join(baseDir, `policy-violations.${project}.ndjson`)
7706
+ snapshotPath: import_node_path40.default.join(baseDir, `${project}.json`),
7707
+ errorsPath: import_node_path40.default.join(baseDir, `errors.${project}.ndjson`),
7708
+ staleEventsPath: import_node_path40.default.join(baseDir, `stale-events.${project}.ndjson`),
7709
+ embeddingsCachePath: import_node_path40.default.join(baseDir, `embeddings.${project}.json`),
7710
+ policyViolationsPath: import_node_path40.default.join(baseDir, `policy-violations.${project}.ndjson`)
7495
7711
  };
7496
7712
  }
7497
7713
  var Projects = class {
@@ -7527,25 +7743,25 @@ var Projects = class {
7527
7743
 
7528
7744
  // src/registry.ts
7529
7745
  init_cjs_shims();
7530
- var import_node_fs23 = require("fs");
7746
+ var import_node_fs24 = require("fs");
7531
7747
  var import_node_os3 = __toESM(require("os"), 1);
7532
- var import_node_path40 = __toESM(require("path"), 1);
7533
- var import_types27 = require("@neat.is/types");
7748
+ var import_node_path41 = __toESM(require("path"), 1);
7749
+ var import_types28 = require("@neat.is/types");
7534
7750
  var LOCK_TIMEOUT_MS = 5e3;
7535
7751
  var LOCK_RETRY_MS = 50;
7536
7752
  function neatHome() {
7537
7753
  const override = process.env.NEAT_HOME;
7538
- if (override && override.length > 0) return import_node_path40.default.resolve(override);
7539
- return import_node_path40.default.join(import_node_os3.default.homedir(), ".neat");
7754
+ if (override && override.length > 0) return import_node_path41.default.resolve(override);
7755
+ return import_node_path41.default.join(import_node_os3.default.homedir(), ".neat");
7540
7756
  }
7541
7757
  function registryPath() {
7542
- return import_node_path40.default.join(neatHome(), "projects.json");
7758
+ return import_node_path41.default.join(neatHome(), "projects.json");
7543
7759
  }
7544
7760
  function registryLockPath() {
7545
- return import_node_path40.default.join(neatHome(), "projects.json.lock");
7761
+ return import_node_path41.default.join(neatHome(), "projects.json.lock");
7546
7762
  }
7547
7763
  function daemonPidPath() {
7548
- return import_node_path40.default.join(neatHome(), "neatd.pid");
7764
+ return import_node_path41.default.join(neatHome(), "neatd.pid");
7549
7765
  }
7550
7766
  function isPidAliveDefault(pid) {
7551
7767
  try {
@@ -7557,7 +7773,7 @@ function isPidAliveDefault(pid) {
7557
7773
  }
7558
7774
  async function readPidFile(file) {
7559
7775
  try {
7560
- const raw = await import_node_fs23.promises.readFile(file, "utf8");
7776
+ const raw = await import_node_fs24.promises.readFile(file, "utf8");
7561
7777
  const pid = Number.parseInt(raw.trim(), 10);
7562
7778
  return Number.isInteger(pid) && pid > 0 ? pid : void 0;
7563
7779
  } catch {
@@ -7605,32 +7821,32 @@ function lockHolderMessage(holder, lockPath, timeoutMs) {
7605
7821
  }
7606
7822
  }
7607
7823
  async function normalizeProjectPath(input) {
7608
- const resolved = import_node_path40.default.resolve(input);
7824
+ const resolved = import_node_path41.default.resolve(input);
7609
7825
  try {
7610
- return await import_node_fs23.promises.realpath(resolved);
7826
+ return await import_node_fs24.promises.realpath(resolved);
7611
7827
  } catch {
7612
7828
  return resolved;
7613
7829
  }
7614
7830
  }
7615
7831
  async function writeAtomically(target, contents) {
7616
- await import_node_fs23.promises.mkdir(import_node_path40.default.dirname(target), { recursive: true });
7832
+ await import_node_fs24.promises.mkdir(import_node_path41.default.dirname(target), { recursive: true });
7617
7833
  const tmp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
7618
- const fd = await import_node_fs23.promises.open(tmp, "w");
7834
+ const fd = await import_node_fs24.promises.open(tmp, "w");
7619
7835
  try {
7620
7836
  await fd.writeFile(contents, "utf8");
7621
7837
  await fd.sync();
7622
7838
  } finally {
7623
7839
  await fd.close();
7624
7840
  }
7625
- await import_node_fs23.promises.rename(tmp, target);
7841
+ await import_node_fs24.promises.rename(tmp, target);
7626
7842
  }
7627
7843
  async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaultLockHolderProbe) {
7628
7844
  const deadline = Date.now() + timeoutMs;
7629
- await import_node_fs23.promises.mkdir(import_node_path40.default.dirname(lockPath), { recursive: true });
7845
+ await import_node_fs24.promises.mkdir(import_node_path41.default.dirname(lockPath), { recursive: true });
7630
7846
  let probedHolder = false;
7631
7847
  while (true) {
7632
7848
  try {
7633
- const fd = await import_node_fs23.promises.open(lockPath, "wx");
7849
+ const fd = await import_node_fs24.promises.open(lockPath, "wx");
7634
7850
  try {
7635
7851
  await fd.writeFile(`${process.pid}
7636
7852
  `, "utf8");
@@ -7655,7 +7871,7 @@ async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaul
7655
7871
  }
7656
7872
  }
7657
7873
  async function releaseLock(lockPath) {
7658
- await import_node_fs23.promises.unlink(lockPath).catch(() => {
7874
+ await import_node_fs24.promises.unlink(lockPath).catch(() => {
7659
7875
  });
7660
7876
  }
7661
7877
  async function withLock(fn) {
@@ -7671,7 +7887,7 @@ async function readRegistry() {
7671
7887
  const file = registryPath();
7672
7888
  let raw;
7673
7889
  try {
7674
- raw = await import_node_fs23.promises.readFile(file, "utf8");
7890
+ raw = await import_node_fs24.promises.readFile(file, "utf8");
7675
7891
  } catch (err) {
7676
7892
  if (err.code === "ENOENT") {
7677
7893
  return { version: 1, projects: [] };
@@ -7679,10 +7895,10 @@ async function readRegistry() {
7679
7895
  throw err;
7680
7896
  }
7681
7897
  const parsed = JSON.parse(raw);
7682
- return import_types27.RegistryFileSchema.parse(parsed);
7898
+ return import_types28.RegistryFileSchema.parse(parsed);
7683
7899
  }
7684
7900
  async function writeRegistry(reg) {
7685
- const validated = import_types27.RegistryFileSchema.parse(reg);
7901
+ const validated = import_types28.RegistryFileSchema.parse(reg);
7686
7902
  await writeAtomically(registryPath(), JSON.stringify(validated, null, 2) + "\n");
7687
7903
  }
7688
7904
  var ProjectNameCollisionError = class extends Error {
@@ -7776,7 +7992,7 @@ function pruneTtlMs() {
7776
7992
  }
7777
7993
  async function statPathStatus(p) {
7778
7994
  try {
7779
- const stat = await import_node_fs23.promises.stat(p);
7995
+ const stat = await import_node_fs24.promises.stat(p);
7780
7996
  return stat.isDirectory() ? "present" : "unknown";
7781
7997
  } catch (err) {
7782
7998
  return err.code === "ENOENT" ? "gone" : "unknown";
@@ -8023,11 +8239,11 @@ function registerRoutes(scope, ctx) {
8023
8239
  const candidates = req.query.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
8024
8240
  const parsed = [];
8025
8241
  for (const c of candidates) {
8026
- const r = import_types28.DivergenceTypeSchema.safeParse(c);
8242
+ const r = import_types29.DivergenceTypeSchema.safeParse(c);
8027
8243
  if (!r.success) {
8028
8244
  return reply.code(400).send({
8029
8245
  error: `unknown divergence type "${c}"`,
8030
- allowed: import_types28.DivergenceTypeSchema.options
8246
+ allowed: import_types29.DivergenceTypeSchema.options
8031
8247
  });
8032
8248
  }
8033
8249
  parsed.push(r.data);
@@ -8246,7 +8462,7 @@ function registerRoutes(scope, ctx) {
8246
8462
  const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
8247
8463
  let violations = await log.readAll();
8248
8464
  if (req.query.severity) {
8249
- const sev = import_types28.PolicySeveritySchema.safeParse(req.query.severity);
8465
+ const sev = import_types29.PolicySeveritySchema.safeParse(req.query.severity);
8250
8466
  if (!sev.success) {
8251
8467
  return reply.code(400).send({
8252
8468
  error: "invalid severity",
@@ -8285,7 +8501,7 @@ function registerRoutes(scope, ctx) {
8285
8501
  scope.post("/policies/check", async (req, reply) => {
8286
8502
  const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
8287
8503
  if (!proj) return;
8288
- const parsed = import_types28.PoliciesCheckBodySchema.safeParse(req.body ?? {});
8504
+ const parsed = import_types29.PoliciesCheckBodySchema.safeParse(req.body ?? {});
8289
8505
  if (!parsed.success) {
8290
8506
  return reply.code(400).send({
8291
8507
  error: "invalid /policies/check body",
@@ -8547,16 +8763,95 @@ init_otel_grpc();
8547
8763
 
8548
8764
  // src/daemon.ts
8549
8765
  init_cjs_shims();
8550
- var import_node_fs25 = require("fs");
8551
- var import_node_path44 = __toESM(require("path"), 1);
8766
+ var import_node_fs26 = require("fs");
8767
+ var import_node_path45 = __toESM(require("path"), 1);
8552
8768
  var import_node_module = require("module");
8553
8769
  init_otel();
8770
+
8771
+ // src/connectors/index.ts
8772
+ init_cjs_shims();
8773
+ var NO_ENV = "unknown";
8774
+ async function runConnectorPoll(connector, ctx, graph, resolveTarget) {
8775
+ const signals = await connector.poll(ctx);
8776
+ let edgesCreated = 0;
8777
+ let edgesUpdated = 0;
8778
+ let unresolved = 0;
8779
+ for (const signal of signals) {
8780
+ const resolved = resolveTarget(signal, ctx);
8781
+ if (!resolved) {
8782
+ unresolved++;
8783
+ continue;
8784
+ }
8785
+ const serviceNodeId = ensureServiceNode(graph, resolved.serviceName, NO_ENV);
8786
+ const callSite = signal.callSite ? { relPath: signal.callSite.file, line: signal.callSite.line } : void 0;
8787
+ const sourceId = callSite ? ensureObservedFileNode(graph, resolved.serviceName, serviceNodeId, callSite) : serviceNodeId;
8788
+ const evidence = callSite ? {
8789
+ file: reconcileObservedRelPath(graph, resolved.serviceName, callSite.relPath),
8790
+ line: callSite.line
8791
+ } : void 0;
8792
+ const calls = Math.trunc(signal.callCount);
8793
+ if (calls < 1) continue;
8794
+ const errors = Math.min(Math.max(Math.trunc(signal.errorCount), 0), calls);
8795
+ let created = false;
8796
+ let ok = true;
8797
+ for (let i = 0; i < calls; i++) {
8798
+ const result = upsertObservedEdge(
8799
+ graph,
8800
+ resolved.edgeType,
8801
+ sourceId,
8802
+ resolved.targetNodeId,
8803
+ signal.lastObservedIso,
8804
+ i < errors,
8805
+ evidence
8806
+ );
8807
+ if (!result) {
8808
+ ok = false;
8809
+ break;
8810
+ }
8811
+ if (i === 0) created = result.created;
8812
+ }
8813
+ if (!ok) {
8814
+ unresolved++;
8815
+ continue;
8816
+ }
8817
+ if (created) edgesCreated++;
8818
+ else edgesUpdated++;
8819
+ }
8820
+ return { signalCount: signals.length, edgesCreated, edgesUpdated, unresolved };
8821
+ }
8822
+ var DEFAULT_POLL_INTERVAL_MS = 6e4;
8823
+ function startConnectorPollLoop(connector, ctx, graph, resolveTarget, options = {}) {
8824
+ let stopped = false;
8825
+ let since = ctx.since;
8826
+ const intervalMs = options.intervalMs ?? DEFAULT_POLL_INTERVAL_MS;
8827
+ const onError = options.onError ?? ((err) => console.error(`[neatd] connector poll failed (${connector.provider})`, err));
8828
+ const tick = () => {
8829
+ if (stopped) return;
8830
+ void (async () => {
8831
+ const tickStartedAt = (/* @__PURE__ */ new Date()).toISOString();
8832
+ try {
8833
+ await runConnectorPoll(connector, { ...ctx, since }, graph, resolveTarget);
8834
+ since = tickStartedAt;
8835
+ } catch (err) {
8836
+ onError(err);
8837
+ }
8838
+ })();
8839
+ };
8840
+ const interval = setInterval(tick, intervalMs);
8841
+ if (typeof interval.unref === "function") interval.unref();
8842
+ return () => {
8843
+ stopped = true;
8844
+ clearInterval(interval);
8845
+ };
8846
+ }
8847
+
8848
+ // src/daemon.ts
8554
8849
  init_auth();
8555
8850
 
8556
8851
  // src/unrouted.ts
8557
8852
  init_cjs_shims();
8558
- var import_node_fs24 = require("fs");
8559
- var import_node_path43 = __toESM(require("path"), 1);
8853
+ var import_node_fs25 = require("fs");
8854
+ var import_node_path44 = __toESM(require("path"), 1);
8560
8855
  function buildUnroutedSpanRecord(serviceName, traceId, now = /* @__PURE__ */ new Date()) {
8561
8856
  return {
8562
8857
  timestamp: now.toISOString(),
@@ -8566,34 +8861,34 @@ function buildUnroutedSpanRecord(serviceName, traceId, now = /* @__PURE__ */ new
8566
8861
  };
8567
8862
  }
8568
8863
  async function appendUnroutedSpan(neatHome2, record) {
8569
- const target = import_node_path43.default.join(neatHome2, "errors.ndjson");
8570
- await import_node_fs24.promises.mkdir(neatHome2, { recursive: true });
8571
- await import_node_fs24.promises.appendFile(target, JSON.stringify(record) + "\n", "utf8");
8864
+ const target = import_node_path44.default.join(neatHome2, "errors.ndjson");
8865
+ await import_node_fs25.promises.mkdir(neatHome2, { recursive: true });
8866
+ await import_node_fs25.promises.appendFile(target, JSON.stringify(record) + "\n", "utf8");
8572
8867
  }
8573
8868
  function unroutedErrorsPath(neatHome2) {
8574
- return import_node_path43.default.join(neatHome2, "errors.ndjson");
8869
+ return import_node_path44.default.join(neatHome2, "errors.ndjson");
8575
8870
  }
8576
8871
 
8577
8872
  // src/daemon.ts
8578
- var import_types29 = require("@neat.is/types");
8873
+ var import_types30 = require("@neat.is/types");
8579
8874
  function daemonJsonPath(scanPath) {
8580
- return import_node_path44.default.join(scanPath, "neat-out", "daemon.json");
8875
+ return import_node_path45.default.join(scanPath, "neat-out", "daemon.json");
8581
8876
  }
8582
8877
  function daemonsDiscoveryDir(home) {
8583
8878
  const base = home && home.length > 0 ? home : neatHomeFromEnv();
8584
- return import_node_path44.default.join(base, "daemons");
8879
+ return import_node_path45.default.join(base, "daemons");
8585
8880
  }
8586
8881
  function daemonDiscoveryPath(project, home) {
8587
- return import_node_path44.default.join(daemonsDiscoveryDir(home), `${sanitizeDiscoveryName(project)}.json`);
8882
+ return import_node_path45.default.join(daemonsDiscoveryDir(home), `${sanitizeDiscoveryName(project)}.json`);
8588
8883
  }
8589
8884
  function sanitizeDiscoveryName(project) {
8590
8885
  return project.replace(/[^A-Za-z0-9._-]/g, "_");
8591
8886
  }
8592
8887
  function neatHomeFromEnv() {
8593
8888
  const env = process.env.NEAT_HOME;
8594
- if (env && env.length > 0) return import_node_path44.default.resolve(env);
8889
+ if (env && env.length > 0) return import_node_path45.default.resolve(env);
8595
8890
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
8596
- return import_node_path44.default.join(home, ".neat");
8891
+ return import_node_path45.default.join(home, ".neat");
8597
8892
  }
8598
8893
  function resolveNeatVersion() {
8599
8894
  if (process.env.NEAT_LOCAL_VERSION && process.env.NEAT_LOCAL_VERSION.length > 0) {
@@ -8625,7 +8920,7 @@ async function clearDaemonRecord(record, home) {
8625
8920
  } catch {
8626
8921
  }
8627
8922
  try {
8628
- await import_node_fs25.promises.unlink(daemonDiscoveryPath(record.project, home));
8923
+ await import_node_fs26.promises.unlink(daemonDiscoveryPath(record.project, home));
8629
8924
  } catch {
8630
8925
  }
8631
8926
  }
@@ -8638,17 +8933,21 @@ function teardownSlot(slot) {
8638
8933
  slot.stopStaleness();
8639
8934
  } catch {
8640
8935
  }
8936
+ try {
8937
+ slot.stopConnectors();
8938
+ } catch {
8939
+ }
8641
8940
  try {
8642
8941
  slot.detachEvents();
8643
8942
  } catch {
8644
8943
  }
8645
8944
  }
8646
8945
  function neatHomeFor(opts) {
8647
- if (opts.neatHome && opts.neatHome.length > 0) return import_node_path44.default.resolve(opts.neatHome);
8946
+ if (opts.neatHome && opts.neatHome.length > 0) return import_node_path45.default.resolve(opts.neatHome);
8648
8947
  const env = process.env.NEAT_HOME;
8649
- if (env && env.length > 0) return import_node_path44.default.resolve(env);
8948
+ if (env && env.length > 0) return import_node_path45.default.resolve(env);
8650
8949
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
8651
- return import_node_path44.default.join(home, ".neat");
8950
+ return import_node_path45.default.join(home, ".neat");
8652
8951
  }
8653
8952
  function routeSpanToProject(serviceName, projects) {
8654
8953
  if (!serviceName) return DEFAULT_PROJECT;
@@ -8692,13 +8991,13 @@ function spanBelongsToSingleProject(graph, project, serviceName) {
8692
8991
  if (!serviceName) return true;
8693
8992
  if (serviceNameMatchesProject(serviceName, project)) return true;
8694
8993
  return graph.someNode(
8695
- (_id, attrs) => attrs.type === import_types29.NodeType.ServiceNode && attrs.name === serviceName
8994
+ (_id, attrs) => attrs.type === import_types30.NodeType.ServiceNode && attrs.name === serviceName
8696
8995
  );
8697
8996
  }
8698
- async function bootstrapProject(entry) {
8699
- const paths = pathsForProject(entry.name, import_node_path44.default.join(entry.path, "neat-out"));
8997
+ async function bootstrapProject(entry, connectors = []) {
8998
+ const paths = pathsForProject(entry.name, import_node_path45.default.join(entry.path, "neat-out"));
8700
8999
  try {
8701
- const stat = await import_node_fs25.promises.stat(entry.path);
9000
+ const stat = await import_node_fs26.promises.stat(entry.path);
8702
9001
  if (!stat.isDirectory()) {
8703
9002
  throw new Error(`registered path ${entry.path} is not a directory`);
8704
9003
  }
@@ -8716,6 +9015,8 @@ async function bootstrapProject(entry) {
8716
9015
  },
8717
9016
  stopStaleness: () => {
8718
9017
  },
9018
+ stopConnectors: () => {
9019
+ },
8719
9020
  detachEvents: () => {
8720
9021
  },
8721
9022
  status: "broken",
@@ -8734,6 +9035,18 @@ async function bootstrapProject(entry) {
8734
9035
  staleEventsPath: paths.staleEventsPath,
8735
9036
  project: entry.name
8736
9037
  });
9038
+ const stopFns = connectors.map(
9039
+ (registration) => startConnectorPollLoop(
9040
+ registration.connector,
9041
+ { projectDir: entry.path, credentials: registration.credentials },
9042
+ graph,
9043
+ registration.resolveTarget,
9044
+ { intervalMs: registration.intervalMs }
9045
+ )
9046
+ );
9047
+ const stopConnectors = () => {
9048
+ for (const stop of stopFns) stop();
9049
+ };
8737
9050
  await touchLastSeen(entry.name).catch(() => {
8738
9051
  });
8739
9052
  return {
@@ -8743,6 +9056,7 @@ async function bootstrapProject(entry) {
8743
9056
  paths,
8744
9057
  stopPersist,
8745
9058
  stopStaleness,
9059
+ stopConnectors,
8746
9060
  detachEvents,
8747
9061
  status: "active"
8748
9062
  };
@@ -8799,7 +9113,7 @@ async function startDaemon(opts = {}) {
8799
9113
  const projectArg = typeof opts.project === "string" && opts.project.length > 0 ? opts.project : process.env.NEAT_PROJECT && process.env.NEAT_PROJECT.length > 0 ? process.env.NEAT_PROJECT : null;
8800
9114
  const projectPathArg = opts.projectPath && opts.projectPath.length > 0 ? opts.projectPath : process.env.NEAT_PROJECT_PATH && process.env.NEAT_PROJECT_PATH.length > 0 ? process.env.NEAT_PROJECT_PATH : null;
8801
9115
  const singleProject = projectArg;
8802
- const singleProjectPath = singleProject && projectPathArg ? import_node_path44.default.resolve(projectPathArg) : null;
9116
+ const singleProjectPath = singleProject && projectPathArg ? import_node_path45.default.resolve(projectPathArg) : null;
8803
9117
  if (singleProject && !singleProjectPath) {
8804
9118
  throw new Error(
8805
9119
  `neatd: project "${singleProject}" given without a projectPath; pass NEAT_PROJECT_PATH alongside NEAT_PROJECT.`
@@ -8807,14 +9121,14 @@ async function startDaemon(opts = {}) {
8807
9121
  }
8808
9122
  if (!singleProject) {
8809
9123
  try {
8810
- await import_node_fs25.promises.access(regPath);
9124
+ await import_node_fs26.promises.access(regPath);
8811
9125
  } catch {
8812
9126
  throw new Error(
8813
9127
  `neatd: registry not found at ${regPath}. Run \`neat init <path>\` to register a project before starting the daemon.`
8814
9128
  );
8815
9129
  }
8816
9130
  }
8817
- const pidPath = import_node_path44.default.join(home, "neatd.pid");
9131
+ const pidPath = import_node_path45.default.join(home, "neatd.pid");
8818
9132
  await writeAtomically(pidPath, `${process.pid}
8819
9133
  `);
8820
9134
  const slots = /* @__PURE__ */ new Map();
@@ -8858,7 +9172,7 @@ async function startDaemon(opts = {}) {
8858
9172
  }
8859
9173
  async function tryRecoverSlot(entry) {
8860
9174
  try {
8861
- const fresh = await bootstrapProject(entry);
9175
+ const fresh = await bootstrapProject(entry, opts.connectors ?? []);
8862
9176
  const prior = slots.get(entry.name);
8863
9177
  if (prior) teardownSlot(prior);
8864
9178
  slots.set(entry.name, fresh);
@@ -8882,7 +9196,7 @@ async function startDaemon(opts = {}) {
8882
9196
  bootstrapStatus.set(entry.name, "bootstrapping");
8883
9197
  bootstrapStartedAt.set(entry.name, Date.now());
8884
9198
  try {
8885
- const slot = await bootstrapProject(entry);
9199
+ const slot = await bootstrapProject(entry, opts.connectors ?? []);
8886
9200
  const prior = slots.get(entry.name);
8887
9201
  if (prior) teardownSlot(prior);
8888
9202
  slots.set(entry.name, slot);
@@ -9008,7 +9322,7 @@ async function startDaemon(opts = {}) {
9008
9322
  }
9009
9323
  if (restApp) await restApp.close().catch(() => {
9010
9324
  });
9011
- await import_node_fs25.promises.unlink(pidPath).catch(() => {
9325
+ await import_node_fs26.promises.unlink(pidPath).catch(() => {
9012
9326
  });
9013
9327
  throw new Error(
9014
9328
  `neatd: failed to bind REST on port ${restPort} \u2014 ${err.message}`
@@ -9134,7 +9448,7 @@ async function startDaemon(opts = {}) {
9134
9448
  });
9135
9449
  if (otlpApp) await otlpApp.close().catch(() => {
9136
9450
  });
9137
- await import_node_fs25.promises.unlink(pidPath).catch(() => {
9451
+ await import_node_fs26.promises.unlink(pidPath).catch(() => {
9138
9452
  });
9139
9453
  throw new Error(
9140
9454
  `neatd: failed to bind OTLP on port ${otlpPort} \u2014 ${err.message}`
@@ -9169,7 +9483,7 @@ async function startDaemon(opts = {}) {
9169
9483
  });
9170
9484
  if (otlpApp) await otlpApp.close().catch(() => {
9171
9485
  });
9172
- await import_node_fs25.promises.unlink(pidPath).catch(() => {
9486
+ await import_node_fs26.promises.unlink(pidPath).catch(() => {
9173
9487
  });
9174
9488
  throw new Error(
9175
9489
  `neatd: failed to write daemon.json for "${singleProject}" \u2014 ${err.message}`
@@ -9216,9 +9530,9 @@ async function startDaemon(opts = {}) {
9216
9530
  let registryWatcher = null;
9217
9531
  let reloadTimer = null;
9218
9532
  if (!singleProject) try {
9219
- const regDir = import_node_path44.default.dirname(regPath);
9220
- const regBase = import_node_path44.default.basename(regPath);
9221
- registryWatcher = (0, import_node_fs25.watch)(regDir, (_eventType, filename) => {
9533
+ const regDir = import_node_path45.default.dirname(regPath);
9534
+ const regBase = import_node_path45.default.basename(regPath);
9535
+ registryWatcher = (0, import_node_fs26.watch)(regDir, (_eventType, filename) => {
9222
9536
  if (filename !== null && filename !== regBase) return;
9223
9537
  if (reloadTimer) clearTimeout(reloadTimer);
9224
9538
  reloadTimer = setTimeout(() => {
@@ -9267,7 +9581,7 @@ async function startDaemon(opts = {}) {
9267
9581
  if (daemonRecord) {
9268
9582
  await clearDaemonRecord(daemonRecord, home);
9269
9583
  }
9270
- await import_node_fs25.promises.unlink(pidPath).catch(() => {
9584
+ await import_node_fs26.promises.unlink(pidPath).catch(() => {
9271
9585
  });
9272
9586
  };
9273
9587
  return {