@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/neatd.cjs CHANGED
@@ -58,9 +58,9 @@ function mountBearerAuth(app, opts) {
58
58
  const suffixes = [...DEFAULT_UNAUTH_SUFFIXES, ...opts.extraUnauthenticatedSuffixes ?? []];
59
59
  const publicRead = opts.publicRead === true;
60
60
  app.addHook("preHandler", (req2, reply, done) => {
61
- const path47 = (req2.url.split("?")[0] ?? "").replace(/\/+$/, "");
61
+ const path48 = (req2.url.split("?")[0] ?? "").replace(/\/+$/, "");
62
62
  for (const suffix of suffixes) {
63
- if (path47 === suffix || path47.endsWith(suffix)) {
63
+ if (path48 === suffix || path48.endsWith(suffix)) {
64
64
  done();
65
65
  return;
66
66
  }
@@ -190,8 +190,8 @@ function reshapeGrpcRequest(req2) {
190
190
  };
191
191
  }
192
192
  function resolveProtoRoot() {
193
- const here = import_node_path41.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
194
- return import_node_path41.default.resolve(here, "..", "proto");
193
+ const here = import_node_path42.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
194
+ return import_node_path42.default.resolve(here, "..", "proto");
195
195
  }
196
196
  function loadTraceService() {
197
197
  const protoRoot = resolveProtoRoot();
@@ -259,13 +259,13 @@ async function startOtelGrpcReceiver(opts) {
259
259
  })
260
260
  };
261
261
  }
262
- var import_node_url, import_node_path41, import_node_crypto2, grpc, protoLoader;
262
+ var import_node_url, import_node_path42, import_node_crypto2, grpc, protoLoader;
263
263
  var init_otel_grpc = __esm({
264
264
  "src/otel-grpc.ts"() {
265
265
  "use strict";
266
266
  init_cjs_shims();
267
267
  import_node_url = require("url");
268
- import_node_path41 = __toESM(require("path"), 1);
268
+ import_node_path42 = __toESM(require("path"), 1);
269
269
  import_node_crypto2 = require("crypto");
270
270
  grpc = __toESM(require("@grpc/grpc-js"), 1);
271
271
  protoLoader = __toESM(require("@grpc/proto-loader"), 1);
@@ -345,6 +345,29 @@ function messagingDestinationOf(attrs) {
345
345
  }
346
346
  return void 0;
347
347
  }
348
+ function hasWebsocketUpgradeHeader(attrs) {
349
+ const v = attrs["http.request.header.upgrade"];
350
+ const matches = (s) => typeof s === "string" && s.trim().toLowerCase() === "websocket";
351
+ if (Array.isArray(v)) return v.some(matches);
352
+ return matches(v);
353
+ }
354
+ function websocketChannelPathOf(attrs) {
355
+ const route = attrs["http.route"];
356
+ if (typeof route === "string" && route.length > 0) return route;
357
+ for (const key of ["url.path", "http.target"]) {
358
+ const v = attrs[key];
359
+ if (typeof v === "string" && v.length > 0) {
360
+ const q = v.indexOf("?");
361
+ const path48 = q === -1 ? v : v.slice(0, q);
362
+ if (path48.length > 0) return path48;
363
+ }
364
+ }
365
+ return void 0;
366
+ }
367
+ function websocketChannelOf(attrs) {
368
+ if (!hasWebsocketUpgradeHeader(attrs)) return void 0;
369
+ return websocketChannelPathOf(attrs);
370
+ }
348
371
  function parseOtlpRequest(body) {
349
372
  const out = [];
350
373
  for (const rs of body.resourceSpans ?? []) {
@@ -375,6 +398,10 @@ function parseOtlpRequest(body) {
375
398
  messagingDestination: messagingDestinationOf(attrs),
376
399
  graphqlOperationName: typeof attrs["graphql.operation.name"] === "string" && attrs["graphql.operation.name"].length > 0 ? attrs["graphql.operation.name"] : void 0,
377
400
  graphqlOperationType: typeof attrs["graphql.operation.type"] === "string" && attrs["graphql.operation.type"].length > 0 ? attrs["graphql.operation.type"] : void 0,
401
+ rpcSystem: typeof attrs["rpc.system"] === "string" && attrs["rpc.system"].length > 0 ? attrs["rpc.system"] : void 0,
402
+ rpcService: typeof attrs["rpc.service"] === "string" && attrs["rpc.service"].length > 0 ? attrs["rpc.service"] : void 0,
403
+ rpcMethod: typeof attrs["rpc.method"] === "string" && attrs["rpc.method"].length > 0 ? attrs["rpc.method"] : void 0,
404
+ websocketChannel: websocketChannelOf(attrs),
378
405
  statusCode: span.status?.code,
379
406
  errorMessage: span.status?.message,
380
407
  exception: extractExceptionFromEvents(span.events)
@@ -386,10 +413,10 @@ function parseOtlpRequest(body) {
386
413
  return out;
387
414
  }
388
415
  function loadProtoRoot() {
389
- const here = import_node_path42.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
390
- const protoRoot = import_node_path42.default.resolve(here, "..", "proto");
416
+ const here = import_node_path43.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
417
+ const protoRoot = import_node_path43.default.resolve(here, "..", "proto");
391
418
  const root = new import_protobufjs.default.Root();
392
- root.resolvePath = (_origin, target) => import_node_path42.default.resolve(protoRoot, target);
419
+ root.resolvePath = (_origin, target) => import_node_path43.default.resolve(protoRoot, target);
393
420
  root.loadSync(
394
421
  "opentelemetry/proto/collector/trace/v1/trace_service.proto",
395
422
  { keepCase: true }
@@ -617,12 +644,12 @@ async function listenSteppingOtlp(app, requestedPort, host) {
617
644
  }
618
645
  }
619
646
  }
620
- 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;
647
+ 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;
621
648
  var init_otel = __esm({
622
649
  "src/otel.ts"() {
623
650
  "use strict";
624
651
  init_cjs_shims();
625
- import_node_path42 = __toESM(require("path"), 1);
652
+ import_node_path43 = __toESM(require("path"), 1);
626
653
  import_node_url2 = require("url");
627
654
  import_fastify2 = __toESM(require("fastify"), 1);
628
655
  import_protobufjs = __toESM(require("protobufjs"), 1);
@@ -645,14 +672,14 @@ __export(neatd_exports, {
645
672
  });
646
673
  module.exports = __toCommonJS(neatd_exports);
647
674
  init_cjs_shims();
648
- var import_node_fs27 = require("fs");
649
- var import_node_path46 = __toESM(require("path"), 1);
675
+ var import_node_fs28 = require("fs");
676
+ var import_node_path47 = __toESM(require("path"), 1);
650
677
  var import_node_module2 = require("module");
651
678
 
652
679
  // src/daemon.ts
653
680
  init_cjs_shims();
654
- var import_node_fs25 = require("fs");
655
- var import_node_path44 = __toESM(require("path"), 1);
681
+ var import_node_fs26 = require("fs");
682
+ var import_node_path45 = __toESM(require("path"), 1);
656
683
  var import_node_module = require("module");
657
684
 
658
685
  // src/graph.ts
@@ -1182,19 +1209,19 @@ function confidenceFromMix(edges, now = Date.now()) {
1182
1209
  function longestIncomingWalk(graph, start, maxDepth) {
1183
1210
  let best = { path: [start], edges: [] };
1184
1211
  const visited = /* @__PURE__ */ new Set([start]);
1185
- function step(node, path47, edges) {
1186
- if (path47.length > best.path.length) {
1187
- best = { path: [...path47], edges: [...edges] };
1212
+ function step(node, path48, edges) {
1213
+ if (path48.length > best.path.length) {
1214
+ best = { path: [...path48], edges: [...edges] };
1188
1215
  }
1189
- if (path47.length - 1 >= maxDepth) return;
1216
+ if (path48.length - 1 >= maxDepth) return;
1190
1217
  const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
1191
1218
  for (const [srcId, edge] of incoming) {
1192
1219
  if (visited.has(srcId)) continue;
1193
1220
  visited.add(srcId);
1194
- path47.push(srcId);
1221
+ path48.push(srcId);
1195
1222
  edges.push(edge);
1196
- step(srcId, path47, edges);
1197
- path47.pop();
1223
+ step(srcId, path48, edges);
1224
+ path48.pop();
1198
1225
  edges.pop();
1199
1226
  visited.delete(srcId);
1200
1227
  }
@@ -1388,26 +1415,26 @@ function dominantFailingCall(graph, serviceId4, visited) {
1388
1415
  return best;
1389
1416
  }
1390
1417
  function followFailingCallChain(graph, originServiceId, maxDepth) {
1391
- const path47 = [originServiceId];
1418
+ const path48 = [originServiceId];
1392
1419
  const edges = [];
1393
1420
  const visited = /* @__PURE__ */ new Set([originServiceId]);
1394
1421
  let current = originServiceId;
1395
1422
  for (let depth = 0; depth < maxDepth; depth++) {
1396
1423
  const hop = dominantFailingCall(graph, current, visited);
1397
1424
  if (!hop) break;
1398
- path47.push(hop.nextService);
1425
+ path48.push(hop.nextService);
1399
1426
  edges.push(hop.edge);
1400
1427
  visited.add(hop.nextService);
1401
1428
  current = hop.nextService;
1402
1429
  }
1403
1430
  if (edges.length === 0) return null;
1404
- return { path: path47, edges, culprit: current };
1431
+ return { path: path48, edges, culprit: current };
1405
1432
  }
1406
1433
  function crossServiceRootCause(graph, originId, incidents, errorEvent) {
1407
1434
  const chain = followFailingCallChain(graph, originId, ROOT_CAUSE_MAX_DEPTH);
1408
1435
  if (!chain) return null;
1409
1436
  const culprit = chain.culprit;
1410
- const path47 = [...chain.path];
1437
+ const path48 = [...chain.path];
1411
1438
  const edgeProvenances = chain.edges.map((e) => e.provenance);
1412
1439
  const baseConfidence = confidenceFromMix(chain.edges);
1413
1440
  const confidence = Math.max(0, Math.min(1, baseConfidence * INCIDENT_ROOT_CAUSE_CONFIDENCE));
@@ -1415,14 +1442,14 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
1415
1442
  if (loc) {
1416
1443
  let rootCauseNode = culprit;
1417
1444
  if (loc.fileNode) {
1418
- path47.push(loc.fileNode);
1445
+ path48.push(loc.fileNode);
1419
1446
  edgeProvenances.push(import_types.Provenance.OBSERVED);
1420
1447
  rootCauseNode = loc.fileNode;
1421
1448
  }
1422
1449
  return import_types.RootCauseResultSchema.parse({
1423
1450
  rootCauseNode,
1424
1451
  rootCauseReason: loc.rootCauseReason,
1425
- traversalPath: path47,
1452
+ traversalPath: path48,
1426
1453
  edgeProvenances,
1427
1454
  confidence,
1428
1455
  ...loc.fixRecommendation ? { fixRecommendation: loc.fixRecommendation } : {}
@@ -1434,7 +1461,7 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
1434
1461
  return import_types.RootCauseResultSchema.parse({
1435
1462
  rootCauseNode: culprit,
1436
1463
  rootCauseReason: `${culpritName} is failing downstream calls (${errs} observed error${errs === 1 ? "" : "s"})`,
1437
- traversalPath: path47,
1464
+ traversalPath: path48,
1438
1465
  edgeProvenances,
1439
1466
  confidence,
1440
1467
  fixRecommendation: `Inspect ${culpritName}'s failing handler`
@@ -2384,6 +2411,40 @@ function ensureGraphqlOperationNode(graph, serviceName, operationType, operation
2384
2411
  graph.addNode(id, node);
2385
2412
  return id;
2386
2413
  }
2414
+ function spanServesGrpcMethod(kind) {
2415
+ return kind !== WIRE_SPAN_KIND_CLIENT && kind !== WIRE_SPAN_KIND_PRODUCER && kind !== WIRE_SPAN_KIND_CONSUMER;
2416
+ }
2417
+ function ensureGrpcMethodNode(graph, rpcService, rpcMethod) {
2418
+ const id = (0, import_types3.grpcMethodId)(rpcService, rpcMethod);
2419
+ if (graph.hasNode(id)) return id;
2420
+ const node = {
2421
+ id,
2422
+ type: import_types3.NodeType.GrpcMethodNode,
2423
+ name: `${rpcService}/${rpcMethod}`,
2424
+ rpcService,
2425
+ rpcMethod,
2426
+ discoveredVia: "otel"
2427
+ };
2428
+ graph.addNode(id, node);
2429
+ return id;
2430
+ }
2431
+ function spanServesWebsocketChannel(kind) {
2432
+ return kind !== WIRE_SPAN_KIND_CLIENT && kind !== WIRE_SPAN_KIND_PRODUCER && kind !== WIRE_SPAN_KIND_CONSUMER;
2433
+ }
2434
+ function ensureWebsocketChannelNode(graph, serviceName, channel) {
2435
+ const id = (0, import_types3.websocketChannelId)(serviceName, channel);
2436
+ if (graph.hasNode(id)) return id;
2437
+ const node = {
2438
+ id,
2439
+ type: import_types3.NodeType.WebSocketChannelNode,
2440
+ name: channel,
2441
+ service: serviceName,
2442
+ channel,
2443
+ discoveredVia: "otel"
2444
+ };
2445
+ graph.addNode(id, node);
2446
+ return id;
2447
+ }
2387
2448
  function messagingDestinationKind(system) {
2388
2449
  return `${system}-topic`;
2389
2450
  }
@@ -2819,6 +2880,34 @@ async function handleSpan(ctx, span) {
2819
2880
  callSiteEvidence
2820
2881
  );
2821
2882
  if (result) affectedNode = targetId;
2883
+ } else if (span.rpcSystem === "grpc" && span.rpcService && span.rpcMethod && spanServesGrpcMethod(span.kind)) {
2884
+ const targetId = ensureGrpcMethodNode(ctx.graph, span.rpcService, span.rpcMethod);
2885
+ const result = upsertObservedEdge(
2886
+ ctx.graph,
2887
+ import_types3.EdgeType.CONTAINS,
2888
+ observedSource(),
2889
+ targetId,
2890
+ ts,
2891
+ isError,
2892
+ callSiteEvidence
2893
+ );
2894
+ if (result) affectedNode = targetId;
2895
+ } else if (span.websocketChannel && spanServesWebsocketChannel(span.kind)) {
2896
+ const targetId = ensureWebsocketChannelNode(
2897
+ ctx.graph,
2898
+ span.service,
2899
+ span.websocketChannel
2900
+ );
2901
+ const result = upsertObservedEdge(
2902
+ ctx.graph,
2903
+ import_types3.EdgeType.CONNECTS_TO,
2904
+ observedSource(),
2905
+ targetId,
2906
+ ts,
2907
+ isError,
2908
+ callSiteEvidence
2909
+ );
2910
+ if (result) affectedNode = targetId;
2822
2911
  } else {
2823
2912
  const host = pickAddress(span);
2824
2913
  let resolvedViaAddress = false;
@@ -5386,17 +5475,138 @@ async function addRoutes(graph, services) {
5386
5475
  return { nodesAdded, edgesAdded };
5387
5476
  }
5388
5477
 
5478
+ // src/extract/proto.ts
5479
+ init_cjs_shims();
5480
+ var import_node_fs15 = require("fs");
5481
+ var import_node_path23 = __toESM(require("path"), 1);
5482
+ var import_types12 = require("@neat.is/types");
5483
+ var PROTO_EXTENSION = ".proto";
5484
+ function packageOf(content) {
5485
+ const m = content.match(/^\s*package\s+([A-Za-z_][A-Za-z0-9_.]*)\s*;/m);
5486
+ return m ? m[1] : null;
5487
+ }
5488
+ function lineAt(content, offset) {
5489
+ return content.slice(0, offset).split("\n").length;
5490
+ }
5491
+ function grpcMethodsFromProto(content, fqPackage) {
5492
+ const out = [];
5493
+ const serviceRe = /\bservice\s+([A-Za-z_][A-Za-z0-9_]*)\s*\{/g;
5494
+ let sm;
5495
+ while ((sm = serviceRe.exec(content)) !== null) {
5496
+ const serviceName = sm[1];
5497
+ const rpcService = fqPackage ? `${fqPackage}.${serviceName}` : serviceName;
5498
+ const bodyStart = serviceRe.lastIndex;
5499
+ let depth = 1;
5500
+ let i = bodyStart;
5501
+ for (; i < content.length && depth > 0; i++) {
5502
+ const ch = content[i];
5503
+ if (ch === "{") depth++;
5504
+ else if (ch === "}") depth--;
5505
+ }
5506
+ const body = content.slice(bodyStart, i - 1);
5507
+ const rpcRe = /\brpc\s+([A-Za-z_][A-Za-z0-9_]*)\s*\(/g;
5508
+ let rm;
5509
+ while ((rm = rpcRe.exec(body)) !== null) {
5510
+ const rpcMethod = rm[1];
5511
+ const line = lineAt(content, bodyStart + rm.index);
5512
+ out.push({ rpcService, rpcMethod, line });
5513
+ }
5514
+ serviceRe.lastIndex = i;
5515
+ }
5516
+ return out;
5517
+ }
5518
+ async function walkProtoFiles(dir) {
5519
+ const out = [];
5520
+ async function walk3(current) {
5521
+ const entries = await import_node_fs15.promises.readdir(current, { withFileTypes: true }).catch(() => []);
5522
+ for (const entry2 of entries) {
5523
+ const full = import_node_path23.default.join(current, entry2.name);
5524
+ if (entry2.isDirectory()) {
5525
+ if (IGNORED_DIRS.has(entry2.name)) continue;
5526
+ if (await isPythonVenvDir(full)) continue;
5527
+ await walk3(full);
5528
+ } else if (entry2.isFile() && import_node_path23.default.extname(entry2.name) === PROTO_EXTENSION) {
5529
+ out.push(full);
5530
+ }
5531
+ }
5532
+ }
5533
+ await walk3(dir);
5534
+ return out;
5535
+ }
5536
+ async function addGrpcMethods(graph, services) {
5537
+ let nodesAdded = 0;
5538
+ let edgesAdded = 0;
5539
+ for (const service of services) {
5540
+ const protoPaths = await walkProtoFiles(service.dir);
5541
+ for (const protoPath of protoPaths) {
5542
+ if (isTestPath(protoPath)) continue;
5543
+ const relFile = toPosix2(import_node_path23.default.relative(service.dir, protoPath));
5544
+ let content;
5545
+ try {
5546
+ content = await import_node_fs15.promises.readFile(protoPath, "utf8");
5547
+ } catch (err) {
5548
+ recordExtractionError("proto extraction", protoPath, err);
5549
+ continue;
5550
+ }
5551
+ let methods;
5552
+ try {
5553
+ methods = grpcMethodsFromProto(content, packageOf(content));
5554
+ } catch (err) {
5555
+ recordExtractionError("proto extraction", protoPath, err);
5556
+ continue;
5557
+ }
5558
+ if (methods.length === 0) continue;
5559
+ for (const method of methods) {
5560
+ const mid = (0, import_types12.grpcMethodId)(method.rpcService, method.rpcMethod);
5561
+ if (!graph.hasNode(mid)) {
5562
+ const node = {
5563
+ id: mid,
5564
+ type: import_types12.NodeType.GrpcMethodNode,
5565
+ name: `${method.rpcService}/${method.rpcMethod}`,
5566
+ rpcService: method.rpcService,
5567
+ rpcMethod: method.rpcMethod,
5568
+ path: relFile,
5569
+ line: method.line,
5570
+ discoveredVia: "static"
5571
+ };
5572
+ graph.addNode(mid, node);
5573
+ nodesAdded++;
5574
+ }
5575
+ const containsId = (0, import_types12.extractedEdgeId)(service.node.id, mid, import_types12.EdgeType.CONTAINS);
5576
+ if (!graph.hasEdge(containsId)) {
5577
+ const edge = {
5578
+ id: containsId,
5579
+ source: service.node.id,
5580
+ target: mid,
5581
+ type: import_types12.EdgeType.CONTAINS,
5582
+ provenance: import_types12.Provenance.EXTRACTED,
5583
+ confidence: (0, import_types12.confidenceForExtracted)("structural"),
5584
+ evidence: {
5585
+ file: relFile,
5586
+ line: method.line,
5587
+ snippet: snippet(content, method.line)
5588
+ }
5589
+ };
5590
+ graph.addEdgeWithKey(containsId, service.node.id, mid, edge);
5591
+ edgesAdded++;
5592
+ }
5593
+ }
5594
+ }
5595
+ }
5596
+ return { nodesAdded, edgesAdded };
5597
+ }
5598
+
5389
5599
  // src/extract/calls/index.ts
5390
5600
  init_cjs_shims();
5391
- var import_types19 = require("@neat.is/types");
5601
+ var import_types20 = require("@neat.is/types");
5392
5602
 
5393
5603
  // src/extract/calls/http.ts
5394
5604
  init_cjs_shims();
5395
- var import_node_path23 = __toESM(require("path"), 1);
5605
+ var import_node_path24 = __toESM(require("path"), 1);
5396
5606
  var import_tree_sitter3 = __toESM(require("tree-sitter"), 1);
5397
5607
  var import_tree_sitter_javascript3 = __toESM(require("tree-sitter-javascript"), 1);
5398
5608
  var import_tree_sitter_python2 = __toESM(require("tree-sitter-python"), 1);
5399
- var import_types12 = require("@neat.is/types");
5609
+ var import_types13 = require("@neat.is/types");
5400
5610
  var STRING_LITERAL_NODE_TYPES = /* @__PURE__ */ new Set(["string_fragment", "string_content"]);
5401
5611
  var JSX_EXTERNAL_LINK_TAGS = /* @__PURE__ */ new Set(["a", "Link", "NavLink", "ExternalLink", "Anchor"]);
5402
5612
  function isInsideJsxExternalLink(node) {
@@ -5466,7 +5676,7 @@ async function addHttpCallEdges(graph, services) {
5466
5676
  const seen = /* @__PURE__ */ new Set();
5467
5677
  for (const file of files) {
5468
5678
  if (isTestPath(file.path)) continue;
5469
- const parser = import_node_path23.default.extname(file.path) === ".py" ? pyParser : jsParser;
5679
+ const parser = import_node_path24.default.extname(file.path) === ".py" ? pyParser : jsParser;
5470
5680
  let sites;
5471
5681
  try {
5472
5682
  sites = callsFromSource(file.content, parser, knownHosts);
@@ -5475,14 +5685,14 @@ async function addHttpCallEdges(graph, services) {
5475
5685
  continue;
5476
5686
  }
5477
5687
  if (sites.length === 0) continue;
5478
- const relFile = toPosix2(import_node_path23.default.relative(service.dir, file.path));
5688
+ const relFile = toPosix2(import_node_path24.default.relative(service.dir, file.path));
5479
5689
  for (const site of sites) {
5480
5690
  const targetId = hostToNodeId.get(site.host);
5481
5691
  if (!targetId || targetId === service.node.id) continue;
5482
5692
  const dedupKey = `${relFile}|${targetId}`;
5483
5693
  if (seen.has(dedupKey)) continue;
5484
5694
  seen.add(dedupKey);
5485
- const confidence = (0, import_types12.confidenceForExtracted)("url-literal-service-target");
5695
+ const confidence = (0, import_types13.confidenceForExtracted)("url-literal-service-target");
5486
5696
  const ev = {
5487
5697
  file: relFile,
5488
5698
  line: site.line,
@@ -5496,25 +5706,25 @@ async function addHttpCallEdges(graph, services) {
5496
5706
  );
5497
5707
  nodesAdded += n;
5498
5708
  edgesAdded += e;
5499
- if (!(0, import_types12.passesExtractedFloor)(confidence)) {
5709
+ if (!(0, import_types13.passesExtractedFloor)(confidence)) {
5500
5710
  noteExtractedDropped({
5501
5711
  source: fileNodeId,
5502
5712
  target: targetId,
5503
- type: import_types12.EdgeType.CALLS,
5713
+ type: import_types13.EdgeType.CALLS,
5504
5714
  confidence,
5505
5715
  confidenceKind: "url-literal-service-target",
5506
5716
  evidence: ev
5507
5717
  });
5508
5718
  continue;
5509
5719
  }
5510
- const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, targetId, import_types12.EdgeType.CALLS);
5720
+ const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, targetId, import_types13.EdgeType.CALLS);
5511
5721
  if (!graph.hasEdge(edgeId)) {
5512
5722
  const edge = {
5513
5723
  id: edgeId,
5514
5724
  source: fileNodeId,
5515
5725
  target: targetId,
5516
- type: import_types12.EdgeType.CALLS,
5517
- provenance: import_types12.Provenance.EXTRACTED,
5726
+ type: import_types13.EdgeType.CALLS,
5727
+ provenance: import_types13.Provenance.EXTRACTED,
5518
5728
  confidence,
5519
5729
  evidence: ev
5520
5730
  };
@@ -5529,10 +5739,10 @@ async function addHttpCallEdges(graph, services) {
5529
5739
 
5530
5740
  // src/extract/calls/route-match.ts
5531
5741
  init_cjs_shims();
5532
- var import_node_path24 = __toESM(require("path"), 1);
5742
+ var import_node_path25 = __toESM(require("path"), 1);
5533
5743
  var import_tree_sitter4 = __toESM(require("tree-sitter"), 1);
5534
5744
  var import_tree_sitter_javascript4 = __toESM(require("tree-sitter-javascript"), 1);
5535
- var import_types13 = require("@neat.is/types");
5745
+ var import_types14 = require("@neat.is/types");
5536
5746
  var PARSE_CHUNK4 = 16384;
5537
5747
  function parseSource4(parser, source) {
5538
5748
  return parser.parse(
@@ -5697,9 +5907,9 @@ function buildRouteIndex(graph) {
5697
5907
  const index = /* @__PURE__ */ new Map();
5698
5908
  graph.forEachNode((_id, attrs) => {
5699
5909
  const node = attrs;
5700
- if (node.type !== import_types13.NodeType.RouteNode) return;
5910
+ if (node.type !== import_types14.NodeType.RouteNode) return;
5701
5911
  const route = attrs;
5702
- const owner = (0, import_types13.serviceId)(route.service);
5912
+ const owner = (0, import_types14.serviceId)(route.service);
5703
5913
  const entry2 = {
5704
5914
  method: route.method.toUpperCase(),
5705
5915
  normalizedPath: normalizePathTemplate(route.pathTemplate),
@@ -5728,7 +5938,7 @@ async function addRouteCallEdges(graph, services) {
5728
5938
  const seen = /* @__PURE__ */ new Set();
5729
5939
  for (const file of files) {
5730
5940
  if (isTestPath(file.path)) continue;
5731
- if (!JS_CLIENT_EXTENSIONS.has(import_node_path24.default.extname(file.path))) continue;
5941
+ if (!JS_CLIENT_EXTENSIONS.has(import_node_path25.default.extname(file.path))) continue;
5732
5942
  let sites;
5733
5943
  try {
5734
5944
  sites = clientCallSitesFromSource(file.content, jsParser, knownHosts);
@@ -5737,7 +5947,7 @@ async function addRouteCallEdges(graph, services) {
5737
5947
  continue;
5738
5948
  }
5739
5949
  if (sites.length === 0) continue;
5740
- const relFile = toPosix2(import_node_path24.default.relative(service.dir, file.path));
5950
+ const relFile = toPosix2(import_node_path25.default.relative(service.dir, file.path));
5741
5951
  for (const site of sites) {
5742
5952
  const serverServiceId = hostToNodeId.get(site.host);
5743
5953
  if (!serverServiceId || serverServiceId === service.node.id) continue;
@@ -5757,7 +5967,7 @@ async function addRouteCallEdges(graph, services) {
5757
5967
  );
5758
5968
  nodesAdded += n;
5759
5969
  edgesAdded += e;
5760
- const confidence = (0, import_types13.confidenceForExtracted)("verified-call-site");
5970
+ const confidence = (0, import_types14.confidenceForExtracted)("verified-call-site");
5761
5971
  const ev = {
5762
5972
  file: relFile,
5763
5973
  line: site.line,
@@ -5765,25 +5975,25 @@ async function addRouteCallEdges(graph, services) {
5765
5975
  method: site.method ?? match.method,
5766
5976
  pathTemplate: site.pathTemplate
5767
5977
  };
5768
- if (!(0, import_types13.passesExtractedFloor)(confidence)) {
5978
+ if (!(0, import_types14.passesExtractedFloor)(confidence)) {
5769
5979
  noteExtractedDropped({
5770
5980
  source: fileNodeId,
5771
5981
  target: match.routeNodeId,
5772
- type: import_types13.EdgeType.CALLS,
5982
+ type: import_types14.EdgeType.CALLS,
5773
5983
  confidence,
5774
5984
  confidenceKind: "verified-call-site",
5775
5985
  evidence: ev
5776
5986
  });
5777
5987
  continue;
5778
5988
  }
5779
- const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, match.routeNodeId, import_types13.EdgeType.CALLS);
5989
+ const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, match.routeNodeId, import_types14.EdgeType.CALLS);
5780
5990
  if (!graph.hasEdge(edgeId)) {
5781
5991
  const edge = {
5782
5992
  id: edgeId,
5783
5993
  source: fileNodeId,
5784
5994
  target: match.routeNodeId,
5785
- type: import_types13.EdgeType.CALLS,
5786
- provenance: import_types13.Provenance.EXTRACTED,
5995
+ type: import_types14.EdgeType.CALLS,
5996
+ provenance: import_types14.Provenance.EXTRACTED,
5787
5997
  confidence,
5788
5998
  evidence: ev
5789
5999
  };
@@ -5798,8 +6008,8 @@ async function addRouteCallEdges(graph, services) {
5798
6008
 
5799
6009
  // src/extract/calls/kafka.ts
5800
6010
  init_cjs_shims();
5801
- var import_node_path25 = __toESM(require("path"), 1);
5802
- var import_types14 = require("@neat.is/types");
6011
+ var import_node_path26 = __toESM(require("path"), 1);
6012
+ var import_types15 = require("@neat.is/types");
5803
6013
  var PRODUCER_TOPIC_RE = /(?:producer|kafkaProducer)[\s\S]{0,40}?\.send\s*\(\s*\{[\s\S]{0,200}?topic\s*:\s*['"`]([^'"`]+)['"`]/g;
5804
6014
  var CONSUMER_TOPIC_RE = /(?:consumer|kafkaConsumer)[\s\S]{0,40}?\.(?:subscribe|run)\s*\(\s*\{[\s\S]{0,200}?topic[s]?\s*:\s*(?:\[\s*)?['"`]([^'"`]+)['"`]/g;
5805
6015
  function findAll(re, text) {
@@ -5820,7 +6030,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
5820
6030
  seen.add(key);
5821
6031
  const line = lineOf(file.content, topic);
5822
6032
  out.push({
5823
- infraId: (0, import_types14.infraId)("kafka-topic", topic),
6033
+ infraId: (0, import_types15.infraId)("kafka-topic", topic),
5824
6034
  name: topic,
5825
6035
  kind: "kafka-topic",
5826
6036
  edgeType,
@@ -5829,7 +6039,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
5829
6039
  // tier (ADR-066).
5830
6040
  confidenceKind: "verified-call-site",
5831
6041
  evidence: {
5832
- file: import_node_path25.default.relative(serviceDir, file.path),
6042
+ file: import_node_path26.default.relative(serviceDir, file.path),
5833
6043
  line,
5834
6044
  snippet: snippet(file.content, line)
5835
6045
  }
@@ -5842,8 +6052,8 @@ function kafkaEndpointsFromFile(file, serviceDir) {
5842
6052
 
5843
6053
  // src/extract/calls/redis.ts
5844
6054
  init_cjs_shims();
5845
- var import_node_path26 = __toESM(require("path"), 1);
5846
- var import_types15 = require("@neat.is/types");
6055
+ var import_node_path27 = __toESM(require("path"), 1);
6056
+ var import_types16 = require("@neat.is/types");
5847
6057
  var REDIS_URL_RE = /redis(?:s)?:\/\/(?:[^@'"`\s]+@)?([^:/'"`\s]+)(?::(\d+))?/g;
5848
6058
  function redisEndpointsFromFile(file, serviceDir) {
5849
6059
  const out = [];
@@ -5856,7 +6066,7 @@ function redisEndpointsFromFile(file, serviceDir) {
5856
6066
  seen.add(host);
5857
6067
  const line = lineOf(file.content, host);
5858
6068
  out.push({
5859
- infraId: (0, import_types15.infraId)("redis", host),
6069
+ infraId: (0, import_types16.infraId)("redis", host),
5860
6070
  name: host,
5861
6071
  kind: "redis",
5862
6072
  edgeType: "CALLS",
@@ -5865,7 +6075,7 @@ function redisEndpointsFromFile(file, serviceDir) {
5865
6075
  // support tier (ADR-066).
5866
6076
  confidenceKind: "url-with-structural-support",
5867
6077
  evidence: {
5868
- file: import_node_path26.default.relative(serviceDir, file.path),
6078
+ file: import_node_path27.default.relative(serviceDir, file.path),
5869
6079
  line,
5870
6080
  snippet: snippet(file.content, line)
5871
6081
  }
@@ -5876,8 +6086,8 @@ function redisEndpointsFromFile(file, serviceDir) {
5876
6086
 
5877
6087
  // src/extract/calls/aws.ts
5878
6088
  init_cjs_shims();
5879
- var import_node_path27 = __toESM(require("path"), 1);
5880
- var import_types16 = require("@neat.is/types");
6089
+ var import_node_path28 = __toESM(require("path"), 1);
6090
+ var import_types17 = require("@neat.is/types");
5881
6091
  var S3_BUCKET_RE = /Bucket\s*:\s*['"`]([^'"`]+)['"`]/g;
5882
6092
  var DYNAMO_TABLE_RE = /TableName\s*:\s*['"`]([^'"`]+)['"`]/g;
5883
6093
  function hasMarker(text, markers) {
@@ -5901,7 +6111,7 @@ function awsEndpointsFromFile(file, serviceDir) {
5901
6111
  seen.add(key);
5902
6112
  const line = lineOf(file.content, name);
5903
6113
  out.push({
5904
- infraId: (0, import_types16.infraId)(kind, name),
6114
+ infraId: (0, import_types17.infraId)(kind, name),
5905
6115
  name,
5906
6116
  kind,
5907
6117
  edgeType: "CALLS",
@@ -5910,7 +6120,7 @@ function awsEndpointsFromFile(file, serviceDir) {
5910
6120
  // (ADR-066).
5911
6121
  confidenceKind: "verified-call-site",
5912
6122
  evidence: {
5913
- file: import_node_path27.default.relative(serviceDir, file.path),
6123
+ file: import_node_path28.default.relative(serviceDir, file.path),
5914
6124
  line,
5915
6125
  snippet: snippet(file.content, line)
5916
6126
  }
@@ -5935,8 +6145,8 @@ function awsEndpointsFromFile(file, serviceDir) {
5935
6145
 
5936
6146
  // src/extract/calls/grpc.ts
5937
6147
  init_cjs_shims();
5938
- var import_node_path28 = __toESM(require("path"), 1);
5939
- var import_types17 = require("@neat.is/types");
6148
+ var import_node_path29 = __toESM(require("path"), 1);
6149
+ var import_types18 = require("@neat.is/types");
5940
6150
  var GRPC_CLIENT_RE = /new\s+([A-Z][A-Za-z0-9_]*)Client\s*\(\s*['"`]?([^,'"`)]+)?/g;
5941
6151
  var AWS_SDK_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@aws-sdk\/client-([a-z0-9-]+)['"`]/g;
5942
6152
  var GRPC_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@grpc\/grpc-js['"`]|_grpc_pb['"`]/;
@@ -5985,7 +6195,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
5985
6195
  const { kind } = classified;
5986
6196
  const line = lineOf(file.content, m[0]);
5987
6197
  out.push({
5988
- infraId: (0, import_types17.infraId)(kind, name),
6198
+ infraId: (0, import_types18.infraId)(kind, name),
5989
6199
  name,
5990
6200
  kind,
5991
6201
  edgeType: "CALLS",
@@ -5994,7 +6204,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
5994
6204
  // tier (ADR-066).
5995
6205
  confidenceKind: "verified-call-site",
5996
6206
  evidence: {
5997
- file: import_node_path28.default.relative(serviceDir, file.path),
6207
+ file: import_node_path29.default.relative(serviceDir, file.path),
5998
6208
  line,
5999
6209
  snippet: snippet(file.content, line)
6000
6210
  }
@@ -6005,8 +6215,8 @@ function grpcEndpointsFromFile(file, serviceDir) {
6005
6215
 
6006
6216
  // src/extract/calls/supabase.ts
6007
6217
  init_cjs_shims();
6008
- var import_node_path29 = __toESM(require("path"), 1);
6009
- var import_types18 = require("@neat.is/types");
6218
+ var import_node_path30 = __toESM(require("path"), 1);
6219
+ var import_types19 = require("@neat.is/types");
6010
6220
  var SUPABASE_JS_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@supabase\/supabase-js['"`]/;
6011
6221
  var SUPABASE_SSR_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@supabase\/ssr['"`]/;
6012
6222
  var SUPABASE_CLIENT_RE = /\b(createClient|createServerClient|createBrowserClient)\s*\(\s*(?:['"`]([^'"`]*)['"`])?/g;
@@ -6044,7 +6254,7 @@ function supabaseEndpointsFromFile(file, serviceDir) {
6044
6254
  seen.add(name);
6045
6255
  const line = lineOf(file.content, m[0]);
6046
6256
  out.push({
6047
- infraId: (0, import_types18.infraId)("supabase", name),
6257
+ infraId: (0, import_types19.infraId)("supabase", name),
6048
6258
  name,
6049
6259
  kind: "supabase",
6050
6260
  edgeType: "CALLS",
@@ -6054,7 +6264,7 @@ function supabaseEndpointsFromFile(file, serviceDir) {
6054
6264
  // tier (ADR-066), the same grade aws.ts / grpc.ts emit at.
6055
6265
  confidenceKind: "verified-call-site",
6056
6266
  evidence: {
6057
- file: import_node_path29.default.relative(serviceDir, file.path),
6267
+ file: import_node_path30.default.relative(serviceDir, file.path),
6058
6268
  line,
6059
6269
  snippet: snippet(file.content, line)
6060
6270
  }
@@ -6067,11 +6277,11 @@ function supabaseEndpointsFromFile(file, serviceDir) {
6067
6277
  function edgeTypeFromEndpoint(ep) {
6068
6278
  switch (ep.edgeType) {
6069
6279
  case "PUBLISHES_TO":
6070
- return import_types19.EdgeType.PUBLISHES_TO;
6280
+ return import_types20.EdgeType.PUBLISHES_TO;
6071
6281
  case "CONSUMES_FROM":
6072
- return import_types19.EdgeType.CONSUMES_FROM;
6282
+ return import_types20.EdgeType.CONSUMES_FROM;
6073
6283
  default:
6074
- return import_types19.EdgeType.CALLS;
6284
+ return import_types20.EdgeType.CALLS;
6075
6285
  }
6076
6286
  }
6077
6287
  function isAwsKind(kind) {
@@ -6099,7 +6309,7 @@ async function addExternalEndpointEdges(graph, services) {
6099
6309
  if (!graph.hasNode(ep.infraId)) {
6100
6310
  const node = {
6101
6311
  id: ep.infraId,
6102
- type: import_types19.NodeType.InfraNode,
6312
+ type: import_types20.NodeType.InfraNode,
6103
6313
  name: ep.name,
6104
6314
  // #238 — `aws-*` covers AWS-SDK client kinds (aws-s3, aws-dynamodb,
6105
6315
  // aws-cognito-identity-provider, …); `s3-` / `dynamodb-` cover the
@@ -6111,7 +6321,7 @@ async function addExternalEndpointEdges(graph, services) {
6111
6321
  nodesAdded++;
6112
6322
  }
6113
6323
  const edgeType = edgeTypeFromEndpoint(ep);
6114
- const confidence = (0, import_types19.confidenceForExtracted)(ep.confidenceKind);
6324
+ const confidence = (0, import_types20.confidenceForExtracted)(ep.confidenceKind);
6115
6325
  const relFile = toPosix2(ep.evidence.file);
6116
6326
  const { fileNodeId, nodesAdded: n, edgesAdded: e } = ensureFileNode(
6117
6327
  graph,
@@ -6121,7 +6331,7 @@ async function addExternalEndpointEdges(graph, services) {
6121
6331
  );
6122
6332
  nodesAdded += n;
6123
6333
  edgesAdded += e;
6124
- if (!(0, import_types19.passesExtractedFloor)(confidence)) {
6334
+ if (!(0, import_types20.passesExtractedFloor)(confidence)) {
6125
6335
  noteExtractedDropped({
6126
6336
  source: fileNodeId,
6127
6337
  target: ep.infraId,
@@ -6141,7 +6351,7 @@ async function addExternalEndpointEdges(graph, services) {
6141
6351
  source: fileNodeId,
6142
6352
  target: ep.infraId,
6143
6353
  type: edgeType,
6144
- provenance: import_types19.Provenance.EXTRACTED,
6354
+ provenance: import_types20.Provenance.EXTRACTED,
6145
6355
  confidence,
6146
6356
  evidence: ep.evidence
6147
6357
  };
@@ -6167,16 +6377,16 @@ init_cjs_shims();
6167
6377
 
6168
6378
  // src/extract/infra/docker-compose.ts
6169
6379
  init_cjs_shims();
6170
- var import_node_path30 = __toESM(require("path"), 1);
6171
- var import_types21 = require("@neat.is/types");
6380
+ var import_node_path31 = __toESM(require("path"), 1);
6381
+ var import_types22 = require("@neat.is/types");
6172
6382
 
6173
6383
  // src/extract/infra/shared.ts
6174
6384
  init_cjs_shims();
6175
- var import_types20 = require("@neat.is/types");
6385
+ var import_types21 = require("@neat.is/types");
6176
6386
  function makeInfraNode(kind, name, provider = "self", extras) {
6177
6387
  return {
6178
- id: (0, import_types20.infraId)(kind, name),
6179
- type: import_types20.NodeType.InfraNode,
6388
+ id: (0, import_types21.infraId)(kind, name),
6389
+ type: import_types21.NodeType.InfraNode,
6180
6390
  name,
6181
6391
  provider,
6182
6392
  kind,
@@ -6205,7 +6415,7 @@ function dependsOnList(value) {
6205
6415
  }
6206
6416
  function serviceNameToServiceNode(name, services) {
6207
6417
  for (const s of services) {
6208
- if (s.node.name === name || import_node_path30.default.basename(s.dir) === name) return s.node.id;
6418
+ if (s.node.name === name || import_node_path31.default.basename(s.dir) === name) return s.node.id;
6209
6419
  }
6210
6420
  return null;
6211
6421
  }
@@ -6214,7 +6424,7 @@ async function addComposeInfra(graph, scanPath, services) {
6214
6424
  let edgesAdded = 0;
6215
6425
  let composePath = null;
6216
6426
  for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
6217
- const abs = import_node_path30.default.join(scanPath, name);
6427
+ const abs = import_node_path31.default.join(scanPath, name);
6218
6428
  if (await exists(abs)) {
6219
6429
  composePath = abs;
6220
6430
  break;
@@ -6227,13 +6437,13 @@ async function addComposeInfra(graph, scanPath, services) {
6227
6437
  } catch (err) {
6228
6438
  recordExtractionError(
6229
6439
  "infra docker-compose",
6230
- import_node_path30.default.relative(scanPath, composePath),
6440
+ import_node_path31.default.relative(scanPath, composePath),
6231
6441
  err
6232
6442
  );
6233
6443
  return { nodesAdded, edgesAdded };
6234
6444
  }
6235
6445
  if (!compose?.services) return { nodesAdded, edgesAdded };
6236
- const evidenceFile = import_node_path30.default.relative(scanPath, composePath).split(import_node_path30.default.sep).join("/");
6446
+ const evidenceFile = import_node_path31.default.relative(scanPath, composePath).split(import_node_path31.default.sep).join("/");
6237
6447
  const composeNameToNodeId = /* @__PURE__ */ new Map();
6238
6448
  for (const [composeName, svc] of Object.entries(compose.services)) {
6239
6449
  const matchedServiceId = serviceNameToServiceNode(composeName, services);
@@ -6255,15 +6465,15 @@ async function addComposeInfra(graph, scanPath, services) {
6255
6465
  for (const dep of dependsOnList(svc.depends_on)) {
6256
6466
  const targetId = composeNameToNodeId.get(dep);
6257
6467
  if (!targetId) continue;
6258
- const edgeId = (0, import_types4.extractedEdgeId)(sourceId, targetId, import_types21.EdgeType.DEPENDS_ON);
6468
+ const edgeId = (0, import_types4.extractedEdgeId)(sourceId, targetId, import_types22.EdgeType.DEPENDS_ON);
6259
6469
  if (graph.hasEdge(edgeId)) continue;
6260
6470
  const edge = {
6261
6471
  id: edgeId,
6262
6472
  source: sourceId,
6263
6473
  target: targetId,
6264
- type: import_types21.EdgeType.DEPENDS_ON,
6265
- provenance: import_types21.Provenance.EXTRACTED,
6266
- confidence: (0, import_types21.confidenceForExtracted)("structural"),
6474
+ type: import_types22.EdgeType.DEPENDS_ON,
6475
+ provenance: import_types22.Provenance.EXTRACTED,
6476
+ confidence: (0, import_types22.confidenceForExtracted)("structural"),
6267
6477
  evidence: { file: evidenceFile }
6268
6478
  };
6269
6479
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -6275,9 +6485,9 @@ async function addComposeInfra(graph, scanPath, services) {
6275
6485
 
6276
6486
  // src/extract/infra/dockerfile.ts
6277
6487
  init_cjs_shims();
6278
- var import_node_path31 = __toESM(require("path"), 1);
6279
- var import_node_fs15 = require("fs");
6280
- var import_types22 = require("@neat.is/types");
6488
+ var import_node_path32 = __toESM(require("path"), 1);
6489
+ var import_node_fs16 = require("fs");
6490
+ var import_types23 = require("@neat.is/types");
6281
6491
  function readDockerfile(content) {
6282
6492
  let image = null;
6283
6493
  const ports = [];
@@ -6306,15 +6516,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
6306
6516
  let nodesAdded = 0;
6307
6517
  let edgesAdded = 0;
6308
6518
  for (const service of services) {
6309
- const dockerfilePath = import_node_path31.default.join(service.dir, "Dockerfile");
6519
+ const dockerfilePath = import_node_path32.default.join(service.dir, "Dockerfile");
6310
6520
  if (!await exists(dockerfilePath)) continue;
6311
6521
  let content;
6312
6522
  try {
6313
- content = await import_node_fs15.promises.readFile(dockerfilePath, "utf8");
6523
+ content = await import_node_fs16.promises.readFile(dockerfilePath, "utf8");
6314
6524
  } catch (err) {
6315
6525
  recordExtractionError(
6316
6526
  "infra dockerfile",
6317
- import_node_path31.default.relative(scanPath, dockerfilePath),
6527
+ import_node_path32.default.relative(scanPath, dockerfilePath),
6318
6528
  err
6319
6529
  );
6320
6530
  continue;
@@ -6326,8 +6536,8 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
6326
6536
  graph.addNode(node.id, node);
6327
6537
  nodesAdded++;
6328
6538
  }
6329
- const relDockerfile = toPosix2(import_node_path31.default.relative(service.dir, dockerfilePath));
6330
- const evidenceFile = toPosix2(import_node_path31.default.relative(scanPath, dockerfilePath));
6539
+ const relDockerfile = toPosix2(import_node_path32.default.relative(service.dir, dockerfilePath));
6540
+ const evidenceFile = toPosix2(import_node_path32.default.relative(scanPath, dockerfilePath));
6331
6541
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
6332
6542
  graph,
6333
6543
  service.pkg.name,
@@ -6336,15 +6546,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
6336
6546
  );
6337
6547
  nodesAdded += fn;
6338
6548
  edgesAdded += fe;
6339
- const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, node.id, import_types22.EdgeType.RUNS_ON);
6549
+ const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, node.id, import_types23.EdgeType.RUNS_ON);
6340
6550
  if (!graph.hasEdge(edgeId)) {
6341
6551
  const edge = {
6342
6552
  id: edgeId,
6343
6553
  source: fileNodeId,
6344
6554
  target: node.id,
6345
- type: import_types22.EdgeType.RUNS_ON,
6346
- provenance: import_types22.Provenance.EXTRACTED,
6347
- confidence: (0, import_types22.confidenceForExtracted)("structural"),
6555
+ type: import_types23.EdgeType.RUNS_ON,
6556
+ provenance: import_types23.Provenance.EXTRACTED,
6557
+ confidence: (0, import_types23.confidenceForExtracted)("structural"),
6348
6558
  evidence: {
6349
6559
  file: evidenceFile,
6350
6560
  ...facts.entrypoint ? { snippet: facts.entrypoint.slice(0, 120) } : {}
@@ -6359,15 +6569,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
6359
6569
  graph.addNode(portNode.id, portNode);
6360
6570
  nodesAdded++;
6361
6571
  }
6362
- const portEdgeId = (0, import_types4.extractedEdgeId)(fileNodeId, portNode.id, import_types22.EdgeType.CONNECTS_TO);
6572
+ const portEdgeId = (0, import_types4.extractedEdgeId)(fileNodeId, portNode.id, import_types23.EdgeType.CONNECTS_TO);
6363
6573
  if (graph.hasEdge(portEdgeId)) continue;
6364
6574
  const portEdge = {
6365
6575
  id: portEdgeId,
6366
6576
  source: fileNodeId,
6367
6577
  target: portNode.id,
6368
- type: import_types22.EdgeType.CONNECTS_TO,
6369
- provenance: import_types22.Provenance.EXTRACTED,
6370
- confidence: (0, import_types22.confidenceForExtracted)("structural"),
6578
+ type: import_types23.EdgeType.CONNECTS_TO,
6579
+ provenance: import_types23.Provenance.EXTRACTED,
6580
+ confidence: (0, import_types23.confidenceForExtracted)("structural"),
6371
6581
  evidence: { file: evidenceFile, snippet: `EXPOSE ${port}` }
6372
6582
  };
6373
6583
  graph.addEdgeWithKey(portEdgeId, portEdge.source, portEdge.target, portEdge);
@@ -6379,23 +6589,23 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
6379
6589
 
6380
6590
  // src/extract/infra/terraform.ts
6381
6591
  init_cjs_shims();
6382
- var import_node_fs16 = require("fs");
6383
- var import_node_path32 = __toESM(require("path"), 1);
6384
- var import_types23 = require("@neat.is/types");
6592
+ var import_node_fs17 = require("fs");
6593
+ var import_node_path33 = __toESM(require("path"), 1);
6594
+ var import_types24 = require("@neat.is/types");
6385
6595
  var RESOURCE_RE = /resource\s+"(aws_[A-Za-z0-9_]+)"\s+"([A-Za-z0-9_-]+)"/g;
6386
6596
  var REFERENCE_RE = /(?<![\w.])(aws_[A-Za-z0-9_]+)\.([A-Za-z0-9_-]+)/g;
6387
6597
  async function walkTfFiles(start, depth = 0, max = 5) {
6388
6598
  if (depth > max) return [];
6389
6599
  const out = [];
6390
- const entries = await import_node_fs16.promises.readdir(start, { withFileTypes: true }).catch(() => []);
6600
+ const entries = await import_node_fs17.promises.readdir(start, { withFileTypes: true }).catch(() => []);
6391
6601
  for (const entry2 of entries) {
6392
6602
  if (entry2.isDirectory()) {
6393
6603
  if (IGNORED_DIRS.has(entry2.name) || entry2.name === ".terraform") continue;
6394
- const child = import_node_path32.default.join(start, entry2.name);
6604
+ const child = import_node_path33.default.join(start, entry2.name);
6395
6605
  if (await isPythonVenvDir(child)) continue;
6396
6606
  out.push(...await walkTfFiles(child, depth + 1, max));
6397
6607
  } else if (entry2.isFile() && entry2.name.endsWith(".tf")) {
6398
- out.push(import_node_path32.default.join(start, entry2.name));
6608
+ out.push(import_node_path33.default.join(start, entry2.name));
6399
6609
  }
6400
6610
  }
6401
6611
  return out;
@@ -6414,7 +6624,7 @@ function blockBody(content, from) {
6414
6624
  }
6415
6625
  return null;
6416
6626
  }
6417
- function lineAt(content, index) {
6627
+ function lineAt2(content, index) {
6418
6628
  let line = 1;
6419
6629
  for (let i = 0; i < index && i < content.length; i++) {
6420
6630
  if (content[i] === "\n") line++;
@@ -6426,8 +6636,8 @@ async function addTerraformResources(graph, scanPath) {
6426
6636
  let edgesAdded = 0;
6427
6637
  const files = await walkTfFiles(scanPath);
6428
6638
  for (const file of files) {
6429
- const content = await import_node_fs16.promises.readFile(file, "utf8");
6430
- const evidenceFile = toPosix2(import_node_path32.default.relative(scanPath, file));
6639
+ const content = await import_node_fs17.promises.readFile(file, "utf8");
6640
+ const evidenceFile = toPosix2(import_node_path33.default.relative(scanPath, file));
6431
6641
  const resources = [];
6432
6642
  const byKey = /* @__PURE__ */ new Map();
6433
6643
  RESOURCE_RE.lastIndex = 0;
@@ -6462,16 +6672,16 @@ async function addTerraformResources(graph, scanPath) {
6462
6672
  if (!target) continue;
6463
6673
  if (seen.has(target.nodeId)) continue;
6464
6674
  seen.add(target.nodeId);
6465
- const edgeId = (0, import_types4.extractedEdgeId)(resource.nodeId, target.nodeId, import_types23.EdgeType.DEPENDS_ON);
6675
+ const edgeId = (0, import_types4.extractedEdgeId)(resource.nodeId, target.nodeId, import_types24.EdgeType.DEPENDS_ON);
6466
6676
  if (graph.hasEdge(edgeId)) continue;
6467
- const line = lineAt(content, resource.bodyOffset + ref.index);
6677
+ const line = lineAt2(content, resource.bodyOffset + ref.index);
6468
6678
  const edge = {
6469
6679
  id: edgeId,
6470
6680
  source: resource.nodeId,
6471
6681
  target: target.nodeId,
6472
- type: import_types23.EdgeType.DEPENDS_ON,
6473
- provenance: import_types23.Provenance.EXTRACTED,
6474
- confidence: (0, import_types23.confidenceForExtracted)("structural"),
6682
+ type: import_types24.EdgeType.DEPENDS_ON,
6683
+ provenance: import_types24.Provenance.EXTRACTED,
6684
+ confidence: (0, import_types24.confidenceForExtracted)("structural"),
6475
6685
  evidence: { file: evidenceFile, line, snippet: key }
6476
6686
  };
6477
6687
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -6484,8 +6694,8 @@ async function addTerraformResources(graph, scanPath) {
6484
6694
 
6485
6695
  // src/extract/infra/k8s.ts
6486
6696
  init_cjs_shims();
6487
- var import_node_fs17 = require("fs");
6488
- var import_node_path33 = __toESM(require("path"), 1);
6697
+ var import_node_fs18 = require("fs");
6698
+ var import_node_path34 = __toESM(require("path"), 1);
6489
6699
  var import_yaml3 = require("yaml");
6490
6700
  var K8S_KIND_TO_INFRA_KIND = {
6491
6701
  Service: "k8s-service",
@@ -6499,15 +6709,15 @@ var K8S_KIND_TO_INFRA_KIND = {
6499
6709
  async function walkYamlFiles2(start, depth = 0, max = 5) {
6500
6710
  if (depth > max) return [];
6501
6711
  const out = [];
6502
- const entries = await import_node_fs17.promises.readdir(start, { withFileTypes: true }).catch(() => []);
6712
+ const entries = await import_node_fs18.promises.readdir(start, { withFileTypes: true }).catch(() => []);
6503
6713
  for (const entry2 of entries) {
6504
6714
  if (entry2.isDirectory()) {
6505
6715
  if (IGNORED_DIRS.has(entry2.name)) continue;
6506
- const child = import_node_path33.default.join(start, entry2.name);
6716
+ const child = import_node_path34.default.join(start, entry2.name);
6507
6717
  if (await isPythonVenvDir(child)) continue;
6508
6718
  out.push(...await walkYamlFiles2(child, depth + 1, max));
6509
- } else if (entry2.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path33.default.extname(entry2.name))) {
6510
- out.push(import_node_path33.default.join(start, entry2.name));
6719
+ } else if (entry2.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path34.default.extname(entry2.name))) {
6720
+ out.push(import_node_path34.default.join(start, entry2.name));
6511
6721
  }
6512
6722
  }
6513
6723
  return out;
@@ -6516,7 +6726,7 @@ async function addK8sResources(graph, scanPath) {
6516
6726
  let nodesAdded = 0;
6517
6727
  const files = await walkYamlFiles2(scanPath);
6518
6728
  for (const file of files) {
6519
- const content = await import_node_fs17.promises.readFile(file, "utf8");
6729
+ const content = await import_node_fs18.promises.readFile(file, "utf8");
6520
6730
  let docs;
6521
6731
  try {
6522
6732
  docs = (0, import_yaml3.parseAllDocuments)(content).map((d) => d.toJSON());
@@ -6551,17 +6761,17 @@ async function addInfra(graph, scanPath, services) {
6551
6761
  }
6552
6762
 
6553
6763
  // src/extract/index.ts
6554
- var import_node_path35 = __toESM(require("path"), 1);
6764
+ var import_node_path36 = __toESM(require("path"), 1);
6555
6765
 
6556
6766
  // src/extract/retire.ts
6557
6767
  init_cjs_shims();
6558
- var import_node_fs18 = require("fs");
6559
- var import_node_path34 = __toESM(require("path"), 1);
6560
- var import_types24 = require("@neat.is/types");
6768
+ var import_node_fs19 = require("fs");
6769
+ var import_node_path35 = __toESM(require("path"), 1);
6770
+ var import_types25 = require("@neat.is/types");
6561
6771
  function dropOrphanedFileNodes(graph) {
6562
6772
  const orphans = [];
6563
6773
  graph.forEachNode((id, attrs) => {
6564
- if (attrs.type !== import_types24.NodeType.FileNode) return;
6774
+ if (attrs.type !== import_types25.NodeType.FileNode) return;
6565
6775
  if (graph.inboundEdges(id).length === 0 && graph.outboundEdges(id).length === 0) {
6566
6776
  orphans.push(id);
6567
6777
  }
@@ -6574,14 +6784,14 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
6574
6784
  const bases = [scanPath, ...serviceDirs];
6575
6785
  graph.forEachEdge((id, attrs) => {
6576
6786
  const edge = attrs;
6577
- if (edge.provenance !== import_types24.Provenance.EXTRACTED) return;
6787
+ if (edge.provenance !== import_types25.Provenance.EXTRACTED) return;
6578
6788
  const evidenceFile = edge.evidence?.file;
6579
6789
  if (!evidenceFile) return;
6580
- if (import_node_path34.default.isAbsolute(evidenceFile)) {
6581
- if (!(0, import_node_fs18.existsSync)(evidenceFile)) toDrop.push(id);
6790
+ if (import_node_path35.default.isAbsolute(evidenceFile)) {
6791
+ if (!(0, import_node_fs19.existsSync)(evidenceFile)) toDrop.push(id);
6582
6792
  return;
6583
6793
  }
6584
- const found = bases.some((base) => (0, import_node_fs18.existsSync)(import_node_path34.default.join(base, evidenceFile)));
6794
+ const found = bases.some((base) => (0, import_node_fs19.existsSync)(import_node_path35.default.join(base, evidenceFile)));
6585
6795
  if (!found) toDrop.push(id);
6586
6796
  });
6587
6797
  for (const id of toDrop) graph.dropEdge(id);
@@ -6601,6 +6811,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
6601
6811
  const phase2 = await addDatabasesAndCompat(graph, services, scanPath);
6602
6812
  const phase3 = await addConfigNodes(graph, services, scanPath);
6603
6813
  const routePhase = await addRoutes(graph, services);
6814
+ const grpcPhase = await addGrpcMethods(graph, services);
6604
6815
  const phase4 = await addCallEdges(graph, services);
6605
6816
  const phase5 = await addInfra(graph, scanPath, services);
6606
6817
  const ghostsRetired = retireExtractedEdgesByMissingFile(
@@ -6622,7 +6833,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
6622
6833
  }
6623
6834
  const droppedEntries = drainDroppedExtracted();
6624
6835
  if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
6625
- const rejectedPath = import_node_path35.default.join(import_node_path35.default.dirname(opts.errorsPath), "rejected.ndjson");
6836
+ const rejectedPath = import_node_path36.default.join(import_node_path36.default.dirname(opts.errorsPath), "rejected.ndjson");
6626
6837
  try {
6627
6838
  await writeRejectedExtracted(droppedEntries, rejectedPath);
6628
6839
  } catch (err) {
@@ -6632,8 +6843,8 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
6632
6843
  }
6633
6844
  }
6634
6845
  const result = {
6635
- nodesAdded: phase1Nodes + fileEnum.nodesAdded + importGraph.nodesAdded + phase2.nodesAdded + phase3.nodesAdded + routePhase.nodesAdded + phase4.nodesAdded + phase5.nodesAdded,
6636
- edgesAdded: fileEnum.edgesAdded + importGraph.edgesAdded + phase2.edgesAdded + phase3.edgesAdded + routePhase.edgesAdded + phase4.edgesAdded + phase5.edgesAdded,
6846
+ nodesAdded: phase1Nodes + fileEnum.nodesAdded + importGraph.nodesAdded + phase2.nodesAdded + phase3.nodesAdded + routePhase.nodesAdded + grpcPhase.nodesAdded + phase4.nodesAdded + phase5.nodesAdded,
6847
+ edgesAdded: fileEnum.edgesAdded + importGraph.edgesAdded + phase2.edgesAdded + phase3.edgesAdded + routePhase.edgesAdded + grpcPhase.edgesAdded + phase4.edgesAdded + phase5.edgesAdded,
6637
6848
  frontiersPromoted,
6638
6849
  extractionErrors: errorEntries.length,
6639
6850
  errorEntries,
@@ -6656,9 +6867,9 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
6656
6867
 
6657
6868
  // src/persist.ts
6658
6869
  init_cjs_shims();
6659
- var import_node_fs19 = require("fs");
6660
- var import_node_path36 = __toESM(require("path"), 1);
6661
- var import_types25 = require("@neat.is/types");
6870
+ var import_node_fs20 = require("fs");
6871
+ var import_node_path37 = __toESM(require("path"), 1);
6872
+ var import_types26 = require("@neat.is/types");
6662
6873
  var SCHEMA_VERSION = 4;
6663
6874
  function migrateV1ToV2(payload) {
6664
6875
  const nodes = payload.graph.nodes;
@@ -6680,12 +6891,12 @@ function migrateV2ToV3(payload) {
6680
6891
  for (const edge of edges) {
6681
6892
  const attrs = edge.attributes;
6682
6893
  if (!attrs || attrs.provenance !== "FRONTIER") continue;
6683
- attrs.provenance = import_types25.Provenance.OBSERVED;
6894
+ attrs.provenance = import_types26.Provenance.OBSERVED;
6684
6895
  const type = typeof attrs.type === "string" ? attrs.type : void 0;
6685
6896
  const source = typeof attrs.source === "string" ? attrs.source : void 0;
6686
6897
  const target = typeof attrs.target === "string" ? attrs.target : void 0;
6687
6898
  if (type && source && target) {
6688
- const newId = (0, import_types25.observedEdgeId)(source, target, type);
6899
+ const newId = (0, import_types26.observedEdgeId)(source, target, type);
6689
6900
  attrs.id = newId;
6690
6901
  if (edge.key) edge.key = newId;
6691
6902
  }
@@ -6694,7 +6905,7 @@ function migrateV2ToV3(payload) {
6694
6905
  return { ...payload, schemaVersion: 3 };
6695
6906
  }
6696
6907
  async function ensureDir(filePath) {
6697
- await import_node_fs19.promises.mkdir(import_node_path36.default.dirname(filePath), { recursive: true });
6908
+ await import_node_fs20.promises.mkdir(import_node_path37.default.dirname(filePath), { recursive: true });
6698
6909
  }
6699
6910
  async function saveGraphToDisk(graph, outPath) {
6700
6911
  await ensureDir(outPath);
@@ -6704,13 +6915,13 @@ async function saveGraphToDisk(graph, outPath) {
6704
6915
  graph: graph.export()
6705
6916
  };
6706
6917
  const tmp = `${outPath}.tmp`;
6707
- await import_node_fs19.promises.writeFile(tmp, JSON.stringify(payload), "utf8");
6708
- await import_node_fs19.promises.rename(tmp, outPath);
6918
+ await import_node_fs20.promises.writeFile(tmp, JSON.stringify(payload), "utf8");
6919
+ await import_node_fs20.promises.rename(tmp, outPath);
6709
6920
  }
6710
6921
  async function loadGraphFromDisk(graph, outPath) {
6711
6922
  let raw;
6712
6923
  try {
6713
- raw = await import_node_fs19.promises.readFile(outPath, "utf8");
6924
+ raw = await import_node_fs20.promises.readFile(outPath, "utf8");
6714
6925
  } catch (err) {
6715
6926
  if (err.code === "ENOENT") return;
6716
6927
  throw err;
@@ -6775,23 +6986,23 @@ function startPersistLoop(graph, outPath, opts = {}) {
6775
6986
 
6776
6987
  // src/projects.ts
6777
6988
  init_cjs_shims();
6778
- var import_node_path37 = __toESM(require("path"), 1);
6989
+ var import_node_path38 = __toESM(require("path"), 1);
6779
6990
  function pathsForProject(project, baseDir) {
6780
6991
  if (project === DEFAULT_PROJECT) {
6781
6992
  return {
6782
- snapshotPath: import_node_path37.default.join(baseDir, "graph.json"),
6783
- errorsPath: import_node_path37.default.join(baseDir, "errors.ndjson"),
6784
- staleEventsPath: import_node_path37.default.join(baseDir, "stale-events.ndjson"),
6785
- embeddingsCachePath: import_node_path37.default.join(baseDir, "embeddings.json"),
6786
- policyViolationsPath: import_node_path37.default.join(baseDir, "policy-violations.ndjson")
6993
+ snapshotPath: import_node_path38.default.join(baseDir, "graph.json"),
6994
+ errorsPath: import_node_path38.default.join(baseDir, "errors.ndjson"),
6995
+ staleEventsPath: import_node_path38.default.join(baseDir, "stale-events.ndjson"),
6996
+ embeddingsCachePath: import_node_path38.default.join(baseDir, "embeddings.json"),
6997
+ policyViolationsPath: import_node_path38.default.join(baseDir, "policy-violations.ndjson")
6787
6998
  };
6788
6999
  }
6789
7000
  return {
6790
- snapshotPath: import_node_path37.default.join(baseDir, `${project}.json`),
6791
- errorsPath: import_node_path37.default.join(baseDir, `errors.${project}.ndjson`),
6792
- staleEventsPath: import_node_path37.default.join(baseDir, `stale-events.${project}.ndjson`),
6793
- embeddingsCachePath: import_node_path37.default.join(baseDir, `embeddings.${project}.json`),
6794
- policyViolationsPath: import_node_path37.default.join(baseDir, `policy-violations.${project}.ndjson`)
7001
+ snapshotPath: import_node_path38.default.join(baseDir, `${project}.json`),
7002
+ errorsPath: import_node_path38.default.join(baseDir, `errors.${project}.ndjson`),
7003
+ staleEventsPath: import_node_path38.default.join(baseDir, `stale-events.${project}.ndjson`),
7004
+ embeddingsCachePath: import_node_path38.default.join(baseDir, `embeddings.${project}.json`),
7005
+ policyViolationsPath: import_node_path38.default.join(baseDir, `policy-violations.${project}.ndjson`)
6795
7006
  };
6796
7007
  }
6797
7008
  var Projects = class {
@@ -6829,19 +7040,19 @@ var Projects = class {
6829
7040
  init_cjs_shims();
6830
7041
  var import_fastify = __toESM(require("fastify"), 1);
6831
7042
  var import_cors = __toESM(require("@fastify/cors"), 1);
6832
- var import_types28 = require("@neat.is/types");
7043
+ var import_types29 = require("@neat.is/types");
6833
7044
 
6834
7045
  // src/extend/index.ts
6835
7046
  init_cjs_shims();
6836
- var import_node_fs21 = require("fs");
6837
- var import_node_path39 = __toESM(require("path"), 1);
7047
+ var import_node_fs22 = require("fs");
7048
+ var import_node_path40 = __toESM(require("path"), 1);
6838
7049
  var import_node_os2 = __toESM(require("os"), 1);
6839
7050
  var import_instrumentation_registry = require("@neat.is/instrumentation-registry");
6840
7051
 
6841
7052
  // src/installers/package-manager.ts
6842
7053
  init_cjs_shims();
6843
- var import_node_fs20 = require("fs");
6844
- var import_node_path38 = __toESM(require("path"), 1);
7054
+ var import_node_fs21 = require("fs");
7055
+ var import_node_path39 = __toESM(require("path"), 1);
6845
7056
  var import_node_child_process = require("child_process");
6846
7057
  var LOCKFILE_PRIORITY = [
6847
7058
  { lockfile: "bun.lockb", pm: "bun", args: ["install", "--no-summary"] },
@@ -6856,29 +7067,29 @@ var LOCKFILE_PRIORITY = [
6856
7067
  var NPM_FALLBACK_ARGS = ["install", "--no-audit", "--no-fund", "--prefer-offline"];
6857
7068
  async function exists2(p) {
6858
7069
  try {
6859
- await import_node_fs20.promises.access(p);
7070
+ await import_node_fs21.promises.access(p);
6860
7071
  return true;
6861
7072
  } catch {
6862
7073
  return false;
6863
7074
  }
6864
7075
  }
6865
7076
  async function detectPackageManager(serviceDir) {
6866
- let dir = import_node_path38.default.resolve(serviceDir);
7077
+ let dir = import_node_path39.default.resolve(serviceDir);
6867
7078
  const stops = /* @__PURE__ */ new Set();
6868
7079
  for (let i = 0; i < 64; i++) {
6869
7080
  if (stops.has(dir)) break;
6870
7081
  stops.add(dir);
6871
7082
  for (const candidate of LOCKFILE_PRIORITY) {
6872
- const lockPath = import_node_path38.default.join(dir, candidate.lockfile);
7083
+ const lockPath = import_node_path39.default.join(dir, candidate.lockfile);
6873
7084
  if (await exists2(lockPath)) {
6874
7085
  return { pm: candidate.pm, cwd: dir, args: [...candidate.args] };
6875
7086
  }
6876
7087
  }
6877
- const parent = import_node_path38.default.dirname(dir);
7088
+ const parent = import_node_path39.default.dirname(dir);
6878
7089
  if (parent === dir) break;
6879
7090
  dir = parent;
6880
7091
  }
6881
- return { pm: "npm", cwd: import_node_path38.default.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
7092
+ return { pm: "npm", cwd: import_node_path39.default.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
6882
7093
  }
6883
7094
  async function runPackageManagerInstall(cmd) {
6884
7095
  return new Promise((resolve) => {
@@ -6920,30 +7131,30 @@ ${err.message}`
6920
7131
  // src/extend/index.ts
6921
7132
  async function fileExists2(p) {
6922
7133
  try {
6923
- await import_node_fs21.promises.access(p);
7134
+ await import_node_fs22.promises.access(p);
6924
7135
  return true;
6925
7136
  } catch {
6926
7137
  return false;
6927
7138
  }
6928
7139
  }
6929
7140
  async function readPackageJson(scanPath) {
6930
- const pkgPath = import_node_path39.default.join(scanPath, "package.json");
6931
- const raw = await import_node_fs21.promises.readFile(pkgPath, "utf8");
7141
+ const pkgPath = import_node_path40.default.join(scanPath, "package.json");
7142
+ const raw = await import_node_fs22.promises.readFile(pkgPath, "utf8");
6932
7143
  return JSON.parse(raw);
6933
7144
  }
6934
7145
  async function findHookFiles(scanPath) {
6935
- const entries = await import_node_fs21.promises.readdir(scanPath);
7146
+ const entries = await import_node_fs22.promises.readdir(scanPath);
6936
7147
  return entries.filter(
6937
7148
  (e) => (e.startsWith("instrumentation") || e.startsWith("otel-init")) && /\.(ts|js)$/.test(e)
6938
7149
  ).sort();
6939
7150
  }
6940
7151
  function extendLogPath() {
6941
- return process.env.NEAT_EXTEND_LOG ?? import_node_path39.default.join(import_node_os2.default.homedir(), ".neat", "extend-log.ndjson");
7152
+ return process.env.NEAT_EXTEND_LOG ?? import_node_path40.default.join(import_node_os2.default.homedir(), ".neat", "extend-log.ndjson");
6942
7153
  }
6943
7154
  async function appendExtendLog(entry2) {
6944
7155
  const logPath = extendLogPath();
6945
- await import_node_fs21.promises.mkdir(import_node_path39.default.dirname(logPath), { recursive: true });
6946
- await import_node_fs21.promises.appendFile(logPath, JSON.stringify(entry2) + "\n", "utf8");
7156
+ await import_node_fs22.promises.mkdir(import_node_path40.default.dirname(logPath), { recursive: true });
7157
+ await import_node_fs22.promises.appendFile(logPath, JSON.stringify(entry2) + "\n", "utf8");
6947
7158
  }
6948
7159
  function splicedContent(fileContent, snippet2) {
6949
7160
  if (fileContent.includes("__INSTRUMENTATION_BLOCK__")) {
@@ -7001,7 +7212,7 @@ function lookupInstrumentation(library, installedVersion) {
7001
7212
  }
7002
7213
  async function describeProjectInstrumentation(ctx) {
7003
7214
  const hookFiles = await findHookFiles(ctx.scanPath);
7004
- const envNeat = await fileExists2(import_node_path39.default.join(ctx.scanPath, ".env.neat"));
7215
+ const envNeat = await fileExists2(import_node_path40.default.join(ctx.scanPath, ".env.neat"));
7005
7216
  const registryInstrPackages = new Set(
7006
7217
  (0, import_instrumentation_registry.list)().map((e) => e.instrumentation_package).filter((p) => !!p)
7007
7218
  );
@@ -7023,31 +7234,31 @@ async function applyExtension(ctx, args, options) {
7023
7234
  );
7024
7235
  }
7025
7236
  for (const file of hookFiles) {
7026
- const content = await import_node_fs21.promises.readFile(import_node_path39.default.join(ctx.scanPath, file), "utf8");
7237
+ const content = await import_node_fs22.promises.readFile(import_node_path40.default.join(ctx.scanPath, file), "utf8");
7027
7238
  if (content.includes(args.registration_snippet)) {
7028
7239
  return { library: args.library, filesTouched: [], depsAdded: [], installOutput: "", alreadyApplied: true };
7029
7240
  }
7030
7241
  }
7031
7242
  const primaryFile = hookFiles[0];
7032
- const primaryPath = import_node_path39.default.join(ctx.scanPath, primaryFile);
7243
+ const primaryPath = import_node_path40.default.join(ctx.scanPath, primaryFile);
7033
7244
  const filesTouched = [];
7034
7245
  const depsAdded = [];
7035
- const pkgPath = import_node_path39.default.join(ctx.scanPath, "package.json");
7246
+ const pkgPath = import_node_path40.default.join(ctx.scanPath, "package.json");
7036
7247
  const pkg = await readPackageJson(ctx.scanPath);
7037
7248
  if (!(pkg.dependencies ?? {})[args.instrumentation_package]) {
7038
7249
  pkg.dependencies = { ...pkg.dependencies ?? {}, [args.instrumentation_package]: args.version };
7039
- await import_node_fs21.promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
7250
+ await import_node_fs22.promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
7040
7251
  filesTouched.push("package.json");
7041
7252
  depsAdded.push(`${args.instrumentation_package}@${args.version}`);
7042
7253
  }
7043
- const hookContent = await import_node_fs21.promises.readFile(primaryPath, "utf8");
7254
+ const hookContent = await import_node_fs22.promises.readFile(primaryPath, "utf8");
7044
7255
  const patched = splicedContent(hookContent, args.registration_snippet);
7045
7256
  if (!patched) {
7046
7257
  throw new Error(
7047
7258
  `Could not find instrumentation insertion point in ${primaryFile}. Expected __INSTRUMENTATION_BLOCK__, instrumentations.push(, or new NodeSDK(.`
7048
7259
  );
7049
7260
  }
7050
- await import_node_fs21.promises.writeFile(primaryPath, patched, "utf8");
7261
+ await import_node_fs22.promises.writeFile(primaryPath, patched, "utf8");
7051
7262
  filesTouched.push(primaryFile);
7052
7263
  const cmd = await detectPackageManager(ctx.scanPath);
7053
7264
  const installer = options?.runInstall ?? runPackageManagerInstall;
@@ -7078,7 +7289,7 @@ async function dryRunExtension(ctx, args) {
7078
7289
  };
7079
7290
  }
7080
7291
  for (const file of hookFiles) {
7081
- const content = await import_node_fs21.promises.readFile(import_node_path39.default.join(ctx.scanPath, file), "utf8");
7292
+ const content = await import_node_fs22.promises.readFile(import_node_path40.default.join(ctx.scanPath, file), "utf8");
7082
7293
  if (content.includes(args.registration_snippet)) {
7083
7294
  return {
7084
7295
  library: args.library,
@@ -7100,7 +7311,7 @@ async function dryRunExtension(ctx, args) {
7100
7311
  depsToAdd.push(`${args.instrumentation_package}@${args.version}`);
7101
7312
  filesTouched.push("package.json");
7102
7313
  }
7103
- const hookContent = await import_node_fs21.promises.readFile(import_node_path39.default.join(ctx.scanPath, primaryFile), "utf8");
7314
+ const hookContent = await import_node_fs22.promises.readFile(import_node_path40.default.join(ctx.scanPath, primaryFile), "utf8");
7104
7315
  const patched = splicedContent(hookContent, args.registration_snippet);
7105
7316
  if (patched) {
7106
7317
  filesTouched.push(primaryFile);
@@ -7115,28 +7326,28 @@ async function rollbackExtension(ctx, args) {
7115
7326
  if (!await fileExists2(logPath)) {
7116
7327
  return { undone: false, message: "no apply found for library" };
7117
7328
  }
7118
- const raw = await import_node_fs21.promises.readFile(logPath, "utf8");
7329
+ const raw = await import_node_fs22.promises.readFile(logPath, "utf8");
7119
7330
  const entries = raw.trim().split("\n").filter(Boolean).map((line) => JSON.parse(line));
7120
7331
  const match = [...entries].reverse().find((e) => e.project === ctx.project && e.library === args.library);
7121
7332
  if (!match) {
7122
7333
  return { undone: false, message: "no apply found for library" };
7123
7334
  }
7124
- const pkgPath = import_node_path39.default.join(ctx.scanPath, "package.json");
7335
+ const pkgPath = import_node_path40.default.join(ctx.scanPath, "package.json");
7125
7336
  if (await fileExists2(pkgPath)) {
7126
7337
  const pkg = await readPackageJson(ctx.scanPath);
7127
7338
  if (pkg.dependencies?.[match.instrumentation_package]) {
7128
7339
  const { [match.instrumentation_package]: _removed, ...rest } = pkg.dependencies;
7129
7340
  pkg.dependencies = rest;
7130
- await import_node_fs21.promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
7341
+ await import_node_fs22.promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
7131
7342
  }
7132
7343
  }
7133
7344
  const hookFiles = await findHookFiles(ctx.scanPath);
7134
7345
  for (const file of hookFiles) {
7135
- const filePath = import_node_path39.default.join(ctx.scanPath, file);
7136
- const content = await import_node_fs21.promises.readFile(filePath, "utf8");
7346
+ const filePath = import_node_path40.default.join(ctx.scanPath, file);
7347
+ const content = await import_node_fs22.promises.readFile(filePath, "utf8");
7137
7348
  if (content.includes(match.registration_snippet)) {
7138
7349
  const filtered = content.split("\n").filter((line) => !line.includes(match.registration_snippet)).join("\n");
7139
- await import_node_fs21.promises.writeFile(filePath, filtered, "utf8");
7350
+ await import_node_fs22.promises.writeFile(filePath, filtered, "utf8");
7140
7351
  break;
7141
7352
  }
7142
7353
  }
@@ -7148,7 +7359,7 @@ async function rollbackExtension(ctx, args) {
7148
7359
 
7149
7360
  // src/divergences.ts
7150
7361
  init_cjs_shims();
7151
- var import_types26 = require("@neat.is/types");
7362
+ var import_types27 = require("@neat.is/types");
7152
7363
  function bucketKey(source, target, type) {
7153
7364
  return `${type}|${source}|${target}`;
7154
7365
  }
@@ -7156,22 +7367,22 @@ function bucketEdges(graph) {
7156
7367
  const buckets = /* @__PURE__ */ new Map();
7157
7368
  graph.forEachEdge((id, attrs) => {
7158
7369
  const e = attrs;
7159
- const parsed = (0, import_types26.parseEdgeId)(id);
7370
+ const parsed = (0, import_types27.parseEdgeId)(id);
7160
7371
  const provenance = parsed?.provenance ?? e.provenance;
7161
7372
  const key = bucketKey(e.source, e.target, e.type);
7162
7373
  const cur = buckets.get(key) ?? { source: e.source, target: e.target, type: e.type };
7163
7374
  switch (provenance) {
7164
- case import_types26.Provenance.EXTRACTED:
7375
+ case import_types27.Provenance.EXTRACTED:
7165
7376
  cur.extracted = e;
7166
7377
  break;
7167
- case import_types26.Provenance.OBSERVED:
7378
+ case import_types27.Provenance.OBSERVED:
7168
7379
  cur.observed = e;
7169
7380
  break;
7170
- case import_types26.Provenance.INFERRED:
7381
+ case import_types27.Provenance.INFERRED:
7171
7382
  cur.inferred = e;
7172
7383
  break;
7173
7384
  default:
7174
- if (e.provenance === import_types26.Provenance.STALE) cur.stale = e;
7385
+ if (e.provenance === import_types27.Provenance.STALE) cur.stale = e;
7175
7386
  }
7176
7387
  buckets.set(key, cur);
7177
7388
  });
@@ -7180,7 +7391,12 @@ function bucketEdges(graph) {
7180
7391
  function nodeIsFrontier(graph, nodeId) {
7181
7392
  if (!graph.hasNode(nodeId)) return false;
7182
7393
  const attrs = graph.getNodeAttributes(nodeId);
7183
- return attrs.type === import_types26.NodeType.FrontierNode;
7394
+ return attrs.type === import_types27.NodeType.FrontierNode;
7395
+ }
7396
+ function nodeIsWebsocketChannel(graph, nodeId) {
7397
+ if (!graph.hasNode(nodeId)) return false;
7398
+ const attrs = graph.getNodeAttributes(nodeId);
7399
+ return attrs.type === import_types27.NodeType.WebSocketChannelNode;
7184
7400
  }
7185
7401
  function clampConfidence(n) {
7186
7402
  if (!Number.isFinite(n)) return 0;
@@ -7200,14 +7416,14 @@ function gradedConfidence(edge) {
7200
7416
  return clampConfidence(confidenceForEdge(edge));
7201
7417
  }
7202
7418
  var OBSERVABLE_EDGE_TYPES = /* @__PURE__ */ new Set([
7203
- import_types26.EdgeType.CALLS,
7204
- import_types26.EdgeType.CONNECTS_TO,
7205
- import_types26.EdgeType.PUBLISHES_TO,
7206
- import_types26.EdgeType.CONSUMES_FROM
7419
+ import_types27.EdgeType.CALLS,
7420
+ import_types27.EdgeType.CONNECTS_TO,
7421
+ import_types27.EdgeType.PUBLISHES_TO,
7422
+ import_types27.EdgeType.CONSUMES_FROM
7207
7423
  ]);
7208
7424
  function detectMissingDivergences(graph, bucket) {
7209
7425
  const out = [];
7210
- if (bucket.type === import_types26.EdgeType.CONTAINS) return out;
7426
+ if (bucket.type === import_types27.EdgeType.CONTAINS) return out;
7211
7427
  if (bucket.extracted && !bucket.observed && OBSERVABLE_EDGE_TYPES.has(bucket.type)) {
7212
7428
  if (!nodeIsFrontier(graph, bucket.target)) {
7213
7429
  out.push({
@@ -7222,7 +7438,7 @@ function detectMissingDivergences(graph, bucket) {
7222
7438
  });
7223
7439
  }
7224
7440
  }
7225
- if (bucket.observed && !bucket.extracted) {
7441
+ if (bucket.observed && !bucket.extracted && !nodeIsWebsocketChannel(graph, bucket.target)) {
7226
7442
  out.push({
7227
7443
  type: "missing-extracted",
7228
7444
  source: bucket.source,
@@ -7248,7 +7464,7 @@ function declaredHostFor(svc) {
7248
7464
  function hasExtractedConfiguredBy(graph, svcId) {
7249
7465
  for (const edgeId of graph.outboundEdges(svcId)) {
7250
7466
  const e = graph.getEdgeAttributes(edgeId);
7251
- if (e.type === import_types26.EdgeType.CONFIGURED_BY && e.provenance === import_types26.Provenance.EXTRACTED) {
7467
+ if (e.type === import_types27.EdgeType.CONFIGURED_BY && e.provenance === import_types27.Provenance.EXTRACTED) {
7252
7468
  return true;
7253
7469
  }
7254
7470
  }
@@ -7261,10 +7477,10 @@ function detectHostMismatch(graph, svcId, svc) {
7261
7477
  const out = [];
7262
7478
  for (const edgeId of graph.outboundEdges(svcId)) {
7263
7479
  const edge = graph.getEdgeAttributes(edgeId);
7264
- if (edge.type !== import_types26.EdgeType.CONNECTS_TO) continue;
7265
- if (edge.provenance !== import_types26.Provenance.OBSERVED) continue;
7480
+ if (edge.type !== import_types27.EdgeType.CONNECTS_TO) continue;
7481
+ if (edge.provenance !== import_types27.Provenance.OBSERVED) continue;
7266
7482
  const target = graph.getNodeAttributes(edge.target);
7267
- if (target.type !== import_types26.NodeType.DatabaseNode) continue;
7483
+ if (target.type !== import_types27.NodeType.DatabaseNode) continue;
7268
7484
  const observedHost = target.host?.trim();
7269
7485
  if (!observedHost) continue;
7270
7486
  if (observedHost === declaredHost) continue;
@@ -7286,10 +7502,10 @@ function detectCompatDivergences(graph, svcId, svc) {
7286
7502
  const deps = svc.dependencies ?? {};
7287
7503
  for (const edgeId of graph.outboundEdges(svcId)) {
7288
7504
  const edge = graph.getEdgeAttributes(edgeId);
7289
- if (edge.type !== import_types26.EdgeType.CONNECTS_TO) continue;
7290
- if (edge.provenance !== import_types26.Provenance.OBSERVED) continue;
7505
+ if (edge.type !== import_types27.EdgeType.CONNECTS_TO) continue;
7506
+ if (edge.provenance !== import_types27.Provenance.OBSERVED) continue;
7291
7507
  const target = graph.getNodeAttributes(edge.target);
7292
- if (target.type !== import_types26.NodeType.DatabaseNode) continue;
7508
+ if (target.type !== import_types27.NodeType.DatabaseNode) continue;
7293
7509
  for (const pair of compatPairs()) {
7294
7510
  if (pair.engine !== target.engine) continue;
7295
7511
  const declared = deps[pair.driver];
@@ -7348,7 +7564,7 @@ function suppressHostMismatchHalves(all) {
7348
7564
  for (const d of all) {
7349
7565
  if (d.type !== "host-mismatch") continue;
7350
7566
  observedHalf.add(`${d.source}->${d.target}`);
7351
- declaredHalf.add((0, import_types26.databaseId)(d.extractedHost));
7567
+ declaredHalf.add((0, import_types27.databaseId)(d.extractedHost));
7352
7568
  }
7353
7569
  if (observedHalf.size === 0) return all;
7354
7570
  return all.filter((d) => {
@@ -7367,7 +7583,7 @@ function computeDivergences(graph, opts = {}) {
7367
7583
  }
7368
7584
  graph.forEachNode((nodeId, attrs) => {
7369
7585
  const n = attrs;
7370
- if (n.type !== import_types26.NodeType.ServiceNode) return;
7586
+ if (n.type !== import_types27.NodeType.ServiceNode) return;
7371
7587
  const svc = n;
7372
7588
  for (const d of detectHostMismatch(graph, nodeId, svc)) all.push(d);
7373
7589
  for (const d of detectCompatDivergences(graph, nodeId, svc)) all.push(d);
@@ -7401,7 +7617,7 @@ function computeDivergences(graph, opts = {}) {
7401
7617
  if (a.source !== b.source) return a.source.localeCompare(b.source);
7402
7618
  return a.target.localeCompare(b.target);
7403
7619
  });
7404
- return import_types26.DivergenceResultSchema.parse({
7620
+ return import_types27.DivergenceResultSchema.parse({
7405
7621
  divergences: filtered,
7406
7622
  totalAffected: filtered.length,
7407
7623
  computedAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -7410,7 +7626,7 @@ function computeDivergences(graph, opts = {}) {
7410
7626
 
7411
7627
  // src/diff.ts
7412
7628
  init_cjs_shims();
7413
- var import_node_fs22 = require("fs");
7629
+ var import_node_fs23 = require("fs");
7414
7630
  async function loadSnapshotForDiff(target) {
7415
7631
  if (/^https?:\/\//i.test(target)) {
7416
7632
  const res = await fetch(target);
@@ -7419,7 +7635,7 @@ async function loadSnapshotForDiff(target) {
7419
7635
  }
7420
7636
  return await res.json();
7421
7637
  }
7422
- const raw = await import_node_fs22.promises.readFile(target, "utf8");
7638
+ const raw = await import_node_fs23.promises.readFile(target, "utf8");
7423
7639
  return JSON.parse(raw);
7424
7640
  }
7425
7641
  function indexEntries(entries) {
@@ -7487,25 +7703,25 @@ function canonicalJson(value) {
7487
7703
 
7488
7704
  // src/registry.ts
7489
7705
  init_cjs_shims();
7490
- var import_node_fs23 = require("fs");
7706
+ var import_node_fs24 = require("fs");
7491
7707
  var import_node_os3 = __toESM(require("os"), 1);
7492
- var import_node_path40 = __toESM(require("path"), 1);
7493
- var import_types27 = require("@neat.is/types");
7708
+ var import_node_path41 = __toESM(require("path"), 1);
7709
+ var import_types28 = require("@neat.is/types");
7494
7710
  var LOCK_TIMEOUT_MS = 5e3;
7495
7711
  var LOCK_RETRY_MS = 50;
7496
7712
  function neatHome() {
7497
7713
  const override = process.env.NEAT_HOME;
7498
- if (override && override.length > 0) return import_node_path40.default.resolve(override);
7499
- return import_node_path40.default.join(import_node_os3.default.homedir(), ".neat");
7714
+ if (override && override.length > 0) return import_node_path41.default.resolve(override);
7715
+ return import_node_path41.default.join(import_node_os3.default.homedir(), ".neat");
7500
7716
  }
7501
7717
  function registryPath() {
7502
- return import_node_path40.default.join(neatHome(), "projects.json");
7718
+ return import_node_path41.default.join(neatHome(), "projects.json");
7503
7719
  }
7504
7720
  function registryLockPath() {
7505
- return import_node_path40.default.join(neatHome(), "projects.json.lock");
7721
+ return import_node_path41.default.join(neatHome(), "projects.json.lock");
7506
7722
  }
7507
7723
  function daemonPidPath() {
7508
- return import_node_path40.default.join(neatHome(), "neatd.pid");
7724
+ return import_node_path41.default.join(neatHome(), "neatd.pid");
7509
7725
  }
7510
7726
  function isPidAliveDefault(pid) {
7511
7727
  try {
@@ -7517,7 +7733,7 @@ function isPidAliveDefault(pid) {
7517
7733
  }
7518
7734
  async function readPidFile(file) {
7519
7735
  try {
7520
- const raw = await import_node_fs23.promises.readFile(file, "utf8");
7736
+ const raw = await import_node_fs24.promises.readFile(file, "utf8");
7521
7737
  const pid = Number.parseInt(raw.trim(), 10);
7522
7738
  return Number.isInteger(pid) && pid > 0 ? pid : void 0;
7523
7739
  } catch {
@@ -7565,24 +7781,24 @@ function lockHolderMessage(holder, lockPath, timeoutMs) {
7565
7781
  }
7566
7782
  }
7567
7783
  async function writeAtomically(target, contents) {
7568
- await import_node_fs23.promises.mkdir(import_node_path40.default.dirname(target), { recursive: true });
7784
+ await import_node_fs24.promises.mkdir(import_node_path41.default.dirname(target), { recursive: true });
7569
7785
  const tmp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
7570
- const fd = await import_node_fs23.promises.open(tmp, "w");
7786
+ const fd = await import_node_fs24.promises.open(tmp, "w");
7571
7787
  try {
7572
7788
  await fd.writeFile(contents, "utf8");
7573
7789
  await fd.sync();
7574
7790
  } finally {
7575
7791
  await fd.close();
7576
7792
  }
7577
- await import_node_fs23.promises.rename(tmp, target);
7793
+ await import_node_fs24.promises.rename(tmp, target);
7578
7794
  }
7579
7795
  async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaultLockHolderProbe) {
7580
7796
  const deadline = Date.now() + timeoutMs;
7581
- await import_node_fs23.promises.mkdir(import_node_path40.default.dirname(lockPath), { recursive: true });
7797
+ await import_node_fs24.promises.mkdir(import_node_path41.default.dirname(lockPath), { recursive: true });
7582
7798
  let probedHolder = false;
7583
7799
  while (true) {
7584
7800
  try {
7585
- const fd = await import_node_fs23.promises.open(lockPath, "wx");
7801
+ const fd = await import_node_fs24.promises.open(lockPath, "wx");
7586
7802
  try {
7587
7803
  await fd.writeFile(`${process.pid}
7588
7804
  `, "utf8");
@@ -7607,7 +7823,7 @@ async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaul
7607
7823
  }
7608
7824
  }
7609
7825
  async function releaseLock(lockPath) {
7610
- await import_node_fs23.promises.unlink(lockPath).catch(() => {
7826
+ await import_node_fs24.promises.unlink(lockPath).catch(() => {
7611
7827
  });
7612
7828
  }
7613
7829
  async function withLock(fn) {
@@ -7623,7 +7839,7 @@ async function readRegistry() {
7623
7839
  const file = registryPath();
7624
7840
  let raw;
7625
7841
  try {
7626
- raw = await import_node_fs23.promises.readFile(file, "utf8");
7842
+ raw = await import_node_fs24.promises.readFile(file, "utf8");
7627
7843
  } catch (err) {
7628
7844
  if (err.code === "ENOENT") {
7629
7845
  return { version: 1, projects: [] };
@@ -7631,10 +7847,10 @@ async function readRegistry() {
7631
7847
  throw err;
7632
7848
  }
7633
7849
  const parsed = JSON.parse(raw);
7634
- return import_types27.RegistryFileSchema.parse(parsed);
7850
+ return import_types28.RegistryFileSchema.parse(parsed);
7635
7851
  }
7636
7852
  async function writeRegistry(reg) {
7637
- const validated = import_types27.RegistryFileSchema.parse(reg);
7853
+ const validated = import_types28.RegistryFileSchema.parse(reg);
7638
7854
  await writeAtomically(registryPath(), JSON.stringify(validated, null, 2) + "\n");
7639
7855
  }
7640
7856
  async function getProject(name) {
@@ -7678,7 +7894,7 @@ function pruneTtlMs() {
7678
7894
  }
7679
7895
  async function statPathStatus(p) {
7680
7896
  try {
7681
- const stat = await import_node_fs23.promises.stat(p);
7897
+ const stat = await import_node_fs24.promises.stat(p);
7682
7898
  return stat.isDirectory() ? "present" : "unknown";
7683
7899
  } catch (err) {
7684
7900
  return err.code === "ENOENT" ? "gone" : "unknown";
@@ -7925,11 +8141,11 @@ function registerRoutes(scope, ctx) {
7925
8141
  const candidates = req2.query.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
7926
8142
  const parsed = [];
7927
8143
  for (const c of candidates) {
7928
- const r = import_types28.DivergenceTypeSchema.safeParse(c);
8144
+ const r = import_types29.DivergenceTypeSchema.safeParse(c);
7929
8145
  if (!r.success) {
7930
8146
  return reply.code(400).send({
7931
8147
  error: `unknown divergence type "${c}"`,
7932
- allowed: import_types28.DivergenceTypeSchema.options
8148
+ allowed: import_types29.DivergenceTypeSchema.options
7933
8149
  });
7934
8150
  }
7935
8151
  parsed.push(r.data);
@@ -8148,7 +8364,7 @@ function registerRoutes(scope, ctx) {
8148
8364
  const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
8149
8365
  let violations = await log.readAll();
8150
8366
  if (req2.query.severity) {
8151
- const sev = import_types28.PolicySeveritySchema.safeParse(req2.query.severity);
8367
+ const sev = import_types29.PolicySeveritySchema.safeParse(req2.query.severity);
8152
8368
  if (!sev.success) {
8153
8369
  return reply.code(400).send({
8154
8370
  error: "invalid severity",
@@ -8187,7 +8403,7 @@ function registerRoutes(scope, ctx) {
8187
8403
  scope.post("/policies/check", async (req2, reply) => {
8188
8404
  const proj = resolveProject(registry, req2, reply, ctx.bootstrap, ctx.singleProject);
8189
8405
  if (!proj) return;
8190
- const parsed = import_types28.PoliciesCheckBodySchema.safeParse(req2.body ?? {});
8406
+ const parsed = import_types29.PoliciesCheckBodySchema.safeParse(req2.body ?? {});
8191
8407
  if (!parsed.success) {
8192
8408
  return reply.code(400).send({
8193
8409
  error: "invalid /policies/check body",
@@ -8445,12 +8661,91 @@ async function buildApi(opts) {
8445
8661
 
8446
8662
  // src/daemon.ts
8447
8663
  init_otel();
8664
+
8665
+ // src/connectors/index.ts
8666
+ init_cjs_shims();
8667
+ var NO_ENV = "unknown";
8668
+ async function runConnectorPoll(connector, ctx, graph, resolveTarget) {
8669
+ const signals = await connector.poll(ctx);
8670
+ let edgesCreated = 0;
8671
+ let edgesUpdated = 0;
8672
+ let unresolved = 0;
8673
+ for (const signal of signals) {
8674
+ const resolved = resolveTarget(signal, ctx);
8675
+ if (!resolved) {
8676
+ unresolved++;
8677
+ continue;
8678
+ }
8679
+ const serviceNodeId = ensureServiceNode(graph, resolved.serviceName, NO_ENV);
8680
+ const callSite = signal.callSite ? { relPath: signal.callSite.file, line: signal.callSite.line } : void 0;
8681
+ const sourceId = callSite ? ensureObservedFileNode(graph, resolved.serviceName, serviceNodeId, callSite) : serviceNodeId;
8682
+ const evidence = callSite ? {
8683
+ file: reconcileObservedRelPath(graph, resolved.serviceName, callSite.relPath),
8684
+ line: callSite.line
8685
+ } : void 0;
8686
+ const calls = Math.trunc(signal.callCount);
8687
+ if (calls < 1) continue;
8688
+ const errors = Math.min(Math.max(Math.trunc(signal.errorCount), 0), calls);
8689
+ let created = false;
8690
+ let ok = true;
8691
+ for (let i = 0; i < calls; i++) {
8692
+ const result = upsertObservedEdge(
8693
+ graph,
8694
+ resolved.edgeType,
8695
+ sourceId,
8696
+ resolved.targetNodeId,
8697
+ signal.lastObservedIso,
8698
+ i < errors,
8699
+ evidence
8700
+ );
8701
+ if (!result) {
8702
+ ok = false;
8703
+ break;
8704
+ }
8705
+ if (i === 0) created = result.created;
8706
+ }
8707
+ if (!ok) {
8708
+ unresolved++;
8709
+ continue;
8710
+ }
8711
+ if (created) edgesCreated++;
8712
+ else edgesUpdated++;
8713
+ }
8714
+ return { signalCount: signals.length, edgesCreated, edgesUpdated, unresolved };
8715
+ }
8716
+ var DEFAULT_POLL_INTERVAL_MS = 6e4;
8717
+ function startConnectorPollLoop(connector, ctx, graph, resolveTarget, options = {}) {
8718
+ let stopped = false;
8719
+ let since = ctx.since;
8720
+ const intervalMs = options.intervalMs ?? DEFAULT_POLL_INTERVAL_MS;
8721
+ const onError = options.onError ?? ((err) => console.error(`[neatd] connector poll failed (${connector.provider})`, err));
8722
+ const tick = () => {
8723
+ if (stopped) return;
8724
+ void (async () => {
8725
+ const tickStartedAt = (/* @__PURE__ */ new Date()).toISOString();
8726
+ try {
8727
+ await runConnectorPoll(connector, { ...ctx, since }, graph, resolveTarget);
8728
+ since = tickStartedAt;
8729
+ } catch (err) {
8730
+ onError(err);
8731
+ }
8732
+ })();
8733
+ };
8734
+ const interval = setInterval(tick, intervalMs);
8735
+ if (typeof interval.unref === "function") interval.unref();
8736
+ return () => {
8737
+ stopped = true;
8738
+ clearInterval(interval);
8739
+ };
8740
+ }
8741
+
8742
+ // src/daemon.ts
8448
8743
  init_auth();
8449
8744
 
8450
8745
  // src/unrouted.ts
8451
8746
  init_cjs_shims();
8452
- var import_node_fs24 = require("fs");
8453
- var import_node_path43 = __toESM(require("path"), 1);
8747
+ var import_node_fs25 = require("fs");
8748
+ var import_node_path44 = __toESM(require("path"), 1);
8454
8749
  function buildUnroutedSpanRecord(serviceName, traceId, now = /* @__PURE__ */ new Date()) {
8455
8750
  return {
8456
8751
  timestamp: now.toISOString(),
@@ -8460,34 +8755,34 @@ function buildUnroutedSpanRecord(serviceName, traceId, now = /* @__PURE__ */ new
8460
8755
  };
8461
8756
  }
8462
8757
  async function appendUnroutedSpan(neatHome3, record) {
8463
- const target = import_node_path43.default.join(neatHome3, "errors.ndjson");
8464
- await import_node_fs24.promises.mkdir(neatHome3, { recursive: true });
8465
- await import_node_fs24.promises.appendFile(target, JSON.stringify(record) + "\n", "utf8");
8758
+ const target = import_node_path44.default.join(neatHome3, "errors.ndjson");
8759
+ await import_node_fs25.promises.mkdir(neatHome3, { recursive: true });
8760
+ await import_node_fs25.promises.appendFile(target, JSON.stringify(record) + "\n", "utf8");
8466
8761
  }
8467
8762
  function unroutedErrorsPath(neatHome3) {
8468
- return import_node_path43.default.join(neatHome3, "errors.ndjson");
8763
+ return import_node_path44.default.join(neatHome3, "errors.ndjson");
8469
8764
  }
8470
8765
 
8471
8766
  // src/daemon.ts
8472
- var import_types29 = require("@neat.is/types");
8767
+ var import_types30 = require("@neat.is/types");
8473
8768
  function daemonJsonPath(scanPath) {
8474
- return import_node_path44.default.join(scanPath, "neat-out", "daemon.json");
8769
+ return import_node_path45.default.join(scanPath, "neat-out", "daemon.json");
8475
8770
  }
8476
8771
  function daemonsDiscoveryDir(home) {
8477
8772
  const base = home && home.length > 0 ? home : neatHomeFromEnv();
8478
- return import_node_path44.default.join(base, "daemons");
8773
+ return import_node_path45.default.join(base, "daemons");
8479
8774
  }
8480
8775
  function daemonDiscoveryPath(project, home) {
8481
- return import_node_path44.default.join(daemonsDiscoveryDir(home), `${sanitizeDiscoveryName(project)}.json`);
8776
+ return import_node_path45.default.join(daemonsDiscoveryDir(home), `${sanitizeDiscoveryName(project)}.json`);
8482
8777
  }
8483
8778
  function sanitizeDiscoveryName(project) {
8484
8779
  return project.replace(/[^A-Za-z0-9._-]/g, "_");
8485
8780
  }
8486
8781
  function neatHomeFromEnv() {
8487
8782
  const env = process.env.NEAT_HOME;
8488
- if (env && env.length > 0) return import_node_path44.default.resolve(env);
8783
+ if (env && env.length > 0) return import_node_path45.default.resolve(env);
8489
8784
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
8490
- return import_node_path44.default.join(home, ".neat");
8785
+ return import_node_path45.default.join(home, ".neat");
8491
8786
  }
8492
8787
  function resolveNeatVersion() {
8493
8788
  if (process.env.NEAT_LOCAL_VERSION && process.env.NEAT_LOCAL_VERSION.length > 0) {
@@ -8519,7 +8814,7 @@ async function clearDaemonRecord(record, home) {
8519
8814
  } catch {
8520
8815
  }
8521
8816
  try {
8522
- await import_node_fs25.promises.unlink(daemonDiscoveryPath(record.project, home));
8817
+ await import_node_fs26.promises.unlink(daemonDiscoveryPath(record.project, home));
8523
8818
  } catch {
8524
8819
  }
8525
8820
  }
@@ -8528,12 +8823,12 @@ function reconcileDaemonRecordSync(record, home) {
8528
8823
  const stopped = { ...record, status: "stopped" };
8529
8824
  const target = daemonJsonPath(record.projectPath);
8530
8825
  const tmp = `${target}.${process.pid}.tmp`;
8531
- (0, import_node_fs25.writeFileSync)(tmp, JSON.stringify(stopped, null, 2) + "\n");
8532
- (0, import_node_fs25.renameSync)(tmp, target);
8826
+ (0, import_node_fs26.writeFileSync)(tmp, JSON.stringify(stopped, null, 2) + "\n");
8827
+ (0, import_node_fs26.renameSync)(tmp, target);
8533
8828
  } catch {
8534
8829
  }
8535
8830
  try {
8536
- (0, import_node_fs25.unlinkSync)(daemonDiscoveryPath(record.project, home));
8831
+ (0, import_node_fs26.unlinkSync)(daemonDiscoveryPath(record.project, home));
8537
8832
  } catch {
8538
8833
  }
8539
8834
  }
@@ -8546,17 +8841,21 @@ function teardownSlot(slot) {
8546
8841
  slot.stopStaleness();
8547
8842
  } catch {
8548
8843
  }
8844
+ try {
8845
+ slot.stopConnectors();
8846
+ } catch {
8847
+ }
8549
8848
  try {
8550
8849
  slot.detachEvents();
8551
8850
  } catch {
8552
8851
  }
8553
8852
  }
8554
8853
  function neatHomeFor(opts) {
8555
- if (opts.neatHome && opts.neatHome.length > 0) return import_node_path44.default.resolve(opts.neatHome);
8854
+ if (opts.neatHome && opts.neatHome.length > 0) return import_node_path45.default.resolve(opts.neatHome);
8556
8855
  const env = process.env.NEAT_HOME;
8557
- if (env && env.length > 0) return import_node_path44.default.resolve(env);
8856
+ if (env && env.length > 0) return import_node_path45.default.resolve(env);
8558
8857
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
8559
- return import_node_path44.default.join(home, ".neat");
8858
+ return import_node_path45.default.join(home, ".neat");
8560
8859
  }
8561
8860
  function routeSpanToProject(serviceName, projects) {
8562
8861
  if (!serviceName) return DEFAULT_PROJECT;
@@ -8600,13 +8899,13 @@ function spanBelongsToSingleProject(graph, project, serviceName) {
8600
8899
  if (!serviceName) return true;
8601
8900
  if (serviceNameMatchesProject(serviceName, project)) return true;
8602
8901
  return graph.someNode(
8603
- (_id, attrs) => attrs.type === import_types29.NodeType.ServiceNode && attrs.name === serviceName
8902
+ (_id, attrs) => attrs.type === import_types30.NodeType.ServiceNode && attrs.name === serviceName
8604
8903
  );
8605
8904
  }
8606
- async function bootstrapProject(entry2) {
8607
- const paths = pathsForProject(entry2.name, import_node_path44.default.join(entry2.path, "neat-out"));
8905
+ async function bootstrapProject(entry2, connectors = []) {
8906
+ const paths = pathsForProject(entry2.name, import_node_path45.default.join(entry2.path, "neat-out"));
8608
8907
  try {
8609
- const stat = await import_node_fs25.promises.stat(entry2.path);
8908
+ const stat = await import_node_fs26.promises.stat(entry2.path);
8610
8909
  if (!stat.isDirectory()) {
8611
8910
  throw new Error(`registered path ${entry2.path} is not a directory`);
8612
8911
  }
@@ -8624,6 +8923,8 @@ async function bootstrapProject(entry2) {
8624
8923
  },
8625
8924
  stopStaleness: () => {
8626
8925
  },
8926
+ stopConnectors: () => {
8927
+ },
8627
8928
  detachEvents: () => {
8628
8929
  },
8629
8930
  status: "broken",
@@ -8642,6 +8943,18 @@ async function bootstrapProject(entry2) {
8642
8943
  staleEventsPath: paths.staleEventsPath,
8643
8944
  project: entry2.name
8644
8945
  });
8946
+ const stopFns = connectors.map(
8947
+ (registration) => startConnectorPollLoop(
8948
+ registration.connector,
8949
+ { projectDir: entry2.path, credentials: registration.credentials },
8950
+ graph,
8951
+ registration.resolveTarget,
8952
+ { intervalMs: registration.intervalMs }
8953
+ )
8954
+ );
8955
+ const stopConnectors = () => {
8956
+ for (const stop of stopFns) stop();
8957
+ };
8645
8958
  await touchLastSeen(entry2.name).catch(() => {
8646
8959
  });
8647
8960
  return {
@@ -8651,6 +8964,7 @@ async function bootstrapProject(entry2) {
8651
8964
  paths,
8652
8965
  stopPersist,
8653
8966
  stopStaleness,
8967
+ stopConnectors,
8654
8968
  detachEvents,
8655
8969
  status: "active"
8656
8970
  };
@@ -8707,7 +9021,7 @@ async function startDaemon(opts = {}) {
8707
9021
  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;
8708
9022
  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;
8709
9023
  const singleProject = projectArg;
8710
- const singleProjectPath = singleProject && projectPathArg ? import_node_path44.default.resolve(projectPathArg) : null;
9024
+ const singleProjectPath = singleProject && projectPathArg ? import_node_path45.default.resolve(projectPathArg) : null;
8711
9025
  if (singleProject && !singleProjectPath) {
8712
9026
  throw new Error(
8713
9027
  `neatd: project "${singleProject}" given without a projectPath; pass NEAT_PROJECT_PATH alongside NEAT_PROJECT.`
@@ -8715,14 +9029,14 @@ async function startDaemon(opts = {}) {
8715
9029
  }
8716
9030
  if (!singleProject) {
8717
9031
  try {
8718
- await import_node_fs25.promises.access(regPath);
9032
+ await import_node_fs26.promises.access(regPath);
8719
9033
  } catch {
8720
9034
  throw new Error(
8721
9035
  `neatd: registry not found at ${regPath}. Run \`neat init <path>\` to register a project before starting the daemon.`
8722
9036
  );
8723
9037
  }
8724
9038
  }
8725
- const pidPath = import_node_path44.default.join(home, "neatd.pid");
9039
+ const pidPath = import_node_path45.default.join(home, "neatd.pid");
8726
9040
  await writeAtomically(pidPath, `${process.pid}
8727
9041
  `);
8728
9042
  const slots = /* @__PURE__ */ new Map();
@@ -8766,7 +9080,7 @@ async function startDaemon(opts = {}) {
8766
9080
  }
8767
9081
  async function tryRecoverSlot(entry2) {
8768
9082
  try {
8769
- const fresh = await bootstrapProject(entry2);
9083
+ const fresh = await bootstrapProject(entry2, opts.connectors ?? []);
8770
9084
  const prior = slots.get(entry2.name);
8771
9085
  if (prior) teardownSlot(prior);
8772
9086
  slots.set(entry2.name, fresh);
@@ -8790,7 +9104,7 @@ async function startDaemon(opts = {}) {
8790
9104
  bootstrapStatus.set(entry2.name, "bootstrapping");
8791
9105
  bootstrapStartedAt.set(entry2.name, Date.now());
8792
9106
  try {
8793
- const slot = await bootstrapProject(entry2);
9107
+ const slot = await bootstrapProject(entry2, opts.connectors ?? []);
8794
9108
  const prior = slots.get(entry2.name);
8795
9109
  if (prior) teardownSlot(prior);
8796
9110
  slots.set(entry2.name, slot);
@@ -8916,7 +9230,7 @@ async function startDaemon(opts = {}) {
8916
9230
  }
8917
9231
  if (restApp) await restApp.close().catch(() => {
8918
9232
  });
8919
- await import_node_fs25.promises.unlink(pidPath).catch(() => {
9233
+ await import_node_fs26.promises.unlink(pidPath).catch(() => {
8920
9234
  });
8921
9235
  throw new Error(
8922
9236
  `neatd: failed to bind REST on port ${restPort} \u2014 ${err.message}`
@@ -9042,7 +9356,7 @@ async function startDaemon(opts = {}) {
9042
9356
  });
9043
9357
  if (otlpApp) await otlpApp.close().catch(() => {
9044
9358
  });
9045
- await import_node_fs25.promises.unlink(pidPath).catch(() => {
9359
+ await import_node_fs26.promises.unlink(pidPath).catch(() => {
9046
9360
  });
9047
9361
  throw new Error(
9048
9362
  `neatd: failed to bind OTLP on port ${otlpPort} \u2014 ${err.message}`
@@ -9077,7 +9391,7 @@ async function startDaemon(opts = {}) {
9077
9391
  });
9078
9392
  if (otlpApp) await otlpApp.close().catch(() => {
9079
9393
  });
9080
- await import_node_fs25.promises.unlink(pidPath).catch(() => {
9394
+ await import_node_fs26.promises.unlink(pidPath).catch(() => {
9081
9395
  });
9082
9396
  throw new Error(
9083
9397
  `neatd: failed to write daemon.json for "${singleProject}" \u2014 ${err.message}`
@@ -9124,9 +9438,9 @@ async function startDaemon(opts = {}) {
9124
9438
  let registryWatcher = null;
9125
9439
  let reloadTimer = null;
9126
9440
  if (!singleProject) try {
9127
- const regDir = import_node_path44.default.dirname(regPath);
9128
- const regBase = import_node_path44.default.basename(regPath);
9129
- registryWatcher = (0, import_node_fs25.watch)(regDir, (_eventType, filename) => {
9441
+ const regDir = import_node_path45.default.dirname(regPath);
9442
+ const regBase = import_node_path45.default.basename(regPath);
9443
+ registryWatcher = (0, import_node_fs26.watch)(regDir, (_eventType, filename) => {
9130
9444
  if (filename !== null && filename !== regBase) return;
9131
9445
  if (reloadTimer) clearTimeout(reloadTimer);
9132
9446
  reloadTimer = setTimeout(() => {
@@ -9175,7 +9489,7 @@ async function startDaemon(opts = {}) {
9175
9489
  if (daemonRecord) {
9176
9490
  await clearDaemonRecord(daemonRecord, home);
9177
9491
  }
9178
- await import_node_fs25.promises.unlink(pidPath).catch(() => {
9492
+ await import_node_fs26.promises.unlink(pidPath).catch(() => {
9179
9493
  });
9180
9494
  };
9181
9495
  return {
@@ -9198,9 +9512,9 @@ init_auth();
9198
9512
  // src/web-spawn.ts
9199
9513
  init_cjs_shims();
9200
9514
  var import_node_child_process2 = require("child_process");
9201
- var import_node_fs26 = require("fs");
9515
+ var import_node_fs27 = require("fs");
9202
9516
  var import_node_net = __toESM(require("net"), 1);
9203
- var import_node_path45 = __toESM(require("path"), 1);
9517
+ var import_node_path46 = __toESM(require("path"), 1);
9204
9518
  var DEFAULT_WEB_PORT = 6328;
9205
9519
  var DEFAULT_REST_PORT = 8080;
9206
9520
  function asValidPort(value) {
@@ -9209,11 +9523,11 @@ function asValidPort(value) {
9209
9523
  }
9210
9524
  function projectRoot() {
9211
9525
  const fromEnv = process.env.NEAT_SCAN_PATH;
9212
- return import_node_path45.default.resolve(fromEnv && fromEnv.length > 0 ? fromEnv : process.cwd());
9526
+ return import_node_path46.default.resolve(fromEnv && fromEnv.length > 0 ? fromEnv : process.cwd());
9213
9527
  }
9214
9528
  async function readDaemonPorts(root) {
9215
9529
  try {
9216
- const raw = await import_node_fs26.promises.readFile(import_node_path45.default.join(root, "neat-out", "daemon.json"), "utf8");
9530
+ const raw = await import_node_fs27.promises.readFile(import_node_path46.default.join(root, "neat-out", "daemon.json"), "utf8");
9217
9531
  const parsed = JSON.parse(raw);
9218
9532
  const ports = parsed?.ports ?? {};
9219
9533
  return { web: asValidPort(ports.web), rest: asValidPort(ports.rest) };
@@ -9258,10 +9572,10 @@ function resolveWebPackageDir() {
9258
9572
  eval("require")
9259
9573
  );
9260
9574
  const pkgJsonPath = req.resolve("@neat.is/web/package.json");
9261
- return import_node_path45.default.dirname(pkgJsonPath);
9575
+ return import_node_path46.default.dirname(pkgJsonPath);
9262
9576
  }
9263
9577
  function resolveStandaloneServerEntry(webDir) {
9264
- return import_node_path45.default.join(webDir, ".next/standalone/packages/web/server.js");
9578
+ return import_node_path46.default.join(webDir, ".next/standalone/packages/web/server.js");
9265
9579
  }
9266
9580
  async function pickInternalPort() {
9267
9581
  return new Promise((resolve, reject) => {
@@ -9314,7 +9628,7 @@ async function spawnWebUI(restPort, opts = {}) {
9314
9628
  NEAT_API_URL: apiUrl
9315
9629
  };
9316
9630
  child = (0, import_node_child_process2.spawn)(process.execPath, [serverEntry], {
9317
- cwd: import_node_path45.default.dirname(serverEntry),
9631
+ cwd: import_node_path46.default.dirname(serverEntry),
9318
9632
  env,
9319
9633
  stdio: ["ignore", "inherit", "inherit"],
9320
9634
  detached: false
@@ -9490,14 +9804,14 @@ function localVersion() {
9490
9804
  }
9491
9805
  function neatHome2() {
9492
9806
  if (process.env.NEAT_HOME && process.env.NEAT_HOME.length > 0) {
9493
- return import_node_path46.default.resolve(process.env.NEAT_HOME);
9807
+ return import_node_path47.default.resolve(process.env.NEAT_HOME);
9494
9808
  }
9495
9809
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
9496
- return import_node_path46.default.join(home, ".neat");
9810
+ return import_node_path47.default.join(home, ".neat");
9497
9811
  }
9498
9812
  async function readPid() {
9499
9813
  try {
9500
- const raw = await import_node_fs27.promises.readFile(import_node_path46.default.join(neatHome2(), "neatd.pid"), "utf8");
9814
+ const raw = await import_node_fs28.promises.readFile(import_node_path47.default.join(neatHome2(), "neatd.pid"), "utf8");
9501
9815
  const n = Number.parseInt(raw.trim(), 10);
9502
9816
  return Number.isFinite(n) ? n : null;
9503
9817
  } catch {