@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/cli.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", (req, reply, done) => {
61
- const path55 = (req.url.split("?")[0] ?? "").replace(/\/+$/, "");
61
+ const path56 = (req.url.split("?")[0] ?? "").replace(/\/+$/, "");
62
62
  for (const suffix of suffixes) {
63
- if (path55 === suffix || path55.endsWith(suffix)) {
63
+ if (path56 === suffix || path56.endsWith(suffix)) {
64
64
  done();
65
65
  return;
66
66
  }
@@ -190,8 +190,8 @@ function reshapeGrpcRequest(req) {
190
190
  };
191
191
  }
192
192
  function resolveProtoRoot() {
193
- const here = import_node_path43.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
194
- return import_node_path43.default.resolve(here, "..", "proto");
193
+ const here = import_node_path44.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
194
+ return import_node_path44.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_url2, import_node_path43, import_node_crypto2, grpc, protoLoader;
262
+ var import_node_url2, import_node_path44, 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_url2 = require("url");
268
- import_node_path43 = __toESM(require("path"), 1);
268
+ import_node_path44 = __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 path56 = q === -1 ? v : v.slice(0, q);
362
+ if (path56.length > 0) return path56;
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_path44.default.dirname((0, import_node_url3.fileURLToPath)(importMetaUrl));
390
- const protoRoot = import_node_path44.default.resolve(here, "..", "proto");
416
+ const here = import_node_path45.default.dirname((0, import_node_url3.fileURLToPath)(importMetaUrl));
417
+ const protoRoot = import_node_path45.default.resolve(here, "..", "proto");
391
418
  const root = new import_protobufjs.default.Root();
392
- root.resolvePath = (_origin, target) => import_node_path44.default.resolve(protoRoot, target);
419
+ root.resolvePath = (_origin, target) => import_node_path45.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_path44, import_node_url3, 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_path45, import_node_url3, 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_path44 = __toESM(require("path"), 1);
652
+ import_node_path45 = __toESM(require("path"), 1);
626
653
  import_node_url3 = require("url");
627
654
  import_fastify2 = __toESM(require("fastify"), 1);
628
655
  import_protobufjs = __toESM(require("protobufjs"), 1);
@@ -659,8 +686,8 @@ __export(cli_exports, {
659
686
  });
660
687
  module.exports = __toCommonJS(cli_exports);
661
688
  init_cjs_shims();
662
- var import_node_path54 = __toESM(require("path"), 1);
663
- var import_node_fs34 = require("fs");
689
+ var import_node_path55 = __toESM(require("path"), 1);
690
+ var import_node_fs35 = require("fs");
664
691
 
665
692
  // src/banner.ts
666
693
  init_cjs_shims();
@@ -1225,19 +1252,19 @@ function confidenceFromMix(edges, now = Date.now()) {
1225
1252
  function longestIncomingWalk(graph, start, maxDepth) {
1226
1253
  let best = { path: [start], edges: [] };
1227
1254
  const visited = /* @__PURE__ */ new Set([start]);
1228
- function step(node, path55, edges) {
1229
- if (path55.length > best.path.length) {
1230
- best = { path: [...path55], edges: [...edges] };
1255
+ function step(node, path56, edges) {
1256
+ if (path56.length > best.path.length) {
1257
+ best = { path: [...path56], edges: [...edges] };
1231
1258
  }
1232
- if (path55.length - 1 >= maxDepth) return;
1259
+ if (path56.length - 1 >= maxDepth) return;
1233
1260
  const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
1234
1261
  for (const [srcId, edge] of incoming) {
1235
1262
  if (visited.has(srcId)) continue;
1236
1263
  visited.add(srcId);
1237
- path55.push(srcId);
1264
+ path56.push(srcId);
1238
1265
  edges.push(edge);
1239
- step(srcId, path55, edges);
1240
- path55.pop();
1266
+ step(srcId, path56, edges);
1267
+ path56.pop();
1241
1268
  edges.pop();
1242
1269
  visited.delete(srcId);
1243
1270
  }
@@ -1431,26 +1458,26 @@ function dominantFailingCall(graph, serviceId4, visited) {
1431
1458
  return best;
1432
1459
  }
1433
1460
  function followFailingCallChain(graph, originServiceId, maxDepth) {
1434
- const path55 = [originServiceId];
1461
+ const path56 = [originServiceId];
1435
1462
  const edges = [];
1436
1463
  const visited = /* @__PURE__ */ new Set([originServiceId]);
1437
1464
  let current = originServiceId;
1438
1465
  for (let depth = 0; depth < maxDepth; depth++) {
1439
1466
  const hop = dominantFailingCall(graph, current, visited);
1440
1467
  if (!hop) break;
1441
- path55.push(hop.nextService);
1468
+ path56.push(hop.nextService);
1442
1469
  edges.push(hop.edge);
1443
1470
  visited.add(hop.nextService);
1444
1471
  current = hop.nextService;
1445
1472
  }
1446
1473
  if (edges.length === 0) return null;
1447
- return { path: path55, edges, culprit: current };
1474
+ return { path: path56, edges, culprit: current };
1448
1475
  }
1449
1476
  function crossServiceRootCause(graph, originId, incidents, errorEvent) {
1450
1477
  const chain = followFailingCallChain(graph, originId, ROOT_CAUSE_MAX_DEPTH);
1451
1478
  if (!chain) return null;
1452
1479
  const culprit = chain.culprit;
1453
- const path55 = [...chain.path];
1480
+ const path56 = [...chain.path];
1454
1481
  const edgeProvenances = chain.edges.map((e) => e.provenance);
1455
1482
  const baseConfidence = confidenceFromMix(chain.edges);
1456
1483
  const confidence = Math.max(0, Math.min(1, baseConfidence * INCIDENT_ROOT_CAUSE_CONFIDENCE));
@@ -1458,14 +1485,14 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
1458
1485
  if (loc) {
1459
1486
  let rootCauseNode = culprit;
1460
1487
  if (loc.fileNode) {
1461
- path55.push(loc.fileNode);
1488
+ path56.push(loc.fileNode);
1462
1489
  edgeProvenances.push(import_types.Provenance.OBSERVED);
1463
1490
  rootCauseNode = loc.fileNode;
1464
1491
  }
1465
1492
  return import_types.RootCauseResultSchema.parse({
1466
1493
  rootCauseNode,
1467
1494
  rootCauseReason: loc.rootCauseReason,
1468
- traversalPath: path55,
1495
+ traversalPath: path56,
1469
1496
  edgeProvenances,
1470
1497
  confidence,
1471
1498
  ...loc.fixRecommendation ? { fixRecommendation: loc.fixRecommendation } : {}
@@ -1477,7 +1504,7 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
1477
1504
  return import_types.RootCauseResultSchema.parse({
1478
1505
  rootCauseNode: culprit,
1479
1506
  rootCauseReason: `${culpritName} is failing downstream calls (${errs} observed error${errs === 1 ? "" : "s"})`,
1480
- traversalPath: path55,
1507
+ traversalPath: path56,
1481
1508
  edgeProvenances,
1482
1509
  confidence,
1483
1510
  fixRecommendation: `Inspect ${culpritName}'s failing handler`
@@ -2427,6 +2454,40 @@ function ensureGraphqlOperationNode(graph, serviceName, operationType, operation
2427
2454
  graph.addNode(id, node);
2428
2455
  return id;
2429
2456
  }
2457
+ function spanServesGrpcMethod(kind) {
2458
+ return kind !== WIRE_SPAN_KIND_CLIENT && kind !== WIRE_SPAN_KIND_PRODUCER && kind !== WIRE_SPAN_KIND_CONSUMER;
2459
+ }
2460
+ function ensureGrpcMethodNode(graph, rpcService, rpcMethod) {
2461
+ const id = (0, import_types3.grpcMethodId)(rpcService, rpcMethod);
2462
+ if (graph.hasNode(id)) return id;
2463
+ const node = {
2464
+ id,
2465
+ type: import_types3.NodeType.GrpcMethodNode,
2466
+ name: `${rpcService}/${rpcMethod}`,
2467
+ rpcService,
2468
+ rpcMethod,
2469
+ discoveredVia: "otel"
2470
+ };
2471
+ graph.addNode(id, node);
2472
+ return id;
2473
+ }
2474
+ function spanServesWebsocketChannel(kind) {
2475
+ return kind !== WIRE_SPAN_KIND_CLIENT && kind !== WIRE_SPAN_KIND_PRODUCER && kind !== WIRE_SPAN_KIND_CONSUMER;
2476
+ }
2477
+ function ensureWebsocketChannelNode(graph, serviceName, channel) {
2478
+ const id = (0, import_types3.websocketChannelId)(serviceName, channel);
2479
+ if (graph.hasNode(id)) return id;
2480
+ const node = {
2481
+ id,
2482
+ type: import_types3.NodeType.WebSocketChannelNode,
2483
+ name: channel,
2484
+ service: serviceName,
2485
+ channel,
2486
+ discoveredVia: "otel"
2487
+ };
2488
+ graph.addNode(id, node);
2489
+ return id;
2490
+ }
2430
2491
  function messagingDestinationKind(system) {
2431
2492
  return `${system}-topic`;
2432
2493
  }
@@ -2862,6 +2923,34 @@ async function handleSpan(ctx, span) {
2862
2923
  callSiteEvidence
2863
2924
  );
2864
2925
  if (result) affectedNode = targetId;
2926
+ } else if (span.rpcSystem === "grpc" && span.rpcService && span.rpcMethod && spanServesGrpcMethod(span.kind)) {
2927
+ const targetId = ensureGrpcMethodNode(ctx.graph, span.rpcService, span.rpcMethod);
2928
+ const result = upsertObservedEdge(
2929
+ ctx.graph,
2930
+ import_types3.EdgeType.CONTAINS,
2931
+ observedSource(),
2932
+ targetId,
2933
+ ts,
2934
+ isError,
2935
+ callSiteEvidence
2936
+ );
2937
+ if (result) affectedNode = targetId;
2938
+ } else if (span.websocketChannel && spanServesWebsocketChannel(span.kind)) {
2939
+ const targetId = ensureWebsocketChannelNode(
2940
+ ctx.graph,
2941
+ span.service,
2942
+ span.websocketChannel
2943
+ );
2944
+ const result = upsertObservedEdge(
2945
+ ctx.graph,
2946
+ import_types3.EdgeType.CONNECTS_TO,
2947
+ observedSource(),
2948
+ targetId,
2949
+ ts,
2950
+ isError,
2951
+ callSiteEvidence
2952
+ );
2953
+ if (result) affectedNode = targetId;
2865
2954
  } else {
2866
2955
  const host = pickAddress(span);
2867
2956
  let resolvedViaAddress = false;
@@ -5444,17 +5533,138 @@ async function addRoutes(graph, services) {
5444
5533
  return { nodesAdded, edgesAdded };
5445
5534
  }
5446
5535
 
5536
+ // src/extract/proto.ts
5537
+ init_cjs_shims();
5538
+ var import_node_fs16 = require("fs");
5539
+ var import_node_path24 = __toESM(require("path"), 1);
5540
+ var import_types12 = require("@neat.is/types");
5541
+ var PROTO_EXTENSION = ".proto";
5542
+ function packageOf(content) {
5543
+ const m = content.match(/^\s*package\s+([A-Za-z_][A-Za-z0-9_.]*)\s*;/m);
5544
+ return m ? m[1] : null;
5545
+ }
5546
+ function lineAt(content, offset) {
5547
+ return content.slice(0, offset).split("\n").length;
5548
+ }
5549
+ function grpcMethodsFromProto(content, fqPackage) {
5550
+ const out = [];
5551
+ const serviceRe = /\bservice\s+([A-Za-z_][A-Za-z0-9_]*)\s*\{/g;
5552
+ let sm;
5553
+ while ((sm = serviceRe.exec(content)) !== null) {
5554
+ const serviceName = sm[1];
5555
+ const rpcService = fqPackage ? `${fqPackage}.${serviceName}` : serviceName;
5556
+ const bodyStart = serviceRe.lastIndex;
5557
+ let depth = 1;
5558
+ let i = bodyStart;
5559
+ for (; i < content.length && depth > 0; i++) {
5560
+ const ch = content[i];
5561
+ if (ch === "{") depth++;
5562
+ else if (ch === "}") depth--;
5563
+ }
5564
+ const body = content.slice(bodyStart, i - 1);
5565
+ const rpcRe = /\brpc\s+([A-Za-z_][A-Za-z0-9_]*)\s*\(/g;
5566
+ let rm;
5567
+ while ((rm = rpcRe.exec(body)) !== null) {
5568
+ const rpcMethod = rm[1];
5569
+ const line = lineAt(content, bodyStart + rm.index);
5570
+ out.push({ rpcService, rpcMethod, line });
5571
+ }
5572
+ serviceRe.lastIndex = i;
5573
+ }
5574
+ return out;
5575
+ }
5576
+ async function walkProtoFiles(dir) {
5577
+ const out = [];
5578
+ async function walk3(current) {
5579
+ const entries = await import_node_fs16.promises.readdir(current, { withFileTypes: true }).catch(() => []);
5580
+ for (const entry2 of entries) {
5581
+ const full = import_node_path24.default.join(current, entry2.name);
5582
+ if (entry2.isDirectory()) {
5583
+ if (IGNORED_DIRS.has(entry2.name)) continue;
5584
+ if (await isPythonVenvDir(full)) continue;
5585
+ await walk3(full);
5586
+ } else if (entry2.isFile() && import_node_path24.default.extname(entry2.name) === PROTO_EXTENSION) {
5587
+ out.push(full);
5588
+ }
5589
+ }
5590
+ }
5591
+ await walk3(dir);
5592
+ return out;
5593
+ }
5594
+ async function addGrpcMethods(graph, services) {
5595
+ let nodesAdded = 0;
5596
+ let edgesAdded = 0;
5597
+ for (const service of services) {
5598
+ const protoPaths = await walkProtoFiles(service.dir);
5599
+ for (const protoPath of protoPaths) {
5600
+ if (isTestPath(protoPath)) continue;
5601
+ const relFile = toPosix2(import_node_path24.default.relative(service.dir, protoPath));
5602
+ let content;
5603
+ try {
5604
+ content = await import_node_fs16.promises.readFile(protoPath, "utf8");
5605
+ } catch (err) {
5606
+ recordExtractionError("proto extraction", protoPath, err);
5607
+ continue;
5608
+ }
5609
+ let methods;
5610
+ try {
5611
+ methods = grpcMethodsFromProto(content, packageOf(content));
5612
+ } catch (err) {
5613
+ recordExtractionError("proto extraction", protoPath, err);
5614
+ continue;
5615
+ }
5616
+ if (methods.length === 0) continue;
5617
+ for (const method of methods) {
5618
+ const mid = (0, import_types12.grpcMethodId)(method.rpcService, method.rpcMethod);
5619
+ if (!graph.hasNode(mid)) {
5620
+ const node = {
5621
+ id: mid,
5622
+ type: import_types12.NodeType.GrpcMethodNode,
5623
+ name: `${method.rpcService}/${method.rpcMethod}`,
5624
+ rpcService: method.rpcService,
5625
+ rpcMethod: method.rpcMethod,
5626
+ path: relFile,
5627
+ line: method.line,
5628
+ discoveredVia: "static"
5629
+ };
5630
+ graph.addNode(mid, node);
5631
+ nodesAdded++;
5632
+ }
5633
+ const containsId = (0, import_types12.extractedEdgeId)(service.node.id, mid, import_types12.EdgeType.CONTAINS);
5634
+ if (!graph.hasEdge(containsId)) {
5635
+ const edge = {
5636
+ id: containsId,
5637
+ source: service.node.id,
5638
+ target: mid,
5639
+ type: import_types12.EdgeType.CONTAINS,
5640
+ provenance: import_types12.Provenance.EXTRACTED,
5641
+ confidence: (0, import_types12.confidenceForExtracted)("structural"),
5642
+ evidence: {
5643
+ file: relFile,
5644
+ line: method.line,
5645
+ snippet: snippet(content, method.line)
5646
+ }
5647
+ };
5648
+ graph.addEdgeWithKey(containsId, service.node.id, mid, edge);
5649
+ edgesAdded++;
5650
+ }
5651
+ }
5652
+ }
5653
+ }
5654
+ return { nodesAdded, edgesAdded };
5655
+ }
5656
+
5447
5657
  // src/extract/calls/index.ts
5448
5658
  init_cjs_shims();
5449
- var import_types19 = require("@neat.is/types");
5659
+ var import_types20 = require("@neat.is/types");
5450
5660
 
5451
5661
  // src/extract/calls/http.ts
5452
5662
  init_cjs_shims();
5453
- var import_node_path24 = __toESM(require("path"), 1);
5663
+ var import_node_path25 = __toESM(require("path"), 1);
5454
5664
  var import_tree_sitter3 = __toESM(require("tree-sitter"), 1);
5455
5665
  var import_tree_sitter_javascript3 = __toESM(require("tree-sitter-javascript"), 1);
5456
5666
  var import_tree_sitter_python2 = __toESM(require("tree-sitter-python"), 1);
5457
- var import_types12 = require("@neat.is/types");
5667
+ var import_types13 = require("@neat.is/types");
5458
5668
  var STRING_LITERAL_NODE_TYPES = /* @__PURE__ */ new Set(["string_fragment", "string_content"]);
5459
5669
  var JSX_EXTERNAL_LINK_TAGS = /* @__PURE__ */ new Set(["a", "Link", "NavLink", "ExternalLink", "Anchor"]);
5460
5670
  function isInsideJsxExternalLink(node) {
@@ -5524,7 +5734,7 @@ async function addHttpCallEdges(graph, services) {
5524
5734
  const seen = /* @__PURE__ */ new Set();
5525
5735
  for (const file of files) {
5526
5736
  if (isTestPath(file.path)) continue;
5527
- const parser = import_node_path24.default.extname(file.path) === ".py" ? pyParser : jsParser;
5737
+ const parser = import_node_path25.default.extname(file.path) === ".py" ? pyParser : jsParser;
5528
5738
  let sites;
5529
5739
  try {
5530
5740
  sites = callsFromSource(file.content, parser, knownHosts);
@@ -5533,14 +5743,14 @@ async function addHttpCallEdges(graph, services) {
5533
5743
  continue;
5534
5744
  }
5535
5745
  if (sites.length === 0) continue;
5536
- const relFile = toPosix2(import_node_path24.default.relative(service.dir, file.path));
5746
+ const relFile = toPosix2(import_node_path25.default.relative(service.dir, file.path));
5537
5747
  for (const site of sites) {
5538
5748
  const targetId = hostToNodeId.get(site.host);
5539
5749
  if (!targetId || targetId === service.node.id) continue;
5540
5750
  const dedupKey = `${relFile}|${targetId}`;
5541
5751
  if (seen.has(dedupKey)) continue;
5542
5752
  seen.add(dedupKey);
5543
- const confidence = (0, import_types12.confidenceForExtracted)("url-literal-service-target");
5753
+ const confidence = (0, import_types13.confidenceForExtracted)("url-literal-service-target");
5544
5754
  const ev = {
5545
5755
  file: relFile,
5546
5756
  line: site.line,
@@ -5554,25 +5764,25 @@ async function addHttpCallEdges(graph, services) {
5554
5764
  );
5555
5765
  nodesAdded += n;
5556
5766
  edgesAdded += e;
5557
- if (!(0, import_types12.passesExtractedFloor)(confidence)) {
5767
+ if (!(0, import_types13.passesExtractedFloor)(confidence)) {
5558
5768
  noteExtractedDropped({
5559
5769
  source: fileNodeId,
5560
5770
  target: targetId,
5561
- type: import_types12.EdgeType.CALLS,
5771
+ type: import_types13.EdgeType.CALLS,
5562
5772
  confidence,
5563
5773
  confidenceKind: "url-literal-service-target",
5564
5774
  evidence: ev
5565
5775
  });
5566
5776
  continue;
5567
5777
  }
5568
- const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, targetId, import_types12.EdgeType.CALLS);
5778
+ const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, targetId, import_types13.EdgeType.CALLS);
5569
5779
  if (!graph.hasEdge(edgeId)) {
5570
5780
  const edge = {
5571
5781
  id: edgeId,
5572
5782
  source: fileNodeId,
5573
5783
  target: targetId,
5574
- type: import_types12.EdgeType.CALLS,
5575
- provenance: import_types12.Provenance.EXTRACTED,
5784
+ type: import_types13.EdgeType.CALLS,
5785
+ provenance: import_types13.Provenance.EXTRACTED,
5576
5786
  confidence,
5577
5787
  evidence: ev
5578
5788
  };
@@ -5587,10 +5797,10 @@ async function addHttpCallEdges(graph, services) {
5587
5797
 
5588
5798
  // src/extract/calls/route-match.ts
5589
5799
  init_cjs_shims();
5590
- var import_node_path25 = __toESM(require("path"), 1);
5800
+ var import_node_path26 = __toESM(require("path"), 1);
5591
5801
  var import_tree_sitter4 = __toESM(require("tree-sitter"), 1);
5592
5802
  var import_tree_sitter_javascript4 = __toESM(require("tree-sitter-javascript"), 1);
5593
- var import_types13 = require("@neat.is/types");
5803
+ var import_types14 = require("@neat.is/types");
5594
5804
  var PARSE_CHUNK4 = 16384;
5595
5805
  function parseSource4(parser, source) {
5596
5806
  return parser.parse(
@@ -5755,9 +5965,9 @@ function buildRouteIndex(graph) {
5755
5965
  const index = /* @__PURE__ */ new Map();
5756
5966
  graph.forEachNode((_id, attrs) => {
5757
5967
  const node = attrs;
5758
- if (node.type !== import_types13.NodeType.RouteNode) return;
5968
+ if (node.type !== import_types14.NodeType.RouteNode) return;
5759
5969
  const route = attrs;
5760
- const owner = (0, import_types13.serviceId)(route.service);
5970
+ const owner = (0, import_types14.serviceId)(route.service);
5761
5971
  const entry2 = {
5762
5972
  method: route.method.toUpperCase(),
5763
5973
  normalizedPath: normalizePathTemplate(route.pathTemplate),
@@ -5786,7 +5996,7 @@ async function addRouteCallEdges(graph, services) {
5786
5996
  const seen = /* @__PURE__ */ new Set();
5787
5997
  for (const file of files) {
5788
5998
  if (isTestPath(file.path)) continue;
5789
- if (!JS_CLIENT_EXTENSIONS.has(import_node_path25.default.extname(file.path))) continue;
5999
+ if (!JS_CLIENT_EXTENSIONS.has(import_node_path26.default.extname(file.path))) continue;
5790
6000
  let sites;
5791
6001
  try {
5792
6002
  sites = clientCallSitesFromSource(file.content, jsParser, knownHosts);
@@ -5795,7 +6005,7 @@ async function addRouteCallEdges(graph, services) {
5795
6005
  continue;
5796
6006
  }
5797
6007
  if (sites.length === 0) continue;
5798
- const relFile = toPosix2(import_node_path25.default.relative(service.dir, file.path));
6008
+ const relFile = toPosix2(import_node_path26.default.relative(service.dir, file.path));
5799
6009
  for (const site of sites) {
5800
6010
  const serverServiceId = hostToNodeId.get(site.host);
5801
6011
  if (!serverServiceId || serverServiceId === service.node.id) continue;
@@ -5815,7 +6025,7 @@ async function addRouteCallEdges(graph, services) {
5815
6025
  );
5816
6026
  nodesAdded += n;
5817
6027
  edgesAdded += e;
5818
- const confidence = (0, import_types13.confidenceForExtracted)("verified-call-site");
6028
+ const confidence = (0, import_types14.confidenceForExtracted)("verified-call-site");
5819
6029
  const ev = {
5820
6030
  file: relFile,
5821
6031
  line: site.line,
@@ -5823,25 +6033,25 @@ async function addRouteCallEdges(graph, services) {
5823
6033
  method: site.method ?? match.method,
5824
6034
  pathTemplate: site.pathTemplate
5825
6035
  };
5826
- if (!(0, import_types13.passesExtractedFloor)(confidence)) {
6036
+ if (!(0, import_types14.passesExtractedFloor)(confidence)) {
5827
6037
  noteExtractedDropped({
5828
6038
  source: fileNodeId,
5829
6039
  target: match.routeNodeId,
5830
- type: import_types13.EdgeType.CALLS,
6040
+ type: import_types14.EdgeType.CALLS,
5831
6041
  confidence,
5832
6042
  confidenceKind: "verified-call-site",
5833
6043
  evidence: ev
5834
6044
  });
5835
6045
  continue;
5836
6046
  }
5837
- const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, match.routeNodeId, import_types13.EdgeType.CALLS);
6047
+ const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, match.routeNodeId, import_types14.EdgeType.CALLS);
5838
6048
  if (!graph.hasEdge(edgeId)) {
5839
6049
  const edge = {
5840
6050
  id: edgeId,
5841
6051
  source: fileNodeId,
5842
6052
  target: match.routeNodeId,
5843
- type: import_types13.EdgeType.CALLS,
5844
- provenance: import_types13.Provenance.EXTRACTED,
6053
+ type: import_types14.EdgeType.CALLS,
6054
+ provenance: import_types14.Provenance.EXTRACTED,
5845
6055
  confidence,
5846
6056
  evidence: ev
5847
6057
  };
@@ -5856,8 +6066,8 @@ async function addRouteCallEdges(graph, services) {
5856
6066
 
5857
6067
  // src/extract/calls/kafka.ts
5858
6068
  init_cjs_shims();
5859
- var import_node_path26 = __toESM(require("path"), 1);
5860
- var import_types14 = require("@neat.is/types");
6069
+ var import_node_path27 = __toESM(require("path"), 1);
6070
+ var import_types15 = require("@neat.is/types");
5861
6071
  var PRODUCER_TOPIC_RE = /(?:producer|kafkaProducer)[\s\S]{0,40}?\.send\s*\(\s*\{[\s\S]{0,200}?topic\s*:\s*['"`]([^'"`]+)['"`]/g;
5862
6072
  var CONSUMER_TOPIC_RE = /(?:consumer|kafkaConsumer)[\s\S]{0,40}?\.(?:subscribe|run)\s*\(\s*\{[\s\S]{0,200}?topic[s]?\s*:\s*(?:\[\s*)?['"`]([^'"`]+)['"`]/g;
5863
6073
  function findAll(re, text) {
@@ -5878,7 +6088,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
5878
6088
  seen.add(key);
5879
6089
  const line = lineOf(file.content, topic);
5880
6090
  out.push({
5881
- infraId: (0, import_types14.infraId)("kafka-topic", topic),
6091
+ infraId: (0, import_types15.infraId)("kafka-topic", topic),
5882
6092
  name: topic,
5883
6093
  kind: "kafka-topic",
5884
6094
  edgeType,
@@ -5887,7 +6097,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
5887
6097
  // tier (ADR-066).
5888
6098
  confidenceKind: "verified-call-site",
5889
6099
  evidence: {
5890
- file: import_node_path26.default.relative(serviceDir, file.path),
6100
+ file: import_node_path27.default.relative(serviceDir, file.path),
5891
6101
  line,
5892
6102
  snippet: snippet(file.content, line)
5893
6103
  }
@@ -5900,8 +6110,8 @@ function kafkaEndpointsFromFile(file, serviceDir) {
5900
6110
 
5901
6111
  // src/extract/calls/redis.ts
5902
6112
  init_cjs_shims();
5903
- var import_node_path27 = __toESM(require("path"), 1);
5904
- var import_types15 = require("@neat.is/types");
6113
+ var import_node_path28 = __toESM(require("path"), 1);
6114
+ var import_types16 = require("@neat.is/types");
5905
6115
  var REDIS_URL_RE = /redis(?:s)?:\/\/(?:[^@'"`\s]+@)?([^:/'"`\s]+)(?::(\d+))?/g;
5906
6116
  function redisEndpointsFromFile(file, serviceDir) {
5907
6117
  const out = [];
@@ -5914,7 +6124,7 @@ function redisEndpointsFromFile(file, serviceDir) {
5914
6124
  seen.add(host);
5915
6125
  const line = lineOf(file.content, host);
5916
6126
  out.push({
5917
- infraId: (0, import_types15.infraId)("redis", host),
6127
+ infraId: (0, import_types16.infraId)("redis", host),
5918
6128
  name: host,
5919
6129
  kind: "redis",
5920
6130
  edgeType: "CALLS",
@@ -5923,7 +6133,7 @@ function redisEndpointsFromFile(file, serviceDir) {
5923
6133
  // support tier (ADR-066).
5924
6134
  confidenceKind: "url-with-structural-support",
5925
6135
  evidence: {
5926
- file: import_node_path27.default.relative(serviceDir, file.path),
6136
+ file: import_node_path28.default.relative(serviceDir, file.path),
5927
6137
  line,
5928
6138
  snippet: snippet(file.content, line)
5929
6139
  }
@@ -5934,8 +6144,8 @@ function redisEndpointsFromFile(file, serviceDir) {
5934
6144
 
5935
6145
  // src/extract/calls/aws.ts
5936
6146
  init_cjs_shims();
5937
- var import_node_path28 = __toESM(require("path"), 1);
5938
- var import_types16 = require("@neat.is/types");
6147
+ var import_node_path29 = __toESM(require("path"), 1);
6148
+ var import_types17 = require("@neat.is/types");
5939
6149
  var S3_BUCKET_RE = /Bucket\s*:\s*['"`]([^'"`]+)['"`]/g;
5940
6150
  var DYNAMO_TABLE_RE = /TableName\s*:\s*['"`]([^'"`]+)['"`]/g;
5941
6151
  function hasMarker(text, markers) {
@@ -5959,7 +6169,7 @@ function awsEndpointsFromFile(file, serviceDir) {
5959
6169
  seen.add(key);
5960
6170
  const line = lineOf(file.content, name);
5961
6171
  out.push({
5962
- infraId: (0, import_types16.infraId)(kind, name),
6172
+ infraId: (0, import_types17.infraId)(kind, name),
5963
6173
  name,
5964
6174
  kind,
5965
6175
  edgeType: "CALLS",
@@ -5968,7 +6178,7 @@ function awsEndpointsFromFile(file, serviceDir) {
5968
6178
  // (ADR-066).
5969
6179
  confidenceKind: "verified-call-site",
5970
6180
  evidence: {
5971
- file: import_node_path28.default.relative(serviceDir, file.path),
6181
+ file: import_node_path29.default.relative(serviceDir, file.path),
5972
6182
  line,
5973
6183
  snippet: snippet(file.content, line)
5974
6184
  }
@@ -5993,8 +6203,8 @@ function awsEndpointsFromFile(file, serviceDir) {
5993
6203
 
5994
6204
  // src/extract/calls/grpc.ts
5995
6205
  init_cjs_shims();
5996
- var import_node_path29 = __toESM(require("path"), 1);
5997
- var import_types17 = require("@neat.is/types");
6206
+ var import_node_path30 = __toESM(require("path"), 1);
6207
+ var import_types18 = require("@neat.is/types");
5998
6208
  var GRPC_CLIENT_RE = /new\s+([A-Z][A-Za-z0-9_]*)Client\s*\(\s*['"`]?([^,'"`)]+)?/g;
5999
6209
  var AWS_SDK_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@aws-sdk\/client-([a-z0-9-]+)['"`]/g;
6000
6210
  var GRPC_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@grpc\/grpc-js['"`]|_grpc_pb['"`]/;
@@ -6043,7 +6253,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
6043
6253
  const { kind } = classified;
6044
6254
  const line = lineOf(file.content, m[0]);
6045
6255
  out.push({
6046
- infraId: (0, import_types17.infraId)(kind, name),
6256
+ infraId: (0, import_types18.infraId)(kind, name),
6047
6257
  name,
6048
6258
  kind,
6049
6259
  edgeType: "CALLS",
@@ -6052,7 +6262,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
6052
6262
  // tier (ADR-066).
6053
6263
  confidenceKind: "verified-call-site",
6054
6264
  evidence: {
6055
- file: import_node_path29.default.relative(serviceDir, file.path),
6265
+ file: import_node_path30.default.relative(serviceDir, file.path),
6056
6266
  line,
6057
6267
  snippet: snippet(file.content, line)
6058
6268
  }
@@ -6063,8 +6273,8 @@ function grpcEndpointsFromFile(file, serviceDir) {
6063
6273
 
6064
6274
  // src/extract/calls/supabase.ts
6065
6275
  init_cjs_shims();
6066
- var import_node_path30 = __toESM(require("path"), 1);
6067
- var import_types18 = require("@neat.is/types");
6276
+ var import_node_path31 = __toESM(require("path"), 1);
6277
+ var import_types19 = require("@neat.is/types");
6068
6278
  var SUPABASE_JS_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@supabase\/supabase-js['"`]/;
6069
6279
  var SUPABASE_SSR_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@supabase\/ssr['"`]/;
6070
6280
  var SUPABASE_CLIENT_RE = /\b(createClient|createServerClient|createBrowserClient)\s*\(\s*(?:['"`]([^'"`]*)['"`])?/g;
@@ -6102,7 +6312,7 @@ function supabaseEndpointsFromFile(file, serviceDir) {
6102
6312
  seen.add(name);
6103
6313
  const line = lineOf(file.content, m[0]);
6104
6314
  out.push({
6105
- infraId: (0, import_types18.infraId)("supabase", name),
6315
+ infraId: (0, import_types19.infraId)("supabase", name),
6106
6316
  name,
6107
6317
  kind: "supabase",
6108
6318
  edgeType: "CALLS",
@@ -6112,7 +6322,7 @@ function supabaseEndpointsFromFile(file, serviceDir) {
6112
6322
  // tier (ADR-066), the same grade aws.ts / grpc.ts emit at.
6113
6323
  confidenceKind: "verified-call-site",
6114
6324
  evidence: {
6115
- file: import_node_path30.default.relative(serviceDir, file.path),
6325
+ file: import_node_path31.default.relative(serviceDir, file.path),
6116
6326
  line,
6117
6327
  snippet: snippet(file.content, line)
6118
6328
  }
@@ -6125,11 +6335,11 @@ function supabaseEndpointsFromFile(file, serviceDir) {
6125
6335
  function edgeTypeFromEndpoint(ep) {
6126
6336
  switch (ep.edgeType) {
6127
6337
  case "PUBLISHES_TO":
6128
- return import_types19.EdgeType.PUBLISHES_TO;
6338
+ return import_types20.EdgeType.PUBLISHES_TO;
6129
6339
  case "CONSUMES_FROM":
6130
- return import_types19.EdgeType.CONSUMES_FROM;
6340
+ return import_types20.EdgeType.CONSUMES_FROM;
6131
6341
  default:
6132
- return import_types19.EdgeType.CALLS;
6342
+ return import_types20.EdgeType.CALLS;
6133
6343
  }
6134
6344
  }
6135
6345
  function isAwsKind(kind) {
@@ -6157,7 +6367,7 @@ async function addExternalEndpointEdges(graph, services) {
6157
6367
  if (!graph.hasNode(ep.infraId)) {
6158
6368
  const node = {
6159
6369
  id: ep.infraId,
6160
- type: import_types19.NodeType.InfraNode,
6370
+ type: import_types20.NodeType.InfraNode,
6161
6371
  name: ep.name,
6162
6372
  // #238 — `aws-*` covers AWS-SDK client kinds (aws-s3, aws-dynamodb,
6163
6373
  // aws-cognito-identity-provider, …); `s3-` / `dynamodb-` cover the
@@ -6169,7 +6379,7 @@ async function addExternalEndpointEdges(graph, services) {
6169
6379
  nodesAdded++;
6170
6380
  }
6171
6381
  const edgeType = edgeTypeFromEndpoint(ep);
6172
- const confidence = (0, import_types19.confidenceForExtracted)(ep.confidenceKind);
6382
+ const confidence = (0, import_types20.confidenceForExtracted)(ep.confidenceKind);
6173
6383
  const relFile = toPosix2(ep.evidence.file);
6174
6384
  const { fileNodeId, nodesAdded: n, edgesAdded: e } = ensureFileNode(
6175
6385
  graph,
@@ -6179,7 +6389,7 @@ async function addExternalEndpointEdges(graph, services) {
6179
6389
  );
6180
6390
  nodesAdded += n;
6181
6391
  edgesAdded += e;
6182
- if (!(0, import_types19.passesExtractedFloor)(confidence)) {
6392
+ if (!(0, import_types20.passesExtractedFloor)(confidence)) {
6183
6393
  noteExtractedDropped({
6184
6394
  source: fileNodeId,
6185
6395
  target: ep.infraId,
@@ -6199,7 +6409,7 @@ async function addExternalEndpointEdges(graph, services) {
6199
6409
  source: fileNodeId,
6200
6410
  target: ep.infraId,
6201
6411
  type: edgeType,
6202
- provenance: import_types19.Provenance.EXTRACTED,
6412
+ provenance: import_types20.Provenance.EXTRACTED,
6203
6413
  confidence,
6204
6414
  evidence: ep.evidence
6205
6415
  };
@@ -6225,16 +6435,16 @@ init_cjs_shims();
6225
6435
 
6226
6436
  // src/extract/infra/docker-compose.ts
6227
6437
  init_cjs_shims();
6228
- var import_node_path31 = __toESM(require("path"), 1);
6229
- var import_types21 = require("@neat.is/types");
6438
+ var import_node_path32 = __toESM(require("path"), 1);
6439
+ var import_types22 = require("@neat.is/types");
6230
6440
 
6231
6441
  // src/extract/infra/shared.ts
6232
6442
  init_cjs_shims();
6233
- var import_types20 = require("@neat.is/types");
6443
+ var import_types21 = require("@neat.is/types");
6234
6444
  function makeInfraNode(kind, name, provider = "self", extras) {
6235
6445
  return {
6236
- id: (0, import_types20.infraId)(kind, name),
6237
- type: import_types20.NodeType.InfraNode,
6446
+ id: (0, import_types21.infraId)(kind, name),
6447
+ type: import_types21.NodeType.InfraNode,
6238
6448
  name,
6239
6449
  provider,
6240
6450
  kind,
@@ -6263,7 +6473,7 @@ function dependsOnList(value) {
6263
6473
  }
6264
6474
  function serviceNameToServiceNode(name, services) {
6265
6475
  for (const s of services) {
6266
- if (s.node.name === name || import_node_path31.default.basename(s.dir) === name) return s.node.id;
6476
+ if (s.node.name === name || import_node_path32.default.basename(s.dir) === name) return s.node.id;
6267
6477
  }
6268
6478
  return null;
6269
6479
  }
@@ -6272,7 +6482,7 @@ async function addComposeInfra(graph, scanPath, services) {
6272
6482
  let edgesAdded = 0;
6273
6483
  let composePath = null;
6274
6484
  for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
6275
- const abs = import_node_path31.default.join(scanPath, name);
6485
+ const abs = import_node_path32.default.join(scanPath, name);
6276
6486
  if (await exists(abs)) {
6277
6487
  composePath = abs;
6278
6488
  break;
@@ -6285,13 +6495,13 @@ async function addComposeInfra(graph, scanPath, services) {
6285
6495
  } catch (err) {
6286
6496
  recordExtractionError(
6287
6497
  "infra docker-compose",
6288
- import_node_path31.default.relative(scanPath, composePath),
6498
+ import_node_path32.default.relative(scanPath, composePath),
6289
6499
  err
6290
6500
  );
6291
6501
  return { nodesAdded, edgesAdded };
6292
6502
  }
6293
6503
  if (!compose?.services) return { nodesAdded, edgesAdded };
6294
- const evidenceFile = import_node_path31.default.relative(scanPath, composePath).split(import_node_path31.default.sep).join("/");
6504
+ const evidenceFile = import_node_path32.default.relative(scanPath, composePath).split(import_node_path32.default.sep).join("/");
6295
6505
  const composeNameToNodeId = /* @__PURE__ */ new Map();
6296
6506
  for (const [composeName, svc] of Object.entries(compose.services)) {
6297
6507
  const matchedServiceId = serviceNameToServiceNode(composeName, services);
@@ -6313,15 +6523,15 @@ async function addComposeInfra(graph, scanPath, services) {
6313
6523
  for (const dep of dependsOnList(svc.depends_on)) {
6314
6524
  const targetId = composeNameToNodeId.get(dep);
6315
6525
  if (!targetId) continue;
6316
- const edgeId = (0, import_types4.extractedEdgeId)(sourceId, targetId, import_types21.EdgeType.DEPENDS_ON);
6526
+ const edgeId = (0, import_types4.extractedEdgeId)(sourceId, targetId, import_types22.EdgeType.DEPENDS_ON);
6317
6527
  if (graph.hasEdge(edgeId)) continue;
6318
6528
  const edge = {
6319
6529
  id: edgeId,
6320
6530
  source: sourceId,
6321
6531
  target: targetId,
6322
- type: import_types21.EdgeType.DEPENDS_ON,
6323
- provenance: import_types21.Provenance.EXTRACTED,
6324
- confidence: (0, import_types21.confidenceForExtracted)("structural"),
6532
+ type: import_types22.EdgeType.DEPENDS_ON,
6533
+ provenance: import_types22.Provenance.EXTRACTED,
6534
+ confidence: (0, import_types22.confidenceForExtracted)("structural"),
6325
6535
  evidence: { file: evidenceFile }
6326
6536
  };
6327
6537
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -6333,9 +6543,9 @@ async function addComposeInfra(graph, scanPath, services) {
6333
6543
 
6334
6544
  // src/extract/infra/dockerfile.ts
6335
6545
  init_cjs_shims();
6336
- var import_node_path32 = __toESM(require("path"), 1);
6337
- var import_node_fs16 = require("fs");
6338
- var import_types22 = require("@neat.is/types");
6546
+ var import_node_path33 = __toESM(require("path"), 1);
6547
+ var import_node_fs17 = require("fs");
6548
+ var import_types23 = require("@neat.is/types");
6339
6549
  function readDockerfile(content) {
6340
6550
  let image = null;
6341
6551
  const ports = [];
@@ -6364,15 +6574,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
6364
6574
  let nodesAdded = 0;
6365
6575
  let edgesAdded = 0;
6366
6576
  for (const service of services) {
6367
- const dockerfilePath = import_node_path32.default.join(service.dir, "Dockerfile");
6577
+ const dockerfilePath = import_node_path33.default.join(service.dir, "Dockerfile");
6368
6578
  if (!await exists(dockerfilePath)) continue;
6369
6579
  let content;
6370
6580
  try {
6371
- content = await import_node_fs16.promises.readFile(dockerfilePath, "utf8");
6581
+ content = await import_node_fs17.promises.readFile(dockerfilePath, "utf8");
6372
6582
  } catch (err) {
6373
6583
  recordExtractionError(
6374
6584
  "infra dockerfile",
6375
- import_node_path32.default.relative(scanPath, dockerfilePath),
6585
+ import_node_path33.default.relative(scanPath, dockerfilePath),
6376
6586
  err
6377
6587
  );
6378
6588
  continue;
@@ -6384,8 +6594,8 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
6384
6594
  graph.addNode(node.id, node);
6385
6595
  nodesAdded++;
6386
6596
  }
6387
- const relDockerfile = toPosix2(import_node_path32.default.relative(service.dir, dockerfilePath));
6388
- const evidenceFile = toPosix2(import_node_path32.default.relative(scanPath, dockerfilePath));
6597
+ const relDockerfile = toPosix2(import_node_path33.default.relative(service.dir, dockerfilePath));
6598
+ const evidenceFile = toPosix2(import_node_path33.default.relative(scanPath, dockerfilePath));
6389
6599
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
6390
6600
  graph,
6391
6601
  service.pkg.name,
@@ -6394,15 +6604,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
6394
6604
  );
6395
6605
  nodesAdded += fn;
6396
6606
  edgesAdded += fe;
6397
- const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, node.id, import_types22.EdgeType.RUNS_ON);
6607
+ const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, node.id, import_types23.EdgeType.RUNS_ON);
6398
6608
  if (!graph.hasEdge(edgeId)) {
6399
6609
  const edge = {
6400
6610
  id: edgeId,
6401
6611
  source: fileNodeId,
6402
6612
  target: node.id,
6403
- type: import_types22.EdgeType.RUNS_ON,
6404
- provenance: import_types22.Provenance.EXTRACTED,
6405
- confidence: (0, import_types22.confidenceForExtracted)("structural"),
6613
+ type: import_types23.EdgeType.RUNS_ON,
6614
+ provenance: import_types23.Provenance.EXTRACTED,
6615
+ confidence: (0, import_types23.confidenceForExtracted)("structural"),
6406
6616
  evidence: {
6407
6617
  file: evidenceFile,
6408
6618
  ...facts.entrypoint ? { snippet: facts.entrypoint.slice(0, 120) } : {}
@@ -6417,15 +6627,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
6417
6627
  graph.addNode(portNode.id, portNode);
6418
6628
  nodesAdded++;
6419
6629
  }
6420
- const portEdgeId = (0, import_types4.extractedEdgeId)(fileNodeId, portNode.id, import_types22.EdgeType.CONNECTS_TO);
6630
+ const portEdgeId = (0, import_types4.extractedEdgeId)(fileNodeId, portNode.id, import_types23.EdgeType.CONNECTS_TO);
6421
6631
  if (graph.hasEdge(portEdgeId)) continue;
6422
6632
  const portEdge = {
6423
6633
  id: portEdgeId,
6424
6634
  source: fileNodeId,
6425
6635
  target: portNode.id,
6426
- type: import_types22.EdgeType.CONNECTS_TO,
6427
- provenance: import_types22.Provenance.EXTRACTED,
6428
- confidence: (0, import_types22.confidenceForExtracted)("structural"),
6636
+ type: import_types23.EdgeType.CONNECTS_TO,
6637
+ provenance: import_types23.Provenance.EXTRACTED,
6638
+ confidence: (0, import_types23.confidenceForExtracted)("structural"),
6429
6639
  evidence: { file: evidenceFile, snippet: `EXPOSE ${port}` }
6430
6640
  };
6431
6641
  graph.addEdgeWithKey(portEdgeId, portEdge.source, portEdge.target, portEdge);
@@ -6437,23 +6647,23 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
6437
6647
 
6438
6648
  // src/extract/infra/terraform.ts
6439
6649
  init_cjs_shims();
6440
- var import_node_fs17 = require("fs");
6441
- var import_node_path33 = __toESM(require("path"), 1);
6442
- var import_types23 = require("@neat.is/types");
6650
+ var import_node_fs18 = require("fs");
6651
+ var import_node_path34 = __toESM(require("path"), 1);
6652
+ var import_types24 = require("@neat.is/types");
6443
6653
  var RESOURCE_RE = /resource\s+"(aws_[A-Za-z0-9_]+)"\s+"([A-Za-z0-9_-]+)"/g;
6444
6654
  var REFERENCE_RE = /(?<![\w.])(aws_[A-Za-z0-9_]+)\.([A-Za-z0-9_-]+)/g;
6445
6655
  async function walkTfFiles(start, depth = 0, max = 5) {
6446
6656
  if (depth > max) return [];
6447
6657
  const out = [];
6448
- const entries = await import_node_fs17.promises.readdir(start, { withFileTypes: true }).catch(() => []);
6658
+ const entries = await import_node_fs18.promises.readdir(start, { withFileTypes: true }).catch(() => []);
6449
6659
  for (const entry2 of entries) {
6450
6660
  if (entry2.isDirectory()) {
6451
6661
  if (IGNORED_DIRS.has(entry2.name) || entry2.name === ".terraform") continue;
6452
- const child = import_node_path33.default.join(start, entry2.name);
6662
+ const child = import_node_path34.default.join(start, entry2.name);
6453
6663
  if (await isPythonVenvDir(child)) continue;
6454
6664
  out.push(...await walkTfFiles(child, depth + 1, max));
6455
6665
  } else if (entry2.isFile() && entry2.name.endsWith(".tf")) {
6456
- out.push(import_node_path33.default.join(start, entry2.name));
6666
+ out.push(import_node_path34.default.join(start, entry2.name));
6457
6667
  }
6458
6668
  }
6459
6669
  return out;
@@ -6472,7 +6682,7 @@ function blockBody(content, from) {
6472
6682
  }
6473
6683
  return null;
6474
6684
  }
6475
- function lineAt(content, index) {
6685
+ function lineAt2(content, index) {
6476
6686
  let line = 1;
6477
6687
  for (let i = 0; i < index && i < content.length; i++) {
6478
6688
  if (content[i] === "\n") line++;
@@ -6484,8 +6694,8 @@ async function addTerraformResources(graph, scanPath) {
6484
6694
  let edgesAdded = 0;
6485
6695
  const files = await walkTfFiles(scanPath);
6486
6696
  for (const file of files) {
6487
- const content = await import_node_fs17.promises.readFile(file, "utf8");
6488
- const evidenceFile = toPosix2(import_node_path33.default.relative(scanPath, file));
6697
+ const content = await import_node_fs18.promises.readFile(file, "utf8");
6698
+ const evidenceFile = toPosix2(import_node_path34.default.relative(scanPath, file));
6489
6699
  const resources = [];
6490
6700
  const byKey = /* @__PURE__ */ new Map();
6491
6701
  RESOURCE_RE.lastIndex = 0;
@@ -6520,16 +6730,16 @@ async function addTerraformResources(graph, scanPath) {
6520
6730
  if (!target) continue;
6521
6731
  if (seen.has(target.nodeId)) continue;
6522
6732
  seen.add(target.nodeId);
6523
- const edgeId = (0, import_types4.extractedEdgeId)(resource.nodeId, target.nodeId, import_types23.EdgeType.DEPENDS_ON);
6733
+ const edgeId = (0, import_types4.extractedEdgeId)(resource.nodeId, target.nodeId, import_types24.EdgeType.DEPENDS_ON);
6524
6734
  if (graph.hasEdge(edgeId)) continue;
6525
- const line = lineAt(content, resource.bodyOffset + ref.index);
6735
+ const line = lineAt2(content, resource.bodyOffset + ref.index);
6526
6736
  const edge = {
6527
6737
  id: edgeId,
6528
6738
  source: resource.nodeId,
6529
6739
  target: target.nodeId,
6530
- type: import_types23.EdgeType.DEPENDS_ON,
6531
- provenance: import_types23.Provenance.EXTRACTED,
6532
- confidence: (0, import_types23.confidenceForExtracted)("structural"),
6740
+ type: import_types24.EdgeType.DEPENDS_ON,
6741
+ provenance: import_types24.Provenance.EXTRACTED,
6742
+ confidence: (0, import_types24.confidenceForExtracted)("structural"),
6533
6743
  evidence: { file: evidenceFile, line, snippet: key }
6534
6744
  };
6535
6745
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -6542,8 +6752,8 @@ async function addTerraformResources(graph, scanPath) {
6542
6752
 
6543
6753
  // src/extract/infra/k8s.ts
6544
6754
  init_cjs_shims();
6545
- var import_node_fs18 = require("fs");
6546
- var import_node_path34 = __toESM(require("path"), 1);
6755
+ var import_node_fs19 = require("fs");
6756
+ var import_node_path35 = __toESM(require("path"), 1);
6547
6757
  var import_yaml3 = require("yaml");
6548
6758
  var K8S_KIND_TO_INFRA_KIND = {
6549
6759
  Service: "k8s-service",
@@ -6557,15 +6767,15 @@ var K8S_KIND_TO_INFRA_KIND = {
6557
6767
  async function walkYamlFiles2(start, depth = 0, max = 5) {
6558
6768
  if (depth > max) return [];
6559
6769
  const out = [];
6560
- const entries = await import_node_fs18.promises.readdir(start, { withFileTypes: true }).catch(() => []);
6770
+ const entries = await import_node_fs19.promises.readdir(start, { withFileTypes: true }).catch(() => []);
6561
6771
  for (const entry2 of entries) {
6562
6772
  if (entry2.isDirectory()) {
6563
6773
  if (IGNORED_DIRS.has(entry2.name)) continue;
6564
- const child = import_node_path34.default.join(start, entry2.name);
6774
+ const child = import_node_path35.default.join(start, entry2.name);
6565
6775
  if (await isPythonVenvDir(child)) continue;
6566
6776
  out.push(...await walkYamlFiles2(child, depth + 1, max));
6567
- } else if (entry2.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path34.default.extname(entry2.name))) {
6568
- out.push(import_node_path34.default.join(start, entry2.name));
6777
+ } else if (entry2.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path35.default.extname(entry2.name))) {
6778
+ out.push(import_node_path35.default.join(start, entry2.name));
6569
6779
  }
6570
6780
  }
6571
6781
  return out;
@@ -6574,7 +6784,7 @@ async function addK8sResources(graph, scanPath) {
6574
6784
  let nodesAdded = 0;
6575
6785
  const files = await walkYamlFiles2(scanPath);
6576
6786
  for (const file of files) {
6577
- const content = await import_node_fs18.promises.readFile(file, "utf8");
6787
+ const content = await import_node_fs19.promises.readFile(file, "utf8");
6578
6788
  let docs;
6579
6789
  try {
6580
6790
  docs = (0, import_yaml3.parseAllDocuments)(content).map((d) => d.toJSON());
@@ -6609,17 +6819,17 @@ async function addInfra(graph, scanPath, services) {
6609
6819
  }
6610
6820
 
6611
6821
  // src/extract/index.ts
6612
- var import_node_path36 = __toESM(require("path"), 1);
6822
+ var import_node_path37 = __toESM(require("path"), 1);
6613
6823
 
6614
6824
  // src/extract/retire.ts
6615
6825
  init_cjs_shims();
6616
- var import_node_fs19 = require("fs");
6617
- var import_node_path35 = __toESM(require("path"), 1);
6618
- var import_types24 = require("@neat.is/types");
6826
+ var import_node_fs20 = require("fs");
6827
+ var import_node_path36 = __toESM(require("path"), 1);
6828
+ var import_types25 = require("@neat.is/types");
6619
6829
  function dropOrphanedFileNodes(graph) {
6620
6830
  const orphans = [];
6621
6831
  graph.forEachNode((id, attrs) => {
6622
- if (attrs.type !== import_types24.NodeType.FileNode) return;
6832
+ if (attrs.type !== import_types25.NodeType.FileNode) return;
6623
6833
  if (graph.inboundEdges(id).length === 0 && graph.outboundEdges(id).length === 0) {
6624
6834
  orphans.push(id);
6625
6835
  }
@@ -6632,7 +6842,7 @@ function retireEdgesByFile(graph, file) {
6632
6842
  const toDrop = [];
6633
6843
  graph.forEachEdge((id, attrs) => {
6634
6844
  const edge = attrs;
6635
- if (edge.provenance !== import_types24.Provenance.EXTRACTED) return;
6845
+ if (edge.provenance !== import_types25.Provenance.EXTRACTED) return;
6636
6846
  if (!edge.evidence?.file) return;
6637
6847
  if (edge.evidence.file === normalized) toDrop.push(id);
6638
6848
  });
@@ -6645,14 +6855,14 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
6645
6855
  const bases = [scanPath, ...serviceDirs];
6646
6856
  graph.forEachEdge((id, attrs) => {
6647
6857
  const edge = attrs;
6648
- if (edge.provenance !== import_types24.Provenance.EXTRACTED) return;
6858
+ if (edge.provenance !== import_types25.Provenance.EXTRACTED) return;
6649
6859
  const evidenceFile = edge.evidence?.file;
6650
6860
  if (!evidenceFile) return;
6651
- if (import_node_path35.default.isAbsolute(evidenceFile)) {
6652
- if (!(0, import_node_fs19.existsSync)(evidenceFile)) toDrop.push(id);
6861
+ if (import_node_path36.default.isAbsolute(evidenceFile)) {
6862
+ if (!(0, import_node_fs20.existsSync)(evidenceFile)) toDrop.push(id);
6653
6863
  return;
6654
6864
  }
6655
- const found = bases.some((base) => (0, import_node_fs19.existsSync)(import_node_path35.default.join(base, evidenceFile)));
6865
+ const found = bases.some((base) => (0, import_node_fs20.existsSync)(import_node_path36.default.join(base, evidenceFile)));
6656
6866
  if (!found) toDrop.push(id);
6657
6867
  });
6658
6868
  for (const id of toDrop) graph.dropEdge(id);
@@ -6672,6 +6882,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
6672
6882
  const phase2 = await addDatabasesAndCompat(graph, services, scanPath);
6673
6883
  const phase3 = await addConfigNodes(graph, services, scanPath);
6674
6884
  const routePhase = await addRoutes(graph, services);
6885
+ const grpcPhase = await addGrpcMethods(graph, services);
6675
6886
  const phase4 = await addCallEdges(graph, services);
6676
6887
  const phase5 = await addInfra(graph, scanPath, services);
6677
6888
  const ghostsRetired = retireExtractedEdgesByMissingFile(
@@ -6693,7 +6904,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
6693
6904
  }
6694
6905
  const droppedEntries = drainDroppedExtracted();
6695
6906
  if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
6696
- const rejectedPath = import_node_path36.default.join(import_node_path36.default.dirname(opts.errorsPath), "rejected.ndjson");
6907
+ const rejectedPath = import_node_path37.default.join(import_node_path37.default.dirname(opts.errorsPath), "rejected.ndjson");
6697
6908
  try {
6698
6909
  await writeRejectedExtracted(droppedEntries, rejectedPath);
6699
6910
  } catch (err) {
@@ -6703,8 +6914,8 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
6703
6914
  }
6704
6915
  }
6705
6916
  const result = {
6706
- nodesAdded: phase1Nodes + fileEnum.nodesAdded + importGraph.nodesAdded + phase2.nodesAdded + phase3.nodesAdded + routePhase.nodesAdded + phase4.nodesAdded + phase5.nodesAdded,
6707
- edgesAdded: fileEnum.edgesAdded + importGraph.edgesAdded + phase2.edgesAdded + phase3.edgesAdded + routePhase.edgesAdded + phase4.edgesAdded + phase5.edgesAdded,
6917
+ nodesAdded: phase1Nodes + fileEnum.nodesAdded + importGraph.nodesAdded + phase2.nodesAdded + phase3.nodesAdded + routePhase.nodesAdded + grpcPhase.nodesAdded + phase4.nodesAdded + phase5.nodesAdded,
6918
+ edgesAdded: fileEnum.edgesAdded + importGraph.edgesAdded + phase2.edgesAdded + phase3.edgesAdded + routePhase.edgesAdded + grpcPhase.edgesAdded + phase4.edgesAdded + phase5.edgesAdded,
6708
6919
  frontiersPromoted,
6709
6920
  extractionErrors: errorEntries.length,
6710
6921
  errorEntries,
@@ -6727,7 +6938,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
6727
6938
 
6728
6939
  // src/divergences.ts
6729
6940
  init_cjs_shims();
6730
- var import_types25 = require("@neat.is/types");
6941
+ var import_types26 = require("@neat.is/types");
6731
6942
  function bucketKey(source, target, type) {
6732
6943
  return `${type}|${source}|${target}`;
6733
6944
  }
@@ -6735,22 +6946,22 @@ function bucketEdges(graph) {
6735
6946
  const buckets = /* @__PURE__ */ new Map();
6736
6947
  graph.forEachEdge((id, attrs) => {
6737
6948
  const e = attrs;
6738
- const parsed = (0, import_types25.parseEdgeId)(id);
6949
+ const parsed = (0, import_types26.parseEdgeId)(id);
6739
6950
  const provenance = parsed?.provenance ?? e.provenance;
6740
6951
  const key = bucketKey(e.source, e.target, e.type);
6741
6952
  const cur = buckets.get(key) ?? { source: e.source, target: e.target, type: e.type };
6742
6953
  switch (provenance) {
6743
- case import_types25.Provenance.EXTRACTED:
6954
+ case import_types26.Provenance.EXTRACTED:
6744
6955
  cur.extracted = e;
6745
6956
  break;
6746
- case import_types25.Provenance.OBSERVED:
6957
+ case import_types26.Provenance.OBSERVED:
6747
6958
  cur.observed = e;
6748
6959
  break;
6749
- case import_types25.Provenance.INFERRED:
6960
+ case import_types26.Provenance.INFERRED:
6750
6961
  cur.inferred = e;
6751
6962
  break;
6752
6963
  default:
6753
- if (e.provenance === import_types25.Provenance.STALE) cur.stale = e;
6964
+ if (e.provenance === import_types26.Provenance.STALE) cur.stale = e;
6754
6965
  }
6755
6966
  buckets.set(key, cur);
6756
6967
  });
@@ -6759,7 +6970,12 @@ function bucketEdges(graph) {
6759
6970
  function nodeIsFrontier(graph, nodeId) {
6760
6971
  if (!graph.hasNode(nodeId)) return false;
6761
6972
  const attrs = graph.getNodeAttributes(nodeId);
6762
- return attrs.type === import_types25.NodeType.FrontierNode;
6973
+ return attrs.type === import_types26.NodeType.FrontierNode;
6974
+ }
6975
+ function nodeIsWebsocketChannel(graph, nodeId) {
6976
+ if (!graph.hasNode(nodeId)) return false;
6977
+ const attrs = graph.getNodeAttributes(nodeId);
6978
+ return attrs.type === import_types26.NodeType.WebSocketChannelNode;
6763
6979
  }
6764
6980
  function clampConfidence(n) {
6765
6981
  if (!Number.isFinite(n)) return 0;
@@ -6779,14 +6995,14 @@ function gradedConfidence(edge) {
6779
6995
  return clampConfidence(confidenceForEdge(edge));
6780
6996
  }
6781
6997
  var OBSERVABLE_EDGE_TYPES = /* @__PURE__ */ new Set([
6782
- import_types25.EdgeType.CALLS,
6783
- import_types25.EdgeType.CONNECTS_TO,
6784
- import_types25.EdgeType.PUBLISHES_TO,
6785
- import_types25.EdgeType.CONSUMES_FROM
6998
+ import_types26.EdgeType.CALLS,
6999
+ import_types26.EdgeType.CONNECTS_TO,
7000
+ import_types26.EdgeType.PUBLISHES_TO,
7001
+ import_types26.EdgeType.CONSUMES_FROM
6786
7002
  ]);
6787
7003
  function detectMissingDivergences(graph, bucket) {
6788
7004
  const out = [];
6789
- if (bucket.type === import_types25.EdgeType.CONTAINS) return out;
7005
+ if (bucket.type === import_types26.EdgeType.CONTAINS) return out;
6790
7006
  if (bucket.extracted && !bucket.observed && OBSERVABLE_EDGE_TYPES.has(bucket.type)) {
6791
7007
  if (!nodeIsFrontier(graph, bucket.target)) {
6792
7008
  out.push({
@@ -6801,7 +7017,7 @@ function detectMissingDivergences(graph, bucket) {
6801
7017
  });
6802
7018
  }
6803
7019
  }
6804
- if (bucket.observed && !bucket.extracted) {
7020
+ if (bucket.observed && !bucket.extracted && !nodeIsWebsocketChannel(graph, bucket.target)) {
6805
7021
  out.push({
6806
7022
  type: "missing-extracted",
6807
7023
  source: bucket.source,
@@ -6827,7 +7043,7 @@ function declaredHostFor(svc) {
6827
7043
  function hasExtractedConfiguredBy(graph, svcId) {
6828
7044
  for (const edgeId of graph.outboundEdges(svcId)) {
6829
7045
  const e = graph.getEdgeAttributes(edgeId);
6830
- if (e.type === import_types25.EdgeType.CONFIGURED_BY && e.provenance === import_types25.Provenance.EXTRACTED) {
7046
+ if (e.type === import_types26.EdgeType.CONFIGURED_BY && e.provenance === import_types26.Provenance.EXTRACTED) {
6831
7047
  return true;
6832
7048
  }
6833
7049
  }
@@ -6840,10 +7056,10 @@ function detectHostMismatch(graph, svcId, svc) {
6840
7056
  const out = [];
6841
7057
  for (const edgeId of graph.outboundEdges(svcId)) {
6842
7058
  const edge = graph.getEdgeAttributes(edgeId);
6843
- if (edge.type !== import_types25.EdgeType.CONNECTS_TO) continue;
6844
- if (edge.provenance !== import_types25.Provenance.OBSERVED) continue;
7059
+ if (edge.type !== import_types26.EdgeType.CONNECTS_TO) continue;
7060
+ if (edge.provenance !== import_types26.Provenance.OBSERVED) continue;
6845
7061
  const target = graph.getNodeAttributes(edge.target);
6846
- if (target.type !== import_types25.NodeType.DatabaseNode) continue;
7062
+ if (target.type !== import_types26.NodeType.DatabaseNode) continue;
6847
7063
  const observedHost = target.host?.trim();
6848
7064
  if (!observedHost) continue;
6849
7065
  if (observedHost === declaredHost) continue;
@@ -6865,10 +7081,10 @@ function detectCompatDivergences(graph, svcId, svc) {
6865
7081
  const deps = svc.dependencies ?? {};
6866
7082
  for (const edgeId of graph.outboundEdges(svcId)) {
6867
7083
  const edge = graph.getEdgeAttributes(edgeId);
6868
- if (edge.type !== import_types25.EdgeType.CONNECTS_TO) continue;
6869
- if (edge.provenance !== import_types25.Provenance.OBSERVED) continue;
7084
+ if (edge.type !== import_types26.EdgeType.CONNECTS_TO) continue;
7085
+ if (edge.provenance !== import_types26.Provenance.OBSERVED) continue;
6870
7086
  const target = graph.getNodeAttributes(edge.target);
6871
- if (target.type !== import_types25.NodeType.DatabaseNode) continue;
7087
+ if (target.type !== import_types26.NodeType.DatabaseNode) continue;
6872
7088
  for (const pair of compatPairs()) {
6873
7089
  if (pair.engine !== target.engine) continue;
6874
7090
  const declared = deps[pair.driver];
@@ -6927,7 +7143,7 @@ function suppressHostMismatchHalves(all) {
6927
7143
  for (const d of all) {
6928
7144
  if (d.type !== "host-mismatch") continue;
6929
7145
  observedHalf.add(`${d.source}->${d.target}`);
6930
- declaredHalf.add((0, import_types25.databaseId)(d.extractedHost));
7146
+ declaredHalf.add((0, import_types26.databaseId)(d.extractedHost));
6931
7147
  }
6932
7148
  if (observedHalf.size === 0) return all;
6933
7149
  return all.filter((d) => {
@@ -6946,7 +7162,7 @@ function computeDivergences(graph, opts = {}) {
6946
7162
  }
6947
7163
  graph.forEachNode((nodeId, attrs) => {
6948
7164
  const n = attrs;
6949
- if (n.type !== import_types25.NodeType.ServiceNode) return;
7165
+ if (n.type !== import_types26.NodeType.ServiceNode) return;
6950
7166
  const svc = n;
6951
7167
  for (const d of detectHostMismatch(graph, nodeId, svc)) all.push(d);
6952
7168
  for (const d of detectCompatDivergences(graph, nodeId, svc)) all.push(d);
@@ -6980,7 +7196,7 @@ function computeDivergences(graph, opts = {}) {
6980
7196
  if (a.source !== b.source) return a.source.localeCompare(b.source);
6981
7197
  return a.target.localeCompare(b.target);
6982
7198
  });
6983
- return import_types25.DivergenceResultSchema.parse({
7199
+ return import_types26.DivergenceResultSchema.parse({
6984
7200
  divergences: filtered,
6985
7201
  totalAffected: filtered.length,
6986
7202
  computedAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -6989,9 +7205,9 @@ function computeDivergences(graph, opts = {}) {
6989
7205
 
6990
7206
  // src/persist.ts
6991
7207
  init_cjs_shims();
6992
- var import_node_fs20 = require("fs");
6993
- var import_node_path37 = __toESM(require("path"), 1);
6994
- var import_types26 = require("@neat.is/types");
7208
+ var import_node_fs21 = require("fs");
7209
+ var import_node_path38 = __toESM(require("path"), 1);
7210
+ var import_types27 = require("@neat.is/types");
6995
7211
  var SCHEMA_VERSION = 4;
6996
7212
  function migrateV1ToV2(payload) {
6997
7213
  const nodes = payload.graph.nodes;
@@ -7013,12 +7229,12 @@ function migrateV2ToV3(payload) {
7013
7229
  for (const edge of edges) {
7014
7230
  const attrs = edge.attributes;
7015
7231
  if (!attrs || attrs.provenance !== "FRONTIER") continue;
7016
- attrs.provenance = import_types26.Provenance.OBSERVED;
7232
+ attrs.provenance = import_types27.Provenance.OBSERVED;
7017
7233
  const type = typeof attrs.type === "string" ? attrs.type : void 0;
7018
7234
  const source = typeof attrs.source === "string" ? attrs.source : void 0;
7019
7235
  const target = typeof attrs.target === "string" ? attrs.target : void 0;
7020
7236
  if (type && source && target) {
7021
- const newId = (0, import_types26.observedEdgeId)(source, target, type);
7237
+ const newId = (0, import_types27.observedEdgeId)(source, target, type);
7022
7238
  attrs.id = newId;
7023
7239
  if (edge.key) edge.key = newId;
7024
7240
  }
@@ -7027,7 +7243,7 @@ function migrateV2ToV3(payload) {
7027
7243
  return { ...payload, schemaVersion: 3 };
7028
7244
  }
7029
7245
  async function ensureDir(filePath) {
7030
- await import_node_fs20.promises.mkdir(import_node_path37.default.dirname(filePath), { recursive: true });
7246
+ await import_node_fs21.promises.mkdir(import_node_path38.default.dirname(filePath), { recursive: true });
7031
7247
  }
7032
7248
  async function saveGraphToDisk(graph, outPath) {
7033
7249
  await ensureDir(outPath);
@@ -7037,13 +7253,13 @@ async function saveGraphToDisk(graph, outPath) {
7037
7253
  graph: graph.export()
7038
7254
  };
7039
7255
  const tmp = `${outPath}.tmp`;
7040
- await import_node_fs20.promises.writeFile(tmp, JSON.stringify(payload), "utf8");
7041
- await import_node_fs20.promises.rename(tmp, outPath);
7256
+ await import_node_fs21.promises.writeFile(tmp, JSON.stringify(payload), "utf8");
7257
+ await import_node_fs21.promises.rename(tmp, outPath);
7042
7258
  }
7043
7259
  async function loadGraphFromDisk(graph, outPath) {
7044
7260
  let raw;
7045
7261
  try {
7046
- raw = await import_node_fs20.promises.readFile(outPath, "utf8");
7262
+ raw = await import_node_fs21.promises.readFile(outPath, "utf8");
7047
7263
  } catch (err) {
7048
7264
  if (err.code === "ENOENT") return;
7049
7265
  throw err;
@@ -7108,8 +7324,8 @@ function startPersistLoop(graph, outPath, opts = {}) {
7108
7324
 
7109
7325
  // src/gitignore.ts
7110
7326
  init_cjs_shims();
7111
- var import_node_fs21 = require("fs");
7112
- var import_node_path38 = __toESM(require("path"), 1);
7327
+ var import_node_fs22 = require("fs");
7328
+ var import_node_path39 = __toESM(require("path"), 1);
7113
7329
  var NEAT_OUT_LINE = "neat-out/";
7114
7330
  var NEAT_HEADER = "# NEAT \u2014 machine-local snapshots and events";
7115
7331
  function isNeatOutLine(line) {
@@ -7117,15 +7333,15 @@ function isNeatOutLine(line) {
7117
7333
  return trimmed === "neat-out/" || trimmed === "neat-out";
7118
7334
  }
7119
7335
  async function ensureNeatOutIgnored(projectDir) {
7120
- const file = import_node_path38.default.join(projectDir, ".gitignore");
7336
+ const file = import_node_path39.default.join(projectDir, ".gitignore");
7121
7337
  let existing = null;
7122
7338
  try {
7123
- existing = await import_node_fs21.promises.readFile(file, "utf8");
7339
+ existing = await import_node_fs22.promises.readFile(file, "utf8");
7124
7340
  } catch (err) {
7125
7341
  if (err.code !== "ENOENT") throw err;
7126
7342
  }
7127
7343
  if (existing === null) {
7128
- await import_node_fs21.promises.writeFile(file, `${NEAT_HEADER}
7344
+ await import_node_fs22.promises.writeFile(file, `${NEAT_HEADER}
7129
7345
  ${NEAT_OUT_LINE}
7130
7346
  `, "utf8");
7131
7347
  return { action: "created", file };
@@ -7138,13 +7354,13 @@ ${NEAT_OUT_LINE}
7138
7354
  ${NEAT_HEADER}
7139
7355
  ${NEAT_OUT_LINE}
7140
7356
  `;
7141
- await import_node_fs21.promises.writeFile(file, existing + appended, "utf8");
7357
+ await import_node_fs22.promises.writeFile(file, existing + appended, "utf8");
7142
7358
  return { action: "added", file };
7143
7359
  }
7144
7360
 
7145
7361
  // src/summary.ts
7146
7362
  init_cjs_shims();
7147
- var import_types27 = require("@neat.is/types");
7363
+ var import_types28 = require("@neat.is/types");
7148
7364
  function renderOtelEnvBlock() {
7149
7365
  return [
7150
7366
  "for prod OTel routing, set these in your deploy platform's env:",
@@ -7154,19 +7370,19 @@ function renderOtelEnvBlock() {
7154
7370
  }
7155
7371
  function findIncompatServices(nodes) {
7156
7372
  return nodes.filter(
7157
- (n) => n.type === import_types27.NodeType.ServiceNode && Array.isArray(n.incompatibilities) && (n.incompatibilities ?? []).length > 0
7373
+ (n) => n.type === import_types28.NodeType.ServiceNode && Array.isArray(n.incompatibilities) && (n.incompatibilities ?? []).length > 0
7158
7374
  );
7159
7375
  }
7160
7376
  function servicesWithoutObserved(nodes, edges) {
7161
7377
  const seen = /* @__PURE__ */ new Set();
7162
7378
  for (const e of edges) {
7163
- if (e.provenance === import_types27.Provenance.OBSERVED) {
7379
+ if (e.provenance === import_types28.Provenance.OBSERVED) {
7164
7380
  seen.add(e.source);
7165
7381
  seen.add(e.target);
7166
7382
  }
7167
7383
  }
7168
7384
  return nodes.filter(
7169
- (n) => n.type === import_types27.NodeType.ServiceNode && !seen.has(n.id)
7385
+ (n) => n.type === import_types28.NodeType.ServiceNode && !seen.has(n.id)
7170
7386
  );
7171
7387
  }
7172
7388
  function formatDivergence(d) {
@@ -7240,27 +7456,27 @@ function formatIncompat(inc) {
7240
7456
 
7241
7457
  // src/watch.ts
7242
7458
  init_cjs_shims();
7243
- var import_node_fs29 = __toESM(require("fs"), 1);
7244
- var import_node_path48 = __toESM(require("path"), 1);
7459
+ var import_node_fs30 = __toESM(require("fs"), 1);
7460
+ var import_node_path49 = __toESM(require("path"), 1);
7245
7461
  var import_chokidar = __toESM(require("chokidar"), 1);
7246
7462
 
7247
7463
  // src/api.ts
7248
7464
  init_cjs_shims();
7249
7465
  var import_fastify = __toESM(require("fastify"), 1);
7250
7466
  var import_cors = __toESM(require("@fastify/cors"), 1);
7251
- var import_types29 = require("@neat.is/types");
7467
+ var import_types30 = require("@neat.is/types");
7252
7468
 
7253
7469
  // src/extend/index.ts
7254
7470
  init_cjs_shims();
7255
- var import_node_fs23 = require("fs");
7256
- var import_node_path40 = __toESM(require("path"), 1);
7471
+ var import_node_fs24 = require("fs");
7472
+ var import_node_path41 = __toESM(require("path"), 1);
7257
7473
  var import_node_os2 = __toESM(require("os"), 1);
7258
7474
  var import_instrumentation_registry = require("@neat.is/instrumentation-registry");
7259
7475
 
7260
7476
  // src/installers/package-manager.ts
7261
7477
  init_cjs_shims();
7262
- var import_node_fs22 = require("fs");
7263
- var import_node_path39 = __toESM(require("path"), 1);
7478
+ var import_node_fs23 = require("fs");
7479
+ var import_node_path40 = __toESM(require("path"), 1);
7264
7480
  var import_node_child_process = require("child_process");
7265
7481
  var LOCKFILE_PRIORITY = [
7266
7482
  { lockfile: "bun.lockb", pm: "bun", args: ["install", "--no-summary"] },
@@ -7275,29 +7491,29 @@ var LOCKFILE_PRIORITY = [
7275
7491
  var NPM_FALLBACK_ARGS = ["install", "--no-audit", "--no-fund", "--prefer-offline"];
7276
7492
  async function exists2(p) {
7277
7493
  try {
7278
- await import_node_fs22.promises.access(p);
7494
+ await import_node_fs23.promises.access(p);
7279
7495
  return true;
7280
7496
  } catch {
7281
7497
  return false;
7282
7498
  }
7283
7499
  }
7284
7500
  async function detectPackageManager(serviceDir) {
7285
- let dir = import_node_path39.default.resolve(serviceDir);
7501
+ let dir = import_node_path40.default.resolve(serviceDir);
7286
7502
  const stops = /* @__PURE__ */ new Set();
7287
7503
  for (let i = 0; i < 64; i++) {
7288
7504
  if (stops.has(dir)) break;
7289
7505
  stops.add(dir);
7290
7506
  for (const candidate of LOCKFILE_PRIORITY) {
7291
- const lockPath = import_node_path39.default.join(dir, candidate.lockfile);
7507
+ const lockPath = import_node_path40.default.join(dir, candidate.lockfile);
7292
7508
  if (await exists2(lockPath)) {
7293
7509
  return { pm: candidate.pm, cwd: dir, args: [...candidate.args] };
7294
7510
  }
7295
7511
  }
7296
- const parent = import_node_path39.default.dirname(dir);
7512
+ const parent = import_node_path40.default.dirname(dir);
7297
7513
  if (parent === dir) break;
7298
7514
  dir = parent;
7299
7515
  }
7300
- return { pm: "npm", cwd: import_node_path39.default.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
7516
+ return { pm: "npm", cwd: import_node_path40.default.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
7301
7517
  }
7302
7518
  async function runPackageManagerInstall(cmd) {
7303
7519
  return new Promise((resolve) => {
@@ -7339,30 +7555,30 @@ ${err.message}`
7339
7555
  // src/extend/index.ts
7340
7556
  async function fileExists2(p) {
7341
7557
  try {
7342
- await import_node_fs23.promises.access(p);
7558
+ await import_node_fs24.promises.access(p);
7343
7559
  return true;
7344
7560
  } catch {
7345
7561
  return false;
7346
7562
  }
7347
7563
  }
7348
7564
  async function readPackageJson(scanPath) {
7349
- const pkgPath = import_node_path40.default.join(scanPath, "package.json");
7350
- const raw = await import_node_fs23.promises.readFile(pkgPath, "utf8");
7565
+ const pkgPath = import_node_path41.default.join(scanPath, "package.json");
7566
+ const raw = await import_node_fs24.promises.readFile(pkgPath, "utf8");
7351
7567
  return JSON.parse(raw);
7352
7568
  }
7353
7569
  async function findHookFiles(scanPath) {
7354
- const entries = await import_node_fs23.promises.readdir(scanPath);
7570
+ const entries = await import_node_fs24.promises.readdir(scanPath);
7355
7571
  return entries.filter(
7356
7572
  (e) => (e.startsWith("instrumentation") || e.startsWith("otel-init")) && /\.(ts|js)$/.test(e)
7357
7573
  ).sort();
7358
7574
  }
7359
7575
  function extendLogPath() {
7360
- return process.env.NEAT_EXTEND_LOG ?? import_node_path40.default.join(import_node_os2.default.homedir(), ".neat", "extend-log.ndjson");
7576
+ return process.env.NEAT_EXTEND_LOG ?? import_node_path41.default.join(import_node_os2.default.homedir(), ".neat", "extend-log.ndjson");
7361
7577
  }
7362
7578
  async function appendExtendLog(entry2) {
7363
7579
  const logPath = extendLogPath();
7364
- await import_node_fs23.promises.mkdir(import_node_path40.default.dirname(logPath), { recursive: true });
7365
- await import_node_fs23.promises.appendFile(logPath, JSON.stringify(entry2) + "\n", "utf8");
7580
+ await import_node_fs24.promises.mkdir(import_node_path41.default.dirname(logPath), { recursive: true });
7581
+ await import_node_fs24.promises.appendFile(logPath, JSON.stringify(entry2) + "\n", "utf8");
7366
7582
  }
7367
7583
  function splicedContent(fileContent, snippet2) {
7368
7584
  if (fileContent.includes("__INSTRUMENTATION_BLOCK__")) {
@@ -7420,7 +7636,7 @@ function lookupInstrumentation(library, installedVersion) {
7420
7636
  }
7421
7637
  async function describeProjectInstrumentation(ctx) {
7422
7638
  const hookFiles = await findHookFiles(ctx.scanPath);
7423
- const envNeat = await fileExists2(import_node_path40.default.join(ctx.scanPath, ".env.neat"));
7639
+ const envNeat = await fileExists2(import_node_path41.default.join(ctx.scanPath, ".env.neat"));
7424
7640
  const registryInstrPackages = new Set(
7425
7641
  (0, import_instrumentation_registry.list)().map((e) => e.instrumentation_package).filter((p) => !!p)
7426
7642
  );
@@ -7442,31 +7658,31 @@ async function applyExtension(ctx, args, options) {
7442
7658
  );
7443
7659
  }
7444
7660
  for (const file of hookFiles) {
7445
- const content = await import_node_fs23.promises.readFile(import_node_path40.default.join(ctx.scanPath, file), "utf8");
7661
+ const content = await import_node_fs24.promises.readFile(import_node_path41.default.join(ctx.scanPath, file), "utf8");
7446
7662
  if (content.includes(args.registration_snippet)) {
7447
7663
  return { library: args.library, filesTouched: [], depsAdded: [], installOutput: "", alreadyApplied: true };
7448
7664
  }
7449
7665
  }
7450
7666
  const primaryFile = hookFiles[0];
7451
- const primaryPath = import_node_path40.default.join(ctx.scanPath, primaryFile);
7667
+ const primaryPath = import_node_path41.default.join(ctx.scanPath, primaryFile);
7452
7668
  const filesTouched = [];
7453
7669
  const depsAdded = [];
7454
- const pkgPath = import_node_path40.default.join(ctx.scanPath, "package.json");
7670
+ const pkgPath = import_node_path41.default.join(ctx.scanPath, "package.json");
7455
7671
  const pkg = await readPackageJson(ctx.scanPath);
7456
7672
  if (!(pkg.dependencies ?? {})[args.instrumentation_package]) {
7457
7673
  pkg.dependencies = { ...pkg.dependencies ?? {}, [args.instrumentation_package]: args.version };
7458
- await import_node_fs23.promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
7674
+ await import_node_fs24.promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
7459
7675
  filesTouched.push("package.json");
7460
7676
  depsAdded.push(`${args.instrumentation_package}@${args.version}`);
7461
7677
  }
7462
- const hookContent = await import_node_fs23.promises.readFile(primaryPath, "utf8");
7678
+ const hookContent = await import_node_fs24.promises.readFile(primaryPath, "utf8");
7463
7679
  const patched = splicedContent(hookContent, args.registration_snippet);
7464
7680
  if (!patched) {
7465
7681
  throw new Error(
7466
7682
  `Could not find instrumentation insertion point in ${primaryFile}. Expected __INSTRUMENTATION_BLOCK__, instrumentations.push(, or new NodeSDK(.`
7467
7683
  );
7468
7684
  }
7469
- await import_node_fs23.promises.writeFile(primaryPath, patched, "utf8");
7685
+ await import_node_fs24.promises.writeFile(primaryPath, patched, "utf8");
7470
7686
  filesTouched.push(primaryFile);
7471
7687
  const cmd = await detectPackageManager(ctx.scanPath);
7472
7688
  const installer = options?.runInstall ?? runPackageManagerInstall;
@@ -7497,7 +7713,7 @@ async function dryRunExtension(ctx, args) {
7497
7713
  };
7498
7714
  }
7499
7715
  for (const file of hookFiles) {
7500
- const content = await import_node_fs23.promises.readFile(import_node_path40.default.join(ctx.scanPath, file), "utf8");
7716
+ const content = await import_node_fs24.promises.readFile(import_node_path41.default.join(ctx.scanPath, file), "utf8");
7501
7717
  if (content.includes(args.registration_snippet)) {
7502
7718
  return {
7503
7719
  library: args.library,
@@ -7519,7 +7735,7 @@ async function dryRunExtension(ctx, args) {
7519
7735
  depsToAdd.push(`${args.instrumentation_package}@${args.version}`);
7520
7736
  filesTouched.push("package.json");
7521
7737
  }
7522
- const hookContent = await import_node_fs23.promises.readFile(import_node_path40.default.join(ctx.scanPath, primaryFile), "utf8");
7738
+ const hookContent = await import_node_fs24.promises.readFile(import_node_path41.default.join(ctx.scanPath, primaryFile), "utf8");
7523
7739
  const patched = splicedContent(hookContent, args.registration_snippet);
7524
7740
  if (patched) {
7525
7741
  filesTouched.push(primaryFile);
@@ -7534,28 +7750,28 @@ async function rollbackExtension(ctx, args) {
7534
7750
  if (!await fileExists2(logPath)) {
7535
7751
  return { undone: false, message: "no apply found for library" };
7536
7752
  }
7537
- const raw = await import_node_fs23.promises.readFile(logPath, "utf8");
7753
+ const raw = await import_node_fs24.promises.readFile(logPath, "utf8");
7538
7754
  const entries = raw.trim().split("\n").filter(Boolean).map((line) => JSON.parse(line));
7539
7755
  const match = [...entries].reverse().find((e) => e.project === ctx.project && e.library === args.library);
7540
7756
  if (!match) {
7541
7757
  return { undone: false, message: "no apply found for library" };
7542
7758
  }
7543
- const pkgPath = import_node_path40.default.join(ctx.scanPath, "package.json");
7759
+ const pkgPath = import_node_path41.default.join(ctx.scanPath, "package.json");
7544
7760
  if (await fileExists2(pkgPath)) {
7545
7761
  const pkg = await readPackageJson(ctx.scanPath);
7546
7762
  if (pkg.dependencies?.[match.instrumentation_package]) {
7547
7763
  const { [match.instrumentation_package]: _removed, ...rest } = pkg.dependencies;
7548
7764
  pkg.dependencies = rest;
7549
- await import_node_fs23.promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
7765
+ await import_node_fs24.promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
7550
7766
  }
7551
7767
  }
7552
7768
  const hookFiles = await findHookFiles(ctx.scanPath);
7553
7769
  for (const file of hookFiles) {
7554
- const filePath = import_node_path40.default.join(ctx.scanPath, file);
7555
- const content = await import_node_fs23.promises.readFile(filePath, "utf8");
7770
+ const filePath = import_node_path41.default.join(ctx.scanPath, file);
7771
+ const content = await import_node_fs24.promises.readFile(filePath, "utf8");
7556
7772
  if (content.includes(match.registration_snippet)) {
7557
7773
  const filtered = content.split("\n").filter((line) => !line.includes(match.registration_snippet)).join("\n");
7558
- await import_node_fs23.promises.writeFile(filePath, filtered, "utf8");
7774
+ await import_node_fs24.promises.writeFile(filePath, filtered, "utf8");
7559
7775
  break;
7560
7776
  }
7561
7777
  }
@@ -7567,7 +7783,7 @@ async function rollbackExtension(ctx, args) {
7567
7783
 
7568
7784
  // src/diff.ts
7569
7785
  init_cjs_shims();
7570
- var import_node_fs24 = require("fs");
7786
+ var import_node_fs25 = require("fs");
7571
7787
  async function loadSnapshotForDiff(target) {
7572
7788
  if (/^https?:\/\//i.test(target)) {
7573
7789
  const res = await fetch(target);
@@ -7576,7 +7792,7 @@ async function loadSnapshotForDiff(target) {
7576
7792
  }
7577
7793
  return await res.json();
7578
7794
  }
7579
- const raw = await import_node_fs24.promises.readFile(target, "utf8");
7795
+ const raw = await import_node_fs25.promises.readFile(target, "utf8");
7580
7796
  return JSON.parse(raw);
7581
7797
  }
7582
7798
  function indexEntries(entries) {
@@ -7644,23 +7860,23 @@ function canonicalJson(value) {
7644
7860
 
7645
7861
  // src/projects.ts
7646
7862
  init_cjs_shims();
7647
- var import_node_path41 = __toESM(require("path"), 1);
7863
+ var import_node_path42 = __toESM(require("path"), 1);
7648
7864
  function pathsForProject(project, baseDir) {
7649
7865
  if (project === DEFAULT_PROJECT) {
7650
7866
  return {
7651
- snapshotPath: import_node_path41.default.join(baseDir, "graph.json"),
7652
- errorsPath: import_node_path41.default.join(baseDir, "errors.ndjson"),
7653
- staleEventsPath: import_node_path41.default.join(baseDir, "stale-events.ndjson"),
7654
- embeddingsCachePath: import_node_path41.default.join(baseDir, "embeddings.json"),
7655
- policyViolationsPath: import_node_path41.default.join(baseDir, "policy-violations.ndjson")
7867
+ snapshotPath: import_node_path42.default.join(baseDir, "graph.json"),
7868
+ errorsPath: import_node_path42.default.join(baseDir, "errors.ndjson"),
7869
+ staleEventsPath: import_node_path42.default.join(baseDir, "stale-events.ndjson"),
7870
+ embeddingsCachePath: import_node_path42.default.join(baseDir, "embeddings.json"),
7871
+ policyViolationsPath: import_node_path42.default.join(baseDir, "policy-violations.ndjson")
7656
7872
  };
7657
7873
  }
7658
7874
  return {
7659
- snapshotPath: import_node_path41.default.join(baseDir, `${project}.json`),
7660
- errorsPath: import_node_path41.default.join(baseDir, `errors.${project}.ndjson`),
7661
- staleEventsPath: import_node_path41.default.join(baseDir, `stale-events.${project}.ndjson`),
7662
- embeddingsCachePath: import_node_path41.default.join(baseDir, `embeddings.${project}.json`),
7663
- policyViolationsPath: import_node_path41.default.join(baseDir, `policy-violations.${project}.ndjson`)
7875
+ snapshotPath: import_node_path42.default.join(baseDir, `${project}.json`),
7876
+ errorsPath: import_node_path42.default.join(baseDir, `errors.${project}.ndjson`),
7877
+ staleEventsPath: import_node_path42.default.join(baseDir, `stale-events.${project}.ndjson`),
7878
+ embeddingsCachePath: import_node_path42.default.join(baseDir, `embeddings.${project}.json`),
7879
+ policyViolationsPath: import_node_path42.default.join(baseDir, `policy-violations.${project}.ndjson`)
7664
7880
  };
7665
7881
  }
7666
7882
  var Projects = class {
@@ -7696,28 +7912,28 @@ var Projects = class {
7696
7912
 
7697
7913
  // src/registry.ts
7698
7914
  init_cjs_shims();
7699
- var import_node_fs25 = require("fs");
7915
+ var import_node_fs26 = require("fs");
7700
7916
  var import_node_os3 = __toESM(require("os"), 1);
7701
- var import_node_path42 = __toESM(require("path"), 1);
7702
- var import_types28 = require("@neat.is/types");
7917
+ var import_node_path43 = __toESM(require("path"), 1);
7918
+ var import_types29 = require("@neat.is/types");
7703
7919
  var LOCK_TIMEOUT_MS = 5e3;
7704
7920
  var LOCK_RETRY_MS = 50;
7705
7921
  function neatHome() {
7706
7922
  const override = process.env.NEAT_HOME;
7707
- if (override && override.length > 0) return import_node_path42.default.resolve(override);
7708
- return import_node_path42.default.join(import_node_os3.default.homedir(), ".neat");
7923
+ if (override && override.length > 0) return import_node_path43.default.resolve(override);
7924
+ return import_node_path43.default.join(import_node_os3.default.homedir(), ".neat");
7709
7925
  }
7710
7926
  function registryPath() {
7711
- return import_node_path42.default.join(neatHome(), "projects.json");
7927
+ return import_node_path43.default.join(neatHome(), "projects.json");
7712
7928
  }
7713
7929
  function registryLockPath() {
7714
- return import_node_path42.default.join(neatHome(), "projects.json.lock");
7930
+ return import_node_path43.default.join(neatHome(), "projects.json.lock");
7715
7931
  }
7716
7932
  function daemonPidPath() {
7717
- return import_node_path42.default.join(neatHome(), "neatd.pid");
7933
+ return import_node_path43.default.join(neatHome(), "neatd.pid");
7718
7934
  }
7719
7935
  function daemonsDir() {
7720
- return import_node_path42.default.join(neatHome(), "daemons");
7936
+ return import_node_path43.default.join(neatHome(), "daemons");
7721
7937
  }
7722
7938
  function isFiniteInt(v) {
7723
7939
  return typeof v === "number" && Number.isFinite(v);
@@ -7750,7 +7966,7 @@ async function discoverDaemons(probe = defaultDiscoveryProbe) {
7750
7966
  const dir = daemonsDir();
7751
7967
  let names;
7752
7968
  try {
7753
- names = await import_node_fs25.promises.readdir(dir);
7969
+ names = await import_node_fs26.promises.readdir(dir);
7754
7970
  } catch (err) {
7755
7971
  if (err.code === "ENOENT") return [];
7756
7972
  throw err;
@@ -7758,10 +7974,10 @@ async function discoverDaemons(probe = defaultDiscoveryProbe) {
7758
7974
  const out = [];
7759
7975
  for (const name of names) {
7760
7976
  if (!name.endsWith(".json")) continue;
7761
- const file = import_node_path42.default.join(dir, name);
7977
+ const file = import_node_path43.default.join(dir, name);
7762
7978
  let raw;
7763
7979
  try {
7764
- raw = await import_node_fs25.promises.readFile(file, "utf8");
7980
+ raw = await import_node_fs26.promises.readFile(file, "utf8");
7765
7981
  } catch {
7766
7982
  continue;
7767
7983
  }
@@ -7774,7 +7990,7 @@ async function discoverDaemons(probe = defaultDiscoveryProbe) {
7774
7990
  return out;
7775
7991
  }
7776
7992
  async function removeDaemonRecord(source) {
7777
- await import_node_fs25.promises.unlink(source).catch(() => {
7993
+ await import_node_fs26.promises.unlink(source).catch(() => {
7778
7994
  });
7779
7995
  }
7780
7996
  async function listMachineProjects(probe = defaultDiscoveryProbe) {
@@ -7831,7 +8047,7 @@ function isPidAliveDefault(pid) {
7831
8047
  }
7832
8048
  async function readPidFile(file) {
7833
8049
  try {
7834
- const raw = await import_node_fs25.promises.readFile(file, "utf8");
8050
+ const raw = await import_node_fs26.promises.readFile(file, "utf8");
7835
8051
  const pid = Number.parseInt(raw.trim(), 10);
7836
8052
  return Number.isInteger(pid) && pid > 0 ? pid : void 0;
7837
8053
  } catch {
@@ -7879,32 +8095,32 @@ function lockHolderMessage(holder, lockPath, timeoutMs) {
7879
8095
  }
7880
8096
  }
7881
8097
  async function normalizeProjectPath(input) {
7882
- const resolved = import_node_path42.default.resolve(input);
8098
+ const resolved = import_node_path43.default.resolve(input);
7883
8099
  try {
7884
- return await import_node_fs25.promises.realpath(resolved);
8100
+ return await import_node_fs26.promises.realpath(resolved);
7885
8101
  } catch {
7886
8102
  return resolved;
7887
8103
  }
7888
8104
  }
7889
8105
  async function writeAtomically(target, contents) {
7890
- await import_node_fs25.promises.mkdir(import_node_path42.default.dirname(target), { recursive: true });
8106
+ await import_node_fs26.promises.mkdir(import_node_path43.default.dirname(target), { recursive: true });
7891
8107
  const tmp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
7892
- const fd = await import_node_fs25.promises.open(tmp, "w");
8108
+ const fd = await import_node_fs26.promises.open(tmp, "w");
7893
8109
  try {
7894
8110
  await fd.writeFile(contents, "utf8");
7895
8111
  await fd.sync();
7896
8112
  } finally {
7897
8113
  await fd.close();
7898
8114
  }
7899
- await import_node_fs25.promises.rename(tmp, target);
8115
+ await import_node_fs26.promises.rename(tmp, target);
7900
8116
  }
7901
8117
  async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaultLockHolderProbe) {
7902
8118
  const deadline = Date.now() + timeoutMs;
7903
- await import_node_fs25.promises.mkdir(import_node_path42.default.dirname(lockPath), { recursive: true });
8119
+ await import_node_fs26.promises.mkdir(import_node_path43.default.dirname(lockPath), { recursive: true });
7904
8120
  let probedHolder = false;
7905
8121
  while (true) {
7906
8122
  try {
7907
- const fd = await import_node_fs25.promises.open(lockPath, "wx");
8123
+ const fd = await import_node_fs26.promises.open(lockPath, "wx");
7908
8124
  try {
7909
8125
  await fd.writeFile(`${process.pid}
7910
8126
  `, "utf8");
@@ -7929,7 +8145,7 @@ async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaul
7929
8145
  }
7930
8146
  }
7931
8147
  async function releaseLock(lockPath) {
7932
- await import_node_fs25.promises.unlink(lockPath).catch(() => {
8148
+ await import_node_fs26.promises.unlink(lockPath).catch(() => {
7933
8149
  });
7934
8150
  }
7935
8151
  async function withLock(fn) {
@@ -7945,7 +8161,7 @@ async function readRegistry() {
7945
8161
  const file = registryPath();
7946
8162
  let raw;
7947
8163
  try {
7948
- raw = await import_node_fs25.promises.readFile(file, "utf8");
8164
+ raw = await import_node_fs26.promises.readFile(file, "utf8");
7949
8165
  } catch (err) {
7950
8166
  if (err.code === "ENOENT") {
7951
8167
  return { version: 1, projects: [] };
@@ -7953,10 +8169,10 @@ async function readRegistry() {
7953
8169
  throw err;
7954
8170
  }
7955
8171
  const parsed = JSON.parse(raw);
7956
- return import_types28.RegistryFileSchema.parse(parsed);
8172
+ return import_types29.RegistryFileSchema.parse(parsed);
7957
8173
  }
7958
8174
  async function writeRegistry(reg) {
7959
- const validated = import_types28.RegistryFileSchema.parse(reg);
8175
+ const validated = import_types29.RegistryFileSchema.parse(reg);
7960
8176
  await writeAtomically(registryPath(), JSON.stringify(validated, null, 2) + "\n");
7961
8177
  }
7962
8178
  var ProjectNameCollisionError = class extends Error {
@@ -8041,7 +8257,7 @@ function pruneTtlMs() {
8041
8257
  }
8042
8258
  async function statPathStatus(p) {
8043
8259
  try {
8044
- const stat = await import_node_fs25.promises.stat(p);
8260
+ const stat = await import_node_fs26.promises.stat(p);
8045
8261
  return stat.isDirectory() ? "present" : "unknown";
8046
8262
  } catch (err) {
8047
8263
  return err.code === "ENOENT" ? "gone" : "unknown";
@@ -8288,11 +8504,11 @@ function registerRoutes(scope, ctx) {
8288
8504
  const candidates = req.query.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
8289
8505
  const parsed = [];
8290
8506
  for (const c of candidates) {
8291
- const r = import_types29.DivergenceTypeSchema.safeParse(c);
8507
+ const r = import_types30.DivergenceTypeSchema.safeParse(c);
8292
8508
  if (!r.success) {
8293
8509
  return reply.code(400).send({
8294
8510
  error: `unknown divergence type "${c}"`,
8295
- allowed: import_types29.DivergenceTypeSchema.options
8511
+ allowed: import_types30.DivergenceTypeSchema.options
8296
8512
  });
8297
8513
  }
8298
8514
  parsed.push(r.data);
@@ -8511,7 +8727,7 @@ function registerRoutes(scope, ctx) {
8511
8727
  const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
8512
8728
  let violations = await log.readAll();
8513
8729
  if (req.query.severity) {
8514
- const sev = import_types29.PolicySeveritySchema.safeParse(req.query.severity);
8730
+ const sev = import_types30.PolicySeveritySchema.safeParse(req.query.severity);
8515
8731
  if (!sev.success) {
8516
8732
  return reply.code(400).send({
8517
8733
  error: "invalid severity",
@@ -8550,7 +8766,7 @@ function registerRoutes(scope, ctx) {
8550
8766
  scope.post("/policies/check", async (req, reply) => {
8551
8767
  const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
8552
8768
  if (!proj) return;
8553
- const parsed = import_types29.PoliciesCheckBodySchema.safeParse(req.body ?? {});
8769
+ const parsed = import_types30.PoliciesCheckBodySchema.safeParse(req.body ?? {});
8554
8770
  if (!parsed.success) {
8555
8771
  return reply.code(400).send({
8556
8772
  error: "invalid /policies/check body",
@@ -8812,41 +9028,46 @@ init_otel();
8812
9028
 
8813
9029
  // src/daemon.ts
8814
9030
  init_cjs_shims();
8815
- var import_node_fs27 = require("fs");
8816
- var import_node_path46 = __toESM(require("path"), 1);
9031
+ var import_node_fs28 = require("fs");
9032
+ var import_node_path47 = __toESM(require("path"), 1);
8817
9033
  var import_node_module = require("module");
8818
9034
  init_otel();
9035
+
9036
+ // src/connectors/index.ts
9037
+ init_cjs_shims();
9038
+
9039
+ // src/daemon.ts
8819
9040
  init_auth();
8820
9041
 
8821
9042
  // src/unrouted.ts
8822
9043
  init_cjs_shims();
8823
- var import_node_fs26 = require("fs");
8824
- var import_node_path45 = __toESM(require("path"), 1);
9044
+ var import_node_fs27 = require("fs");
9045
+ var import_node_path46 = __toESM(require("path"), 1);
8825
9046
 
8826
9047
  // src/daemon.ts
8827
- var import_types30 = require("@neat.is/types");
9048
+ var import_types31 = require("@neat.is/types");
8828
9049
  function daemonJsonPath(scanPath) {
8829
- return import_node_path46.default.join(scanPath, "neat-out", "daemon.json");
9050
+ return import_node_path47.default.join(scanPath, "neat-out", "daemon.json");
8830
9051
  }
8831
9052
  function daemonsDiscoveryDir(home) {
8832
9053
  const base = home && home.length > 0 ? home : neatHomeFromEnv();
8833
- return import_node_path46.default.join(base, "daemons");
9054
+ return import_node_path47.default.join(base, "daemons");
8834
9055
  }
8835
9056
  function daemonDiscoveryPath(project, home) {
8836
- return import_node_path46.default.join(daemonsDiscoveryDir(home), `${sanitizeDiscoveryName(project)}.json`);
9057
+ return import_node_path47.default.join(daemonsDiscoveryDir(home), `${sanitizeDiscoveryName(project)}.json`);
8837
9058
  }
8838
9059
  function sanitizeDiscoveryName(project) {
8839
9060
  return project.replace(/[^A-Za-z0-9._-]/g, "_");
8840
9061
  }
8841
9062
  function neatHomeFromEnv() {
8842
9063
  const env = process.env.NEAT_HOME;
8843
- if (env && env.length > 0) return import_node_path46.default.resolve(env);
9064
+ if (env && env.length > 0) return import_node_path47.default.resolve(env);
8844
9065
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
8845
- return import_node_path46.default.join(home, ".neat");
9066
+ return import_node_path47.default.join(home, ".neat");
8846
9067
  }
8847
9068
  async function readDaemonRecord(scanPath) {
8848
9069
  try {
8849
- const raw = await import_node_fs27.promises.readFile(daemonJsonPath(scanPath), "utf8");
9070
+ const raw = await import_node_fs28.promises.readFile(daemonJsonPath(scanPath), "utf8");
8850
9071
  const parsed = JSON.parse(raw);
8851
9072
  if (typeof parsed.project === "string" && parsed.ports && typeof parsed.ports.rest === "number" && typeof parsed.ports.otlp === "number" && typeof parsed.ports.web === "number") {
8852
9073
  return parsed;
@@ -8886,7 +9107,7 @@ async function clearDaemonRecord(record, home) {
8886
9107
  } catch {
8887
9108
  }
8888
9109
  try {
8889
- await import_node_fs27.promises.unlink(daemonDiscoveryPath(record.project, home));
9110
+ await import_node_fs28.promises.unlink(daemonDiscoveryPath(record.project, home));
8890
9111
  } catch {
8891
9112
  }
8892
9113
  }
@@ -8912,8 +9133,8 @@ init_otel_grpc();
8912
9133
 
8913
9134
  // src/search.ts
8914
9135
  init_cjs_shims();
8915
- var import_node_fs28 = require("fs");
8916
- var import_node_path47 = __toESM(require("path"), 1);
9136
+ var import_node_fs29 = require("fs");
9137
+ var import_node_path48 = __toESM(require("path"), 1);
8917
9138
  var import_node_crypto3 = require("crypto");
8918
9139
  var DEFAULT_LIMIT = 10;
8919
9140
  var NOMIC_DIM = 768;
@@ -8960,6 +9181,18 @@ function embedText(node) {
8960
9181
  if (opType) parts.push(`operationType=${opType}`);
8961
9182
  break;
8962
9183
  }
9184
+ case "GrpcMethodNode": {
9185
+ const rpcService = node.rpcService;
9186
+ const rpcMethod = node.rpcMethod;
9187
+ if (rpcService) parts.push(`rpcService=${rpcService}`);
9188
+ if (rpcMethod) parts.push(`rpcMethod=${rpcMethod}`);
9189
+ break;
9190
+ }
9191
+ case "WebSocketChannelNode": {
9192
+ const channel = node.channel;
9193
+ if (channel) parts.push(`channel=${channel}`);
9194
+ break;
9195
+ }
8963
9196
  default:
8964
9197
  break;
8965
9198
  }
@@ -9055,7 +9288,7 @@ async function pickEmbedder() {
9055
9288
  }
9056
9289
  async function readCache(cachePath) {
9057
9290
  try {
9058
- const raw = await import_node_fs28.promises.readFile(cachePath, "utf8");
9291
+ const raw = await import_node_fs29.promises.readFile(cachePath, "utf8");
9059
9292
  const parsed = JSON.parse(raw);
9060
9293
  if (parsed.version !== 1) return null;
9061
9294
  return parsed;
@@ -9064,8 +9297,8 @@ async function readCache(cachePath) {
9064
9297
  }
9065
9298
  }
9066
9299
  async function writeCache(cachePath, cache) {
9067
- await import_node_fs28.promises.mkdir(import_node_path47.default.dirname(cachePath), { recursive: true });
9068
- await import_node_fs28.promises.writeFile(cachePath, JSON.stringify(cache));
9300
+ await import_node_fs29.promises.mkdir(import_node_path48.default.dirname(cachePath), { recursive: true });
9301
+ await import_node_fs29.promises.writeFile(cachePath, JSON.stringify(cache));
9069
9302
  }
9070
9303
  var VectorIndex = class {
9071
9304
  constructor(embedder, cachePath) {
@@ -9222,8 +9455,8 @@ var ALL_PHASES = [
9222
9455
  ];
9223
9456
  function classifyChange(relPath) {
9224
9457
  const phases = /* @__PURE__ */ new Set();
9225
- const base = import_node_path48.default.basename(relPath).toLowerCase();
9226
- const segments = relPath.split(import_node_path48.default.sep).map((s) => s.toLowerCase());
9458
+ const base = import_node_path49.default.basename(relPath).toLowerCase();
9459
+ const segments = relPath.split(import_node_path49.default.sep).map((s) => s.toLowerCase());
9227
9460
  if (base === "package.json" || base === "requirements.txt" || base === "pyproject.toml" || base === "setup.py") {
9228
9461
  phases.add("services");
9229
9462
  phases.add("aliases");
@@ -9344,16 +9577,16 @@ function countWatchableDirs(scanPath, limit) {
9344
9577
  if (count >= limit) return;
9345
9578
  let entries;
9346
9579
  try {
9347
- entries = import_node_fs29.default.readdirSync(dir, { withFileTypes: true });
9580
+ entries = import_node_fs30.default.readdirSync(dir, { withFileTypes: true });
9348
9581
  } catch {
9349
9582
  return;
9350
9583
  }
9351
9584
  for (const e of entries) {
9352
9585
  if (count >= limit) return;
9353
9586
  if (!e.isDirectory()) continue;
9354
- if (IGNORED_WATCH_PATHS.some((re) => re.test(import_node_path48.default.join(dir, e.name) + import_node_path48.default.sep))) continue;
9587
+ if (IGNORED_WATCH_PATHS.some((re) => re.test(import_node_path49.default.join(dir, e.name) + import_node_path49.default.sep))) continue;
9355
9588
  count++;
9356
- if (depth < 2) visit(import_node_path48.default.join(dir, e.name), depth + 1);
9589
+ if (depth < 2) visit(import_node_path49.default.join(dir, e.name), depth + 1);
9357
9590
  }
9358
9591
  };
9359
9592
  visit(scanPath, 0);
@@ -9371,8 +9604,8 @@ async function startWatch(graph, opts) {
9371
9604
  const projectName = opts.project ?? DEFAULT_PROJECT;
9372
9605
  await loadGraphFromDisk(graph, opts.outPath);
9373
9606
  const detachEventBus = attachGraphToEventBus(graph, { project: projectName });
9374
- const policyFilePath = import_node_path48.default.join(opts.scanPath, "policy.json");
9375
- const policyViolationsPath = import_node_path48.default.join(import_node_path48.default.dirname(opts.outPath), "policy-violations.ndjson");
9607
+ const policyFilePath = import_node_path49.default.join(opts.scanPath, "policy.json");
9608
+ const policyViolationsPath = import_node_path49.default.join(import_node_path49.default.dirname(opts.outPath), "policy-violations.ndjson");
9376
9609
  let policies = [];
9377
9610
  try {
9378
9611
  policies = await loadPolicyFile(policyFilePath);
@@ -9423,7 +9656,7 @@ async function startWatch(graph, opts) {
9423
9656
  assertBindAuthority(host, auth.authToken);
9424
9657
  const port = opts.port ?? 8080;
9425
9658
  const otelPort = opts.otelPort ?? 4318;
9426
- const cachePath = opts.embeddingsCachePath ?? import_node_path48.default.join(import_node_path48.default.dirname(opts.outPath), "embeddings.json");
9659
+ const cachePath = opts.embeddingsCachePath ?? import_node_path49.default.join(import_node_path49.default.dirname(opts.outPath), "embeddings.json");
9427
9660
  let searchIndex;
9428
9661
  try {
9429
9662
  searchIndex = await buildSearchIndex(graph, { cachePath });
@@ -9441,7 +9674,7 @@ async function startWatch(graph, opts) {
9441
9674
  // Paths are derived from the explicit options the watch caller passes
9442
9675
  // — pathsForProject is only used to fill in the embeddings/snapshot
9443
9676
  // fields so the registry shape is complete.
9444
- ...pathsForProject(projectName, import_node_path48.default.dirname(opts.outPath)),
9677
+ ...pathsForProject(projectName, import_node_path49.default.dirname(opts.outPath)),
9445
9678
  snapshotPath: opts.outPath,
9446
9679
  errorsPath: opts.errorsPath,
9447
9680
  staleEventsPath: opts.staleEventsPath
@@ -9559,9 +9792,9 @@ async function startWatch(graph, opts) {
9559
9792
  };
9560
9793
  const onPath = (absPath) => {
9561
9794
  if (shouldIgnore(absPath)) return;
9562
- const rel = import_node_path48.default.relative(opts.scanPath, absPath);
9795
+ const rel = import_node_path49.default.relative(opts.scanPath, absPath);
9563
9796
  if (!rel || rel.startsWith("..")) return;
9564
- pendingPaths.add(rel.split(import_node_path48.default.sep).join("/"));
9797
+ pendingPaths.add(rel.split(import_node_path49.default.sep).join("/"));
9565
9798
  const phases = classifyChange(rel);
9566
9799
  if (phases.size === 0) {
9567
9800
  for (const p of ALL_PHASES) pending.add(p);
@@ -9617,8 +9850,8 @@ async function startWatch(graph, opts) {
9617
9850
 
9618
9851
  // src/deploy/detect.ts
9619
9852
  init_cjs_shims();
9620
- var import_node_fs30 = require("fs");
9621
- var import_node_path49 = __toESM(require("path"), 1);
9853
+ var import_node_fs31 = require("fs");
9854
+ var import_node_path50 = __toESM(require("path"), 1);
9622
9855
  var import_node_child_process2 = require("child_process");
9623
9856
  var import_node_crypto4 = require("crypto");
9624
9857
  function generateToken() {
@@ -9718,21 +9951,21 @@ async function runDeploy(opts = {}) {
9718
9951
  const token = generateToken();
9719
9952
  switch (substrate) {
9720
9953
  case "docker-compose": {
9721
- const artifactPath = import_node_path49.default.join(cwd, "docker-compose.neat.yml");
9954
+ const artifactPath = import_node_path50.default.join(cwd, "docker-compose.neat.yml");
9722
9955
  const contents = emitDockerCompose(cwd);
9723
- await import_node_fs30.promises.writeFile(artifactPath, contents, "utf8");
9956
+ await import_node_fs31.promises.writeFile(artifactPath, contents, "utf8");
9724
9957
  return {
9725
9958
  substrate,
9726
9959
  artifactPath,
9727
9960
  token,
9728
9961
  contents,
9729
- startCommand: `NEAT_AUTH_TOKEN=${token} docker compose -f ${import_node_path49.default.basename(artifactPath)} up -d`
9962
+ startCommand: `NEAT_AUTH_TOKEN=${token} docker compose -f ${import_node_path50.default.basename(artifactPath)} up -d`
9730
9963
  };
9731
9964
  }
9732
9965
  case "systemd": {
9733
- const artifactPath = import_node_path49.default.join(cwd, "neat.service");
9966
+ const artifactPath = import_node_path50.default.join(cwd, "neat.service");
9734
9967
  const contents = emitSystemdUnit(cwd);
9735
- await import_node_fs30.promises.writeFile(artifactPath, contents, "utf8");
9968
+ await import_node_fs31.promises.writeFile(artifactPath, contents, "utf8");
9736
9969
  return {
9737
9970
  substrate,
9738
9971
  artifactPath,
@@ -9765,8 +9998,8 @@ init_cjs_shims();
9765
9998
 
9766
9999
  // src/installers/javascript.ts
9767
10000
  init_cjs_shims();
9768
- var import_node_fs31 = require("fs");
9769
- var import_node_path50 = __toESM(require("path"), 1);
10001
+ var import_node_fs32 = require("fs");
10002
+ var import_node_path51 = __toESM(require("path"), 1);
9770
10003
  var import_semver2 = __toESM(require("semver"), 1);
9771
10004
 
9772
10005
  // src/installers/templates.ts
@@ -10191,6 +10424,9 @@ export async function register() {
10191
10424
  if (process.env.NEXT_RUNTIME === 'nodejs') {
10192
10425
  await import('./instrumentation.node')
10193
10426
  }
10427
+ if (process.env.NEXT_RUNTIME === 'edge') {
10428
+ await import('./instrumentation.edge')
10429
+ }
10194
10430
  }
10195
10431
  `;
10196
10432
  var NEXT_INSTRUMENTATION_JS = `${NEXT_INSTRUMENTATION_HEADER}
@@ -10198,6 +10434,9 @@ export async function register() {
10198
10434
  if (process.env.NEXT_RUNTIME === 'nodejs') {
10199
10435
  await import('./instrumentation.node')
10200
10436
  }
10437
+ if (process.env.NEXT_RUNTIME === 'edge') {
10438
+ await import('./instrumentation.edge')
10439
+ }
10201
10440
  }
10202
10441
  `;
10203
10442
  var NEXT_INSTRUMENTATION_NODE_TS = `${NEXT_INSTRUMENTATION_HEADER}
@@ -10233,6 +10472,18 @@ ${registrations.join("\n")}
10233
10472
  `;
10234
10473
  return template.replace(/__SERVICE_NAME__/g, serviceName).replace(/__PROJECT__/g, projectName).replace(/__INSTRUMENTATION_BLOCK__\n?/g, block);
10235
10474
  }
10475
+ var NEXT_INSTRUMENTATION_EDGE_HEADER = "// Generated by `neat init --apply` (ADR-126). Next.js edge-runtime instrumentation via @vercel/otel.";
10476
+ var NEXT_INSTRUMENTATION_EDGE_TS = `${NEXT_INSTRUMENTATION_EDGE_HEADER}
10477
+ import { registerOTel } from '@vercel/otel'
10478
+
10479
+ process.env.OTEL_SERVICE_NAME ||= '__SERVICE_NAME__'
10480
+ process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ||= 'http://localhost:4318/v1/traces'
10481
+ ${OTEL_OTLP_PROTOCOL_JS}
10482
+ ${OTEL_OTLP_HEADERS_JS}
10483
+
10484
+ registerOTel({ serviceName: process.env.OTEL_SERVICE_NAME })
10485
+ `;
10486
+ var NEXT_INSTRUMENTATION_EDGE_JS = NEXT_INSTRUMENTATION_EDGE_TS;
10236
10487
  var FRAMEWORK_OTEL_INIT_HEADER = "// Generated by `neat init --apply` (ADR-074). OpenTelemetry SDK hook.";
10237
10488
  var FRAMEWORK_OTEL_INIT_TS_BODY = `${FRAMEWORK_OTEL_INIT_HEADER}
10238
10489
  ${OTEL_ESM_NODE_IMPORTS}
@@ -10333,6 +10584,7 @@ var SDK_PACKAGES = [
10333
10584
  { name: "@opentelemetry/sdk-node", version: "^0.57.0" },
10334
10585
  { name: "@opentelemetry/auto-instrumentations-node", version: "^0.55.0" }
10335
10586
  ];
10587
+ var NEXT_EDGE_PACKAGES = [{ name: "@vercel/otel", version: "^2.1.3" }];
10336
10588
  function getMajor(versionRange) {
10337
10589
  if (!versionRange) return 0;
10338
10590
  const match = versionRange.match(/(\d+)/);
@@ -10362,15 +10614,15 @@ var OTEL_ENV = {
10362
10614
  value: "http://localhost:4318/projects/<project>/v1/traces"
10363
10615
  };
10364
10616
  function serviceNodeName(pkg, serviceDir) {
10365
- return pkg.name ?? import_node_path50.default.basename(serviceDir);
10617
+ return pkg.name ?? import_node_path51.default.basename(serviceDir);
10366
10618
  }
10367
10619
  function projectToken(pkg, serviceDir, project) {
10368
10620
  if (project && project.length > 0) return project;
10369
- return pkg.name ?? import_node_path50.default.basename(serviceDir);
10621
+ return pkg.name ?? import_node_path51.default.basename(serviceDir);
10370
10622
  }
10371
10623
  async function readJsonFile(p) {
10372
10624
  try {
10373
- const raw = await import_node_fs31.promises.readFile(p, "utf8");
10625
+ const raw = await import_node_fs32.promises.readFile(p, "utf8");
10374
10626
  return JSON.parse(raw);
10375
10627
  } catch {
10376
10628
  return null;
@@ -10379,16 +10631,16 @@ async function readJsonFile(p) {
10379
10631
  async function detectRuntimeKind(pkgRoot, pkg) {
10380
10632
  const deps = allDeps(pkg);
10381
10633
  if ("react-native" in deps || "expo" in deps) return "react-native";
10382
- const appJson = await readJsonFile(import_node_path50.default.join(pkgRoot, "app.json"));
10634
+ const appJson = await readJsonFile(import_node_path51.default.join(pkgRoot, "app.json"));
10383
10635
  if (appJson && typeof appJson === "object" && "expo" in appJson) {
10384
10636
  return "react-native";
10385
10637
  }
10386
- if (await exists3(import_node_path50.default.join(pkgRoot, "vite.config.js")) || await exists3(import_node_path50.default.join(pkgRoot, "vite.config.ts")) || await exists3(import_node_path50.default.join(pkgRoot, "vite.config.mjs")) || "vite" in deps) {
10638
+ if (await exists3(import_node_path51.default.join(pkgRoot, "vite.config.js")) || await exists3(import_node_path51.default.join(pkgRoot, "vite.config.ts")) || await exists3(import_node_path51.default.join(pkgRoot, "vite.config.mjs")) || "vite" in deps) {
10387
10639
  return "browser-bundle";
10388
10640
  }
10389
- if (await exists3(import_node_path50.default.join(pkgRoot, "wrangler.toml"))) return "cloudflare-workers";
10390
- if (await exists3(import_node_path50.default.join(pkgRoot, "bun.lockb"))) return "bun";
10391
- if (await exists3(import_node_path50.default.join(pkgRoot, "deno.json")) || await exists3(import_node_path50.default.join(pkgRoot, "deno.lock"))) {
10641
+ if (await exists3(import_node_path51.default.join(pkgRoot, "wrangler.toml"))) return "cloudflare-workers";
10642
+ if (await exists3(import_node_path51.default.join(pkgRoot, "bun.lockb"))) return "bun";
10643
+ if (await exists3(import_node_path51.default.join(pkgRoot, "deno.json")) || await exists3(import_node_path51.default.join(pkgRoot, "deno.lock"))) {
10392
10644
  return "deno";
10393
10645
  }
10394
10646
  const engines = pkg.engines ?? {};
@@ -10397,7 +10649,7 @@ async function detectRuntimeKind(pkgRoot, pkg) {
10397
10649
  }
10398
10650
  async function readPackageJson2(serviceDir) {
10399
10651
  try {
10400
- const raw = await import_node_fs31.promises.readFile(import_node_path50.default.join(serviceDir, "package.json"), "utf8");
10652
+ const raw = await import_node_fs32.promises.readFile(import_node_path51.default.join(serviceDir, "package.json"), "utf8");
10401
10653
  return JSON.parse(raw);
10402
10654
  } catch {
10403
10655
  return null;
@@ -10405,7 +10657,7 @@ async function readPackageJson2(serviceDir) {
10405
10657
  }
10406
10658
  async function exists3(p) {
10407
10659
  try {
10408
- await import_node_fs31.promises.stat(p);
10660
+ await import_node_fs32.promises.stat(p);
10409
10661
  return true;
10410
10662
  } catch {
10411
10663
  return false;
@@ -10413,7 +10665,7 @@ async function exists3(p) {
10413
10665
  }
10414
10666
  async function readFileMaybe(p) {
10415
10667
  try {
10416
- return await import_node_fs31.promises.readFile(p, "utf8");
10668
+ return await import_node_fs32.promises.readFile(p, "utf8");
10417
10669
  } catch {
10418
10670
  return null;
10419
10671
  }
@@ -10441,7 +10693,7 @@ function needsVersionUpgrade(installed, expected) {
10441
10693
  var NEXT_CONFIG_CANDIDATES = ["next.config.js", "next.config.ts", "next.config.mjs"];
10442
10694
  async function findNextConfig(serviceDir) {
10443
10695
  for (const name of NEXT_CONFIG_CANDIDATES) {
10444
- const candidate = import_node_path50.default.join(serviceDir, name);
10696
+ const candidate = import_node_path51.default.join(serviceDir, name);
10445
10697
  if (await exists3(candidate)) return candidate;
10446
10698
  }
10447
10699
  return null;
@@ -10510,7 +10762,7 @@ function hasRemixDependency(pkg) {
10510
10762
  }
10511
10763
  async function findRemixEntry(serviceDir) {
10512
10764
  for (const rel of REMIX_ENTRY_CANDIDATES) {
10513
- const candidate = import_node_path50.default.join(serviceDir, rel);
10765
+ const candidate = import_node_path51.default.join(serviceDir, rel);
10514
10766
  if (await exists3(candidate)) return candidate;
10515
10767
  }
10516
10768
  return null;
@@ -10522,14 +10774,14 @@ function hasSvelteKitDependency(pkg) {
10522
10774
  }
10523
10775
  async function findSvelteKitHooks(serviceDir) {
10524
10776
  for (const rel of SVELTEKIT_HOOKS_CANDIDATES) {
10525
- const candidate = import_node_path50.default.join(serviceDir, rel);
10777
+ const candidate = import_node_path51.default.join(serviceDir, rel);
10526
10778
  if (await exists3(candidate)) return candidate;
10527
10779
  }
10528
10780
  return null;
10529
10781
  }
10530
10782
  async function findSvelteKitConfig(serviceDir) {
10531
10783
  for (const rel of SVELTEKIT_CONFIG_CANDIDATES) {
10532
- const candidate = import_node_path50.default.join(serviceDir, rel);
10784
+ const candidate = import_node_path51.default.join(serviceDir, rel);
10533
10785
  if (await exists3(candidate)) return candidate;
10534
10786
  }
10535
10787
  return null;
@@ -10540,7 +10792,7 @@ function hasNuxtDependency(pkg) {
10540
10792
  }
10541
10793
  async function findNuxtConfig(serviceDir) {
10542
10794
  for (const name of NUXT_CONFIG_CANDIDATES) {
10543
- const candidate = import_node_path50.default.join(serviceDir, name);
10795
+ const candidate = import_node_path51.default.join(serviceDir, name);
10544
10796
  if (await exists3(candidate)) return candidate;
10545
10797
  }
10546
10798
  return null;
@@ -10551,7 +10803,7 @@ function hasAstroDependency(pkg) {
10551
10803
  }
10552
10804
  async function findAstroConfig(serviceDir) {
10553
10805
  for (const name of ASTRO_CONFIG_CANDIDATES) {
10554
- const candidate = import_node_path50.default.join(serviceDir, name);
10806
+ const candidate = import_node_path51.default.join(serviceDir, name);
10555
10807
  if (await exists3(candidate)) return candidate;
10556
10808
  }
10557
10809
  return null;
@@ -10565,7 +10817,7 @@ function parseNextMajor(range) {
10565
10817
  return Number.isFinite(n) ? n : null;
10566
10818
  }
10567
10819
  async function isTypeScriptProject(serviceDir) {
10568
- return exists3(import_node_path50.default.join(serviceDir, "tsconfig.json"));
10820
+ return exists3(import_node_path51.default.join(serviceDir, "tsconfig.json"));
10569
10821
  }
10570
10822
  var INDEX_EXTENSIONS = [".ts", ".tsx", ".js", ".mjs", ".cjs"];
10571
10823
  var INDEX_CANDIDATES = INDEX_EXTENSIONS.map((ext) => `index${ext}`);
@@ -10614,7 +10866,7 @@ function entryFromScript(script) {
10614
10866
  }
10615
10867
  async function resolveEntry(serviceDir, pkg) {
10616
10868
  if (typeof pkg.main === "string" && pkg.main.length > 0) {
10617
- const candidate = import_node_path50.default.resolve(serviceDir, pkg.main);
10869
+ const candidate = import_node_path51.default.resolve(serviceDir, pkg.main);
10618
10870
  if (await exists3(candidate)) return candidate;
10619
10871
  }
10620
10872
  if (pkg.bin) {
@@ -10628,40 +10880,40 @@ async function resolveEntry(serviceDir, pkg) {
10628
10880
  if (typeof first === "string") binEntry = first;
10629
10881
  }
10630
10882
  if (binEntry) {
10631
- const candidate = import_node_path50.default.resolve(serviceDir, binEntry);
10883
+ const candidate = import_node_path51.default.resolve(serviceDir, binEntry);
10632
10884
  if (await exists3(candidate)) return candidate;
10633
10885
  }
10634
10886
  }
10635
10887
  const startEntry = entryFromScript(pkg.scripts?.start);
10636
10888
  if (startEntry) {
10637
- const candidate = import_node_path50.default.resolve(serviceDir, startEntry);
10889
+ const candidate = import_node_path51.default.resolve(serviceDir, startEntry);
10638
10890
  if (await exists3(candidate)) return candidate;
10639
10891
  }
10640
10892
  const devEntry = entryFromScript(pkg.scripts?.dev);
10641
10893
  if (devEntry) {
10642
- const candidate = import_node_path50.default.resolve(serviceDir, devEntry);
10894
+ const candidate = import_node_path51.default.resolve(serviceDir, devEntry);
10643
10895
  if (await exists3(candidate)) return candidate;
10644
10896
  }
10645
10897
  for (const rel of SRC_INDEX_CANDIDATES) {
10646
- const candidate = import_node_path50.default.join(serviceDir, rel);
10898
+ const candidate = import_node_path51.default.join(serviceDir, rel);
10647
10899
  if (await exists3(candidate)) return candidate;
10648
10900
  }
10649
10901
  for (const rel of SRC_NAMED_CANDIDATES) {
10650
- const candidate = import_node_path50.default.join(serviceDir, rel);
10902
+ const candidate = import_node_path51.default.join(serviceDir, rel);
10651
10903
  if (await exists3(candidate)) return candidate;
10652
10904
  }
10653
10905
  for (const rel of ROOT_NAMED_CANDIDATES) {
10654
- const candidate = import_node_path50.default.join(serviceDir, rel);
10906
+ const candidate = import_node_path51.default.join(serviceDir, rel);
10655
10907
  if (await exists3(candidate)) return candidate;
10656
10908
  }
10657
10909
  for (const name of INDEX_CANDIDATES) {
10658
- const candidate = import_node_path50.default.join(serviceDir, name);
10910
+ const candidate = import_node_path51.default.join(serviceDir, name);
10659
10911
  if (await exists3(candidate)) return candidate;
10660
10912
  }
10661
10913
  return null;
10662
10914
  }
10663
10915
  function dispatchEntry(entryFile, pkg) {
10664
- const ext = import_node_path50.default.extname(entryFile).toLowerCase();
10916
+ const ext = import_node_path51.default.extname(entryFile).toLowerCase();
10665
10917
  if (ext === ".ts" || ext === ".tsx") return "ts";
10666
10918
  if (ext === ".mjs") return "esm";
10667
10919
  if (ext === ".cjs") return "cjs";
@@ -10678,9 +10930,9 @@ function otelInitContents(flavor) {
10678
10930
  return OTEL_INIT_CJS;
10679
10931
  }
10680
10932
  function injectionLine(flavor, entryFile, otelInitFile) {
10681
- let rel = import_node_path50.default.relative(import_node_path50.default.dirname(entryFile), otelInitFile);
10933
+ let rel = import_node_path51.default.relative(import_node_path51.default.dirname(entryFile), otelInitFile);
10682
10934
  if (!rel.startsWith(".")) rel = `./${rel}`;
10683
- rel = rel.split(import_node_path50.default.sep).join("/");
10935
+ rel = rel.split(import_node_path51.default.sep).join("/");
10684
10936
  if (flavor === "cjs") return `require('${rel}')`;
10685
10937
  if (flavor === "esm") return `import '${rel}'`;
10686
10938
  const tsRel = rel.replace(/\.ts$/, "");
@@ -10693,23 +10945,27 @@ function lineIsOtelInjection(line) {
10693
10945
  }
10694
10946
  async function detectsSrcLayout(serviceDir) {
10695
10947
  const [hasSrcApp, hasSrcPages, hasRootApp, hasRootPages] = await Promise.all([
10696
- exists3(import_node_path50.default.join(serviceDir, "src", "app")),
10697
- exists3(import_node_path50.default.join(serviceDir, "src", "pages")),
10698
- exists3(import_node_path50.default.join(serviceDir, "app")),
10699
- exists3(import_node_path50.default.join(serviceDir, "pages"))
10948
+ exists3(import_node_path51.default.join(serviceDir, "src", "app")),
10949
+ exists3(import_node_path51.default.join(serviceDir, "src", "pages")),
10950
+ exists3(import_node_path51.default.join(serviceDir, "app")),
10951
+ exists3(import_node_path51.default.join(serviceDir, "pages"))
10700
10952
  ]);
10701
10953
  return (hasSrcApp || hasSrcPages) && !hasRootApp && !hasRootPages;
10702
10954
  }
10703
10955
  async function planNext(serviceDir, pkg, manifestPath, nextConfigPath, project) {
10704
10956
  const useTs = await isTypeScriptProject(serviceDir);
10705
10957
  const srcLayout = await detectsSrcLayout(serviceDir);
10706
- const baseDir = srcLayout ? import_node_path50.default.join(serviceDir, "src") : serviceDir;
10707
- const instrumentationFile = import_node_path50.default.join(baseDir, useTs ? "instrumentation.ts" : "instrumentation.js");
10708
- const instrumentationNodeFile = import_node_path50.default.join(
10958
+ const baseDir = srcLayout ? import_node_path51.default.join(serviceDir, "src") : serviceDir;
10959
+ const instrumentationFile = import_node_path51.default.join(baseDir, useTs ? "instrumentation.ts" : "instrumentation.js");
10960
+ const instrumentationNodeFile = import_node_path51.default.join(
10709
10961
  baseDir,
10710
10962
  useTs ? "instrumentation.node.ts" : "instrumentation.node.js"
10711
10963
  );
10712
- const envNeatFile = import_node_path50.default.join(baseDir, ".env.neat");
10964
+ const instrumentationEdgeFile = import_node_path51.default.join(
10965
+ baseDir,
10966
+ useTs ? "instrumentation.edge.ts" : "instrumentation.edge.js"
10967
+ );
10968
+ const envNeatFile = import_node_path51.default.join(baseDir, ".env.neat");
10713
10969
  const existingDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
10714
10970
  const dependencyEdits = [];
10715
10971
  for (const sdk of SDK_PACKAGES) {
@@ -10721,6 +10977,15 @@ async function planNext(serviceDir, pkg, manifestPath, nextConfigPath, project)
10721
10977
  }
10722
10978
  dependencyEdits.push({ file: manifestPath, kind: "add", name: sdk.name, version: sdk.version });
10723
10979
  }
10980
+ for (const sdk of NEXT_EDGE_PACKAGES) {
10981
+ if (sdk.name in existingDeps) {
10982
+ if (needsVersionUpgrade(existingDeps[sdk.name], sdk.version)) {
10983
+ dependencyEdits.push({ file: manifestPath, kind: "upgrade", name: sdk.name, version: sdk.version, fromVersion: existingDeps[sdk.name] });
10984
+ }
10985
+ continue;
10986
+ }
10987
+ dependencyEdits.push({ file: manifestPath, kind: "add", name: sdk.name, version: sdk.version });
10988
+ }
10724
10989
  const nonBundled = detectNonBundledInstrumentations(pkg);
10725
10990
  for (const inst of nonBundled) {
10726
10991
  if (inst.pkg in existingDeps) {
@@ -10754,6 +11019,17 @@ async function planNext(serviceDir, pkg, manifestPath, nextConfigPath, project)
10754
11019
  skipIfExists: true
10755
11020
  });
10756
11021
  }
11022
+ if (!await exists3(instrumentationEdgeFile)) {
11023
+ generatedFiles.push({
11024
+ file: instrumentationEdgeFile,
11025
+ contents: renderFrameworkOtelInit(
11026
+ useTs ? NEXT_INSTRUMENTATION_EDGE_TS : NEXT_INSTRUMENTATION_EDGE_JS,
11027
+ svcName,
11028
+ projectName
11029
+ ),
11030
+ skipIfExists: true
11031
+ });
11032
+ }
10757
11033
  if (!await exists3(envNeatFile)) {
10758
11034
  generatedFiles.push({
10759
11035
  file: envNeatFile,
@@ -10766,7 +11042,7 @@ async function planNext(serviceDir, pkg, manifestPath, nextConfigPath, project)
10766
11042
  const nextMajor = parseNextMajor(nextRange);
10767
11043
  if (nextMajor !== null && nextMajor < 15) {
10768
11044
  try {
10769
- const raw = await import_node_fs31.promises.readFile(nextConfigPath, "utf8");
11045
+ const raw = await import_node_fs32.promises.readFile(nextConfigPath, "utf8");
10770
11046
  if (!raw.includes("instrumentationHook")) {
10771
11047
  nextConfigEdit = {
10772
11048
  file: nextConfigPath,
@@ -10814,7 +11090,7 @@ function buildDependencyEdits(pkg, manifestPath) {
10814
11090
  return edits;
10815
11091
  }
10816
11092
  async function queueEnvNeat(serviceDir, pkg, project, generatedFiles) {
10817
- const envNeatFile = import_node_path50.default.join(serviceDir, ".env.neat");
11093
+ const envNeatFile = import_node_path51.default.join(serviceDir, ".env.neat");
10818
11094
  if (!await exists3(envNeatFile)) {
10819
11095
  generatedFiles.push({
10820
11096
  file: envNeatFile,
@@ -10849,7 +11125,7 @@ function fileImportsOtelHook(raw, specifiers) {
10849
11125
  }
10850
11126
  async function planRemix(serviceDir, pkg, manifestPath, entryFile, project) {
10851
11127
  const useTs = await isTypeScriptProject(serviceDir);
10852
- const otelServerFile = import_node_path50.default.join(
11128
+ const otelServerFile = import_node_path51.default.join(
10853
11129
  serviceDir,
10854
11130
  useTs ? "app/otel.server.ts" : "app/otel.server.js"
10855
11131
  );
@@ -10870,7 +11146,7 @@ async function planRemix(serviceDir, pkg, manifestPath, entryFile, project) {
10870
11146
  await queueEnvNeat(serviceDir, pkg, project, generatedFiles);
10871
11147
  const entrypointEdits = [];
10872
11148
  try {
10873
- const raw = await import_node_fs31.promises.readFile(entryFile, "utf8");
11149
+ const raw = await import_node_fs32.promises.readFile(entryFile, "utf8");
10874
11150
  if (!fileImportsOtelHook(raw, ["./otel.server"])) {
10875
11151
  const lines = raw.split(/\r?\n/);
10876
11152
  const firstReal = lines[0]?.startsWith("#!") ? lines[1] ?? "" : lines[0] ?? "";
@@ -10906,11 +11182,11 @@ async function planRemix(serviceDir, pkg, manifestPath, entryFile, project) {
10906
11182
  }
10907
11183
  async function planSvelteKit(serviceDir, pkg, manifestPath, hooksFile, project) {
10908
11184
  const useTs = await isTypeScriptProject(serviceDir);
10909
- const otelInitFile = import_node_path50.default.join(
11185
+ const otelInitFile = import_node_path51.default.join(
10910
11186
  serviceDir,
10911
11187
  useTs ? "src/otel-init.ts" : "src/otel-init.js"
10912
11188
  );
10913
- const resolvedHooksFile = hooksFile ?? import_node_path50.default.join(serviceDir, useTs ? "src/hooks.server.ts" : "src/hooks.server.js");
11189
+ const resolvedHooksFile = hooksFile ?? import_node_path51.default.join(serviceDir, useTs ? "src/hooks.server.ts" : "src/hooks.server.js");
10914
11190
  const dependencyEdits = buildDependencyEdits(pkg, manifestPath);
10915
11191
  const generatedFiles = [];
10916
11192
  const entrypointEdits = [];
@@ -10935,7 +11211,7 @@ async function planSvelteKit(serviceDir, pkg, manifestPath, hooksFile, project)
10935
11211
  });
10936
11212
  } else {
10937
11213
  try {
10938
- const raw = await import_node_fs31.promises.readFile(hooksFile, "utf8");
11214
+ const raw = await import_node_fs32.promises.readFile(hooksFile, "utf8");
10939
11215
  if (!fileImportsOtelHook(raw, ["./otel-init"])) {
10940
11216
  const lines = raw.split(/\r?\n/);
10941
11217
  const firstReal = lines[0]?.startsWith("#!") ? lines[1] ?? "" : lines[0] ?? "";
@@ -10972,11 +11248,11 @@ async function planSvelteKit(serviceDir, pkg, manifestPath, hooksFile, project)
10972
11248
  }
10973
11249
  async function planNuxt(serviceDir, pkg, manifestPath, project) {
10974
11250
  const useTs = await isTypeScriptProject(serviceDir);
10975
- const otelPluginFile = import_node_path50.default.join(
11251
+ const otelPluginFile = import_node_path51.default.join(
10976
11252
  serviceDir,
10977
11253
  useTs ? "server/plugins/otel.ts" : "server/plugins/otel.js"
10978
11254
  );
10979
- const otelInitFile = import_node_path50.default.join(
11255
+ const otelInitFile = import_node_path51.default.join(
10980
11256
  serviceDir,
10981
11257
  useTs ? "server/plugins/otel-init.ts" : "server/plugins/otel-init.js"
10982
11258
  );
@@ -11027,19 +11303,19 @@ async function planNuxt(serviceDir, pkg, manifestPath, project) {
11027
11303
  var ASTRO_MIDDLEWARE_CANDIDATES = ["src/middleware.ts", "src/middleware.js"];
11028
11304
  async function findAstroMiddleware(serviceDir) {
11029
11305
  for (const rel of ASTRO_MIDDLEWARE_CANDIDATES) {
11030
- const candidate = import_node_path50.default.join(serviceDir, rel);
11306
+ const candidate = import_node_path51.default.join(serviceDir, rel);
11031
11307
  if (await exists3(candidate)) return candidate;
11032
11308
  }
11033
11309
  return null;
11034
11310
  }
11035
11311
  async function planAstro(serviceDir, pkg, manifestPath, project) {
11036
11312
  const useTs = await isTypeScriptProject(serviceDir);
11037
- const otelInitFile = import_node_path50.default.join(
11313
+ const otelInitFile = import_node_path51.default.join(
11038
11314
  serviceDir,
11039
11315
  useTs ? "src/otel-init.ts" : "src/otel-init.js"
11040
11316
  );
11041
11317
  const existingMiddleware = await findAstroMiddleware(serviceDir);
11042
- const middlewareFile = existingMiddleware ?? import_node_path50.default.join(serviceDir, useTs ? "src/middleware.ts" : "src/middleware.js");
11318
+ const middlewareFile = existingMiddleware ?? import_node_path51.default.join(serviceDir, useTs ? "src/middleware.ts" : "src/middleware.js");
11043
11319
  const dependencyEdits = buildDependencyEdits(pkg, manifestPath);
11044
11320
  const generatedFiles = [];
11045
11321
  const entrypointEdits = [];
@@ -11064,7 +11340,7 @@ async function planAstro(serviceDir, pkg, manifestPath, project) {
11064
11340
  });
11065
11341
  } else {
11066
11342
  try {
11067
- const raw = await import_node_fs31.promises.readFile(existingMiddleware, "utf8");
11343
+ const raw = await import_node_fs32.promises.readFile(existingMiddleware, "utf8");
11068
11344
  if (!fileImportsOtelHook(raw, ["./otel-init"])) {
11069
11345
  const lines = raw.split(/\r?\n/);
11070
11346
  const firstReal = lines[0]?.startsWith("#!") ? lines[1] ?? "" : lines[0] ?? "";
@@ -11135,7 +11411,7 @@ async function findFrameworkDispatch(serviceDir, pkg, manifestPath, project) {
11135
11411
  }
11136
11412
  async function plan(serviceDir, opts) {
11137
11413
  const pkg = await readPackageJson2(serviceDir);
11138
- const manifestPath = import_node_path50.default.join(serviceDir, "package.json");
11414
+ const manifestPath = import_node_path51.default.join(serviceDir, "package.json");
11139
11415
  const project = opts?.project;
11140
11416
  const empty = {
11141
11417
  language: "javascript",
@@ -11170,8 +11446,8 @@ async function plan(serviceDir, opts) {
11170
11446
  return { ...empty, libOnly: true };
11171
11447
  }
11172
11448
  const flavor = dispatchEntry(entryFile, pkg);
11173
- const otelInitFile = import_node_path50.default.join(import_node_path50.default.dirname(entryFile), otelInitFilename(flavor));
11174
- const envNeatFile = import_node_path50.default.join(serviceDir, ".env.neat");
11449
+ const otelInitFile = import_node_path51.default.join(import_node_path51.default.dirname(entryFile), otelInitFilename(flavor));
11450
+ const envNeatFile = import_node_path51.default.join(serviceDir, ".env.neat");
11175
11451
  const existingDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
11176
11452
  const dependencyEdits = [];
11177
11453
  for (const sdk of SDK_PACKAGES) {
@@ -11195,7 +11471,7 @@ async function plan(serviceDir, opts) {
11195
11471
  }
11196
11472
  const entrypointEdits = [];
11197
11473
  try {
11198
- const raw = await import_node_fs31.promises.readFile(entryFile, "utf8");
11474
+ const raw = await import_node_fs32.promises.readFile(entryFile, "utf8");
11199
11475
  const lines = raw.split(/\r?\n/);
11200
11476
  const firstReal = lines[0]?.startsWith("#!") ? lines[1] ?? "" : lines[0] ?? "";
11201
11477
  if (!lineIsOtelInjection(firstReal)) {
@@ -11240,14 +11516,14 @@ async function plan(serviceDir, opts) {
11240
11516
  };
11241
11517
  }
11242
11518
  function isAllowedWritePath(serviceDir, target) {
11243
- const rel = import_node_path50.default.relative(serviceDir, target);
11519
+ const rel = import_node_path51.default.relative(serviceDir, target);
11244
11520
  if (rel.startsWith("..")) return false;
11245
- const base = import_node_path50.default.basename(target);
11521
+ const base = import_node_path51.default.basename(target);
11246
11522
  if (base === "package.json") return true;
11247
11523
  if (base === ".env.neat") return true;
11248
11524
  if (/^otel-init\.(?:js|cjs|mjs|ts)$/.test(base)) return true;
11249
- const relPosix = rel.split(import_node_path50.default.sep).join("/");
11250
- if (/^instrumentation(?:\.node)?\.(?:js|cjs|mjs|ts)$/.test(base)) {
11525
+ const relPosix = rel.split(import_node_path51.default.sep).join("/");
11526
+ if (/^instrumentation(?:\.(?:node|edge))?\.(?:js|cjs|mjs|ts)$/.test(base)) {
11251
11527
  if (relPosix === base) return true;
11252
11528
  if (relPosix === `src/${base}`) return true;
11253
11529
  return false;
@@ -11263,10 +11539,10 @@ function isAllowedWritePath(serviceDir, target) {
11263
11539
  return false;
11264
11540
  }
11265
11541
  async function writeAtomic(file, contents) {
11266
- await import_node_fs31.promises.mkdir(import_node_path50.default.dirname(file), { recursive: true });
11542
+ await import_node_fs32.promises.mkdir(import_node_path51.default.dirname(file), { recursive: true });
11267
11543
  const tmp = `${file}.${process.pid}.${Date.now()}.tmp`;
11268
- await import_node_fs31.promises.writeFile(tmp, contents, "utf8");
11269
- await import_node_fs31.promises.rename(tmp, file);
11544
+ await import_node_fs32.promises.writeFile(tmp, contents, "utf8");
11545
+ await import_node_fs32.promises.rename(tmp, file);
11270
11546
  }
11271
11547
  async function apply(installPlan) {
11272
11548
  const { serviceDir } = installPlan;
@@ -11342,7 +11618,7 @@ async function apply(installPlan) {
11342
11618
  for (const target of allTargets) {
11343
11619
  if (await exists3(target)) {
11344
11620
  try {
11345
- originals.set(target, await import_node_fs31.promises.readFile(target, "utf8"));
11621
+ originals.set(target, await import_node_fs32.promises.readFile(target, "utf8"));
11346
11622
  } catch {
11347
11623
  }
11348
11624
  }
@@ -11425,14 +11701,14 @@ async function rollback(installPlan, originals, createdFiles) {
11425
11701
  const removed = [];
11426
11702
  for (const [file, raw] of originals.entries()) {
11427
11703
  try {
11428
- await import_node_fs31.promises.writeFile(file, raw, "utf8");
11704
+ await import_node_fs32.promises.writeFile(file, raw, "utf8");
11429
11705
  restored.push(file);
11430
11706
  } catch {
11431
11707
  }
11432
11708
  }
11433
11709
  for (const file of createdFiles) {
11434
11710
  try {
11435
- await import_node_fs31.promises.unlink(file);
11711
+ await import_node_fs32.promises.unlink(file);
11436
11712
  removed.push(file);
11437
11713
  } catch {
11438
11714
  }
@@ -11447,8 +11723,8 @@ async function rollback(installPlan, originals, createdFiles) {
11447
11723
  ...removed.map((f) => `removed: ${f}`),
11448
11724
  ""
11449
11725
  ];
11450
- const rollbackPath = import_node_path50.default.join(installPlan.serviceDir, "neat-rollback.patch");
11451
- await import_node_fs31.promises.writeFile(rollbackPath, lines.join("\n"), "utf8");
11726
+ const rollbackPath = import_node_path51.default.join(installPlan.serviceDir, "neat-rollback.patch");
11727
+ await import_node_fs32.promises.writeFile(rollbackPath, lines.join("\n"), "utf8");
11452
11728
  }
11453
11729
  function injectInstrumentationHook(raw) {
11454
11730
  if (raw.includes("instrumentationHook")) return raw;
@@ -11477,8 +11753,8 @@ var javascriptInstaller = {
11477
11753
 
11478
11754
  // src/installers/python.ts
11479
11755
  init_cjs_shims();
11480
- var import_node_fs32 = require("fs");
11481
- var import_node_path51 = __toESM(require("path"), 1);
11756
+ var import_node_fs33 = require("fs");
11757
+ var import_node_path52 = __toESM(require("path"), 1);
11482
11758
  var SDK_PACKAGES2 = [
11483
11759
  { name: "opentelemetry-distro", version: ">=0.49b0" },
11484
11760
  { name: "opentelemetry-exporter-otlp", version: ">=1.28.0" }
@@ -11490,7 +11766,7 @@ var OTEL_ENV2 = {
11490
11766
  };
11491
11767
  async function exists4(p) {
11492
11768
  try {
11493
- await import_node_fs32.promises.stat(p);
11769
+ await import_node_fs33.promises.stat(p);
11494
11770
  return true;
11495
11771
  } catch {
11496
11772
  return false;
@@ -11499,7 +11775,7 @@ async function exists4(p) {
11499
11775
  async function detect2(serviceDir) {
11500
11776
  const markers = ["requirements.txt", "pyproject.toml", "setup.py"];
11501
11777
  for (const m of markers) {
11502
- if (await exists4(import_node_path51.default.join(serviceDir, m))) return true;
11778
+ if (await exists4(import_node_path52.default.join(serviceDir, m))) return true;
11503
11779
  }
11504
11780
  return false;
11505
11781
  }
@@ -11509,9 +11785,9 @@ function reqPackageName(line) {
11509
11785
  return head.replace(/[<>=!~].*$/, "").toLowerCase();
11510
11786
  }
11511
11787
  async function planRequirementsTxtEdits(serviceDir) {
11512
- const file = import_node_path51.default.join(serviceDir, "requirements.txt");
11788
+ const file = import_node_path52.default.join(serviceDir, "requirements.txt");
11513
11789
  if (!await exists4(file)) return null;
11514
- const raw = await import_node_fs32.promises.readFile(file, "utf8");
11790
+ const raw = await import_node_fs33.promises.readFile(file, "utf8");
11515
11791
  const presentNames = new Set(
11516
11792
  raw.split(/\r?\n/).map(reqPackageName).filter((n) => n.length > 0)
11517
11793
  );
@@ -11519,9 +11795,9 @@ async function planRequirementsTxtEdits(serviceDir) {
11519
11795
  return { manifest: file, missing: [...missing] };
11520
11796
  }
11521
11797
  async function planProcfileEdits(serviceDir) {
11522
- const procfile = import_node_path51.default.join(serviceDir, "Procfile");
11798
+ const procfile = import_node_path52.default.join(serviceDir, "Procfile");
11523
11799
  if (!await exists4(procfile)) return [];
11524
- const raw = await import_node_fs32.promises.readFile(procfile, "utf8");
11800
+ const raw = await import_node_fs33.promises.readFile(procfile, "utf8");
11525
11801
  const edits = [];
11526
11802
  for (const line of raw.split(/\r?\n/)) {
11527
11803
  if (line.length === 0) continue;
@@ -11573,8 +11849,8 @@ async function applyRequirementsTxt(manifest, edits, original) {
11573
11849
  const next = `${original}${trailing}${newlines.join("\n")}
11574
11850
  `;
11575
11851
  const tmp = `${manifest}.${process.pid}.${Date.now()}.tmp`;
11576
- await import_node_fs32.promises.writeFile(tmp, next, "utf8");
11577
- await import_node_fs32.promises.rename(tmp, manifest);
11852
+ await import_node_fs33.promises.writeFile(tmp, next, "utf8");
11853
+ await import_node_fs33.promises.rename(tmp, manifest);
11578
11854
  }
11579
11855
  async function applyProcfile(procfile, edits, original) {
11580
11856
  let next = original;
@@ -11583,8 +11859,8 @@ async function applyProcfile(procfile, edits, original) {
11583
11859
  next = next.replace(e.before, e.after);
11584
11860
  }
11585
11861
  const tmp = `${procfile}.${process.pid}.${Date.now()}.tmp`;
11586
- await import_node_fs32.promises.writeFile(tmp, next, "utf8");
11587
- await import_node_fs32.promises.rename(tmp, procfile);
11862
+ await import_node_fs33.promises.writeFile(tmp, next, "utf8");
11863
+ await import_node_fs33.promises.rename(tmp, procfile);
11588
11864
  }
11589
11865
  async function apply2(installPlan) {
11590
11866
  const { serviceDir } = installPlan;
@@ -11597,7 +11873,7 @@ async function apply2(installPlan) {
11597
11873
  const originals = /* @__PURE__ */ new Map();
11598
11874
  for (const file of touched) {
11599
11875
  try {
11600
- originals.set(file, await import_node_fs32.promises.readFile(file, "utf8"));
11876
+ originals.set(file, await import_node_fs33.promises.readFile(file, "utf8"));
11601
11877
  } catch {
11602
11878
  }
11603
11879
  }
@@ -11608,7 +11884,7 @@ async function apply2(installPlan) {
11608
11884
  if (raw === void 0) {
11609
11885
  throw new Error(`python installer: cannot read ${file} during apply`);
11610
11886
  }
11611
- const base = import_node_path51.default.basename(file);
11887
+ const base = import_node_path52.default.basename(file);
11612
11888
  if (base === "requirements.txt") {
11613
11889
  const edits = installPlan.dependencyEdits.filter((e) => e.file === file);
11614
11890
  if (edits.length > 0) {
@@ -11633,7 +11909,7 @@ async function rollback2(installPlan, originals) {
11633
11909
  const restored = [];
11634
11910
  for (const [file, raw] of originals.entries()) {
11635
11911
  try {
11636
- await import_node_fs32.promises.writeFile(file, raw, "utf8");
11912
+ await import_node_fs33.promises.writeFile(file, raw, "utf8");
11637
11913
  restored.push(file);
11638
11914
  } catch {
11639
11915
  }
@@ -11647,8 +11923,8 @@ async function rollback2(installPlan, originals) {
11647
11923
  ...restored.map((f) => `restored: ${f}`),
11648
11924
  ""
11649
11925
  ];
11650
- const rollbackPath = import_node_path51.default.join(installPlan.serviceDir, "neat-rollback.patch");
11651
- await import_node_fs32.promises.writeFile(rollbackPath, lines.join("\n"), "utf8");
11926
+ const rollbackPath = import_node_path52.default.join(installPlan.serviceDir, "neat-rollback.patch");
11927
+ await import_node_fs33.promises.writeFile(rollbackPath, lines.join("\n"), "utf8");
11652
11928
  }
11653
11929
  var pythonInstaller = {
11654
11930
  name: "python",
@@ -11771,10 +12047,10 @@ function renderPatch(sections) {
11771
12047
 
11772
12048
  // src/orchestrator.ts
11773
12049
  init_cjs_shims();
11774
- var import_node_fs33 = require("fs");
12050
+ var import_node_fs34 = require("fs");
11775
12051
  var import_node_http = __toESM(require("http"), 1);
11776
12052
  var import_node_net = __toESM(require("net"), 1);
11777
- var import_node_path52 = __toESM(require("path"), 1);
12053
+ var import_node_path53 = __toESM(require("path"), 1);
11778
12054
  var import_node_child_process3 = require("child_process");
11779
12055
  var import_node_readline = __toESM(require("readline"), 1);
11780
12056
  async function extractAndPersist(opts) {
@@ -11783,7 +12059,7 @@ async function extractAndPersist(opts) {
11783
12059
  const graphKey = opts.projectExplicit ? opts.project : DEFAULT_PROJECT;
11784
12060
  resetGraph(graphKey);
11785
12061
  const graph = getGraph(graphKey);
11786
- const projectPaths = pathsForProject(graphKey, import_node_path52.default.join(opts.scanPath, "neat-out"));
12062
+ const projectPaths = pathsForProject(graphKey, import_node_path53.default.join(opts.scanPath, "neat-out"));
11787
12063
  const extraction = await extractFromDirectory(graph, opts.scanPath, {
11788
12064
  errorsPath: projectPaths.errorsPath
11789
12065
  });
@@ -11835,7 +12111,7 @@ async function applyInstallersOver(services, project, options = {}) {
11835
12111
  libOnly++;
11836
12112
  const appDeps = svc.pkg ? appFrameworkDependencies(svc.pkg) : [];
11837
12113
  if (appDeps.length > 0) {
11838
- const svcName = import_node_path52.default.basename(svc.dir);
12114
+ const svcName = import_node_path53.default.basename(svc.dir);
11839
12115
  const list = appDeps.join(", ");
11840
12116
  console.warn(
11841
12117
  `neat: runtime layer won't engage for ${svcName}: no entry point found.
@@ -11848,7 +12124,7 @@ async function applyInstallersOver(services, project, options = {}) {
11848
12124
  console.log(`skipping ${svc.dir}: browser bundle; browser-OTel support lands in a future release.`);
11849
12125
  } else if (outcome.outcome === "react-native") {
11850
12126
  reactNative++;
11851
- const svcName = import_node_path52.default.basename(svc.dir);
12127
+ const svcName = import_node_path53.default.basename(svc.dir);
11852
12128
  console.log(
11853
12129
  `neat: ${svc.dir} detected as React Native / Expo
11854
12130
  The installer doesn't cover this runtime deterministically.
@@ -11859,7 +12135,7 @@ async function applyInstallersOver(services, project, options = {}) {
11859
12135
  );
11860
12136
  } else if (outcome.outcome === "bun") {
11861
12137
  bun++;
11862
- const svcName = import_node_path52.default.basename(svc.dir);
12138
+ const svcName = import_node_path53.default.basename(svc.dir);
11863
12139
  console.log(
11864
12140
  `neat: ${svc.dir} detected as Bun
11865
12141
  The installer doesn't cover this runtime deterministically.
@@ -11870,7 +12146,7 @@ async function applyInstallersOver(services, project, options = {}) {
11870
12146
  );
11871
12147
  } else if (outcome.outcome === "deno") {
11872
12148
  deno++;
11873
- const svcName = import_node_path52.default.basename(svc.dir);
12149
+ const svcName = import_node_path53.default.basename(svc.dir);
11874
12150
  console.log(
11875
12151
  `neat: ${svc.dir} detected as Deno
11876
12152
  The installer doesn't cover this runtime deterministically.
@@ -11881,7 +12157,7 @@ async function applyInstallersOver(services, project, options = {}) {
11881
12157
  );
11882
12158
  } else if (outcome.outcome === "cloudflare-workers") {
11883
12159
  cloudflareWorkers++;
11884
- const svcName = import_node_path52.default.basename(svc.dir);
12160
+ const svcName = import_node_path53.default.basename(svc.dir);
11885
12161
  console.log(
11886
12162
  `neat: ${svc.dir} detected as Cloudflare Workers
11887
12163
  The installer doesn't cover this runtime deterministically.
@@ -11892,7 +12168,7 @@ async function applyInstallersOver(services, project, options = {}) {
11892
12168
  );
11893
12169
  } else if (outcome.outcome === "electron") {
11894
12170
  electron++;
11895
- const svcName = import_node_path52.default.basename(svc.dir);
12171
+ const svcName = import_node_path53.default.basename(svc.dir);
11896
12172
  console.log(
11897
12173
  `neat: ${svc.dir} detected as Electron
11898
12174
  The installer doesn't cover this runtime deterministically.
@@ -11905,7 +12181,7 @@ async function applyInstallersOver(services, project, options = {}) {
11905
12181
  if (svc.pkg && (outcome.outcome === "instrumented" || outcome.outcome === "already-instrumented")) {
11906
12182
  const gaps = uninstrumentedLibraries(svc.pkg);
11907
12183
  if (gaps.length > 0) {
11908
- const svcName = import_node_path52.default.basename(svc.dir);
12184
+ const svcName = import_node_path53.default.basename(svc.dir);
11909
12185
  const list = gaps.join(", ");
11910
12186
  const subject = gaps.length === 1 ? "this library" : "these libraries";
11911
12187
  const aux = gaps.length === 1 ? "isn't" : "aren't";
@@ -12111,24 +12387,24 @@ async function persistedPortsFor(scanPath) {
12111
12387
  return { rest: record.ports.rest, otlp: record.ports.otlp, web: record.ports.web };
12112
12388
  }
12113
12389
  async function acquireSpawnLock(scanPath) {
12114
- const lockPath = import_node_path52.default.join(scanPath, "neat-out", "daemon.spawn.lock");
12115
- await import_node_fs33.promises.mkdir(import_node_path52.default.dirname(lockPath), { recursive: true });
12390
+ const lockPath = import_node_path53.default.join(scanPath, "neat-out", "daemon.spawn.lock");
12391
+ await import_node_fs34.promises.mkdir(import_node_path53.default.dirname(lockPath), { recursive: true });
12116
12392
  const STALE_LOCK_MS = 6e4;
12117
12393
  try {
12118
- const fd = await import_node_fs33.promises.open(lockPath, "wx");
12394
+ const fd = await import_node_fs34.promises.open(lockPath, "wx");
12119
12395
  await fd.writeFile(`${process.pid}
12120
12396
  `, "utf8");
12121
12397
  await fd.close();
12122
12398
  return async () => {
12123
- await import_node_fs33.promises.unlink(lockPath).catch(() => {
12399
+ await import_node_fs34.promises.unlink(lockPath).catch(() => {
12124
12400
  });
12125
12401
  };
12126
12402
  } catch (err) {
12127
12403
  if (err.code !== "EEXIST") return null;
12128
12404
  try {
12129
- const stat = await import_node_fs33.promises.stat(lockPath);
12405
+ const stat = await import_node_fs34.promises.stat(lockPath);
12130
12406
  if (Date.now() - stat.mtimeMs > STALE_LOCK_MS) {
12131
- await import_node_fs33.promises.unlink(lockPath).catch(() => {
12407
+ await import_node_fs34.promises.unlink(lockPath).catch(() => {
12132
12408
  });
12133
12409
  return acquireSpawnLock(scanPath);
12134
12410
  }
@@ -12157,13 +12433,13 @@ async function healthIsForProject(restPort, project) {
12157
12433
  return false;
12158
12434
  }
12159
12435
  function daemonLogPath(projectPath2) {
12160
- return import_node_path52.default.join(projectPath2, "neat-out", "daemon.log");
12436
+ return import_node_path53.default.join(projectPath2, "neat-out", "daemon.log");
12161
12437
  }
12162
12438
  function spawnDaemonDetached(spec) {
12163
- const here = import_node_path52.default.dirname(new URL(importMetaUrl).pathname);
12439
+ const here = import_node_path53.default.dirname(new URL(importMetaUrl).pathname);
12164
12440
  const candidates = [
12165
- import_node_path52.default.join(here, "neatd.cjs"),
12166
- import_node_path52.default.join(here, "neatd.js")
12441
+ import_node_path53.default.join(here, "neatd.cjs"),
12442
+ import_node_path53.default.join(here, "neatd.js")
12167
12443
  ];
12168
12444
  let entry2 = null;
12169
12445
  const fsSync = require("fs");
@@ -12193,7 +12469,7 @@ function spawnDaemonDetached(spec) {
12193
12469
  let logFd = null;
12194
12470
  if (spec) {
12195
12471
  const logPath = daemonLogPath(spec.projectPath);
12196
- fsSync.mkdirSync(import_node_path52.default.dirname(logPath), { recursive: true });
12472
+ fsSync.mkdirSync(import_node_path53.default.dirname(logPath), { recursive: true });
12197
12473
  logFd = fsSync.openSync(logPath, "a");
12198
12474
  }
12199
12475
  const child = (0, import_node_child_process3.spawn)(process.execPath, [entry2, "start"], {
@@ -12232,7 +12508,7 @@ async function runOrchestrator(opts) {
12232
12508
  browser: "skipped"
12233
12509
  }
12234
12510
  };
12235
- const stat = await import_node_fs33.promises.stat(opts.scanPath).catch(() => null);
12511
+ const stat = await import_node_fs34.promises.stat(opts.scanPath).catch(() => null);
12236
12512
  if (!stat || !stat.isDirectory()) {
12237
12513
  console.error(`neat: ${opts.scanPath} is not a directory`);
12238
12514
  result.exitCode = 2;
@@ -12392,7 +12668,7 @@ async function runOrchestrator(opts) {
12392
12668
  result.steps.browser = openBrowser(dashboardUrl);
12393
12669
  }
12394
12670
  const daemonRunning = result.steps.daemon === "spawned" || result.steps.daemon === "already-running";
12395
- const daemonLog = daemonRunning ? import_node_path52.default.relative(opts.scanPath, daemonLogPath(opts.scanPath)) : null;
12671
+ const daemonLog = daemonRunning ? import_node_path53.default.relative(opts.scanPath, daemonLogPath(opts.scanPath)) : null;
12396
12672
  printSummary(result, graph, dashboardUrl, daemonLog);
12397
12673
  return result;
12398
12674
  }
@@ -12430,11 +12706,11 @@ function printSummary(result, graph, dashboardUrl, daemonLog) {
12430
12706
 
12431
12707
  // src/cli-verbs.ts
12432
12708
  init_cjs_shims();
12433
- var import_node_path53 = __toESM(require("path"), 1);
12709
+ var import_node_path54 = __toESM(require("path"), 1);
12434
12710
 
12435
12711
  // src/cli-client.ts
12436
12712
  init_cjs_shims();
12437
- var import_types31 = require("@neat.is/types");
12713
+ var import_types32 = require("@neat.is/types");
12438
12714
  var HttpError = class extends Error {
12439
12715
  constructor(status2, message, responseBody = "") {
12440
12716
  super(message);
@@ -12459,10 +12735,10 @@ function createHttpClient(baseUrl, bearerToken) {
12459
12735
  const root = baseUrl.replace(/\/$/, "");
12460
12736
  const authHeader = bearerToken && bearerToken.length > 0 ? { authorization: `Bearer ${bearerToken}` } : {};
12461
12737
  return {
12462
- async get(path55) {
12738
+ async get(path56) {
12463
12739
  let res;
12464
12740
  try {
12465
- res = await fetch(`${root}${path55}`, {
12741
+ res = await fetch(`${root}${path56}`, {
12466
12742
  headers: { ...authHeader }
12467
12743
  });
12468
12744
  } catch (err) {
@@ -12474,16 +12750,16 @@ function createHttpClient(baseUrl, bearerToken) {
12474
12750
  const body = await res.text().catch(() => "");
12475
12751
  throw new HttpError(
12476
12752
  res.status,
12477
- `${res.status} ${res.statusText} on GET ${path55}: ${body}`,
12753
+ `${res.status} ${res.statusText} on GET ${path56}: ${body}`,
12478
12754
  body
12479
12755
  );
12480
12756
  }
12481
12757
  return await res.json();
12482
12758
  },
12483
- async post(path55, body) {
12759
+ async post(path56, body) {
12484
12760
  let res;
12485
12761
  try {
12486
- res = await fetch(`${root}${path55}`, {
12762
+ res = await fetch(`${root}${path56}`, {
12487
12763
  method: "POST",
12488
12764
  headers: { "content-type": "application/json", ...authHeader },
12489
12765
  body: JSON.stringify(body)
@@ -12497,7 +12773,7 @@ function createHttpClient(baseUrl, bearerToken) {
12497
12773
  const text = await res.text().catch(() => "");
12498
12774
  throw new HttpError(
12499
12775
  res.status,
12500
- `${res.status} ${res.statusText} on POST ${path55}: ${text}`,
12776
+ `${res.status} ${res.statusText} on POST ${path56}: ${text}`,
12501
12777
  text
12502
12778
  );
12503
12779
  }
@@ -12511,12 +12787,12 @@ function projectPath(project, suffix) {
12511
12787
  }
12512
12788
  async function runRootCause(client, input) {
12513
12789
  const qs = input.errorId ? `?errorId=${encodeURIComponent(input.errorId)}` : "";
12514
- const path55 = projectPath(
12790
+ const path56 = projectPath(
12515
12791
  input.project,
12516
12792
  `/graph/root-cause/${encodeURIComponent(input.errorNode)}${qs}`
12517
12793
  );
12518
12794
  try {
12519
- const result = await client.get(path55);
12795
+ const result = await client.get(path56);
12520
12796
  const arrowPath = result.traversalPath.join(" \u2190 ");
12521
12797
  const provenances = result.edgeProvenances.length ? result.edgeProvenances.join(", ") : "(direct, no edges traversed)";
12522
12798
  const summary = `Root cause for ${input.errorNode} is ${result.rootCauseNode}. ` + result.rootCauseReason + (result.fixRecommendation ? ` Recommended fix: ${result.fixRecommendation}.` : "");
@@ -12542,12 +12818,12 @@ async function runRootCause(client, input) {
12542
12818
  }
12543
12819
  async function runBlastRadius(client, input) {
12544
12820
  const qs = input.depth !== void 0 ? `?depth=${input.depth}` : "";
12545
- const path55 = projectPath(
12821
+ const path56 = projectPath(
12546
12822
  input.project,
12547
12823
  `/graph/blast-radius/${encodeURIComponent(input.nodeId)}${qs}`
12548
12824
  );
12549
12825
  try {
12550
- const result = await client.get(path55);
12826
+ const result = await client.get(path56);
12551
12827
  if (result.totalAffected === 0) {
12552
12828
  return {
12553
12829
  summary: `${result.origin} has no dependents. Nothing else would break if it failed.`
@@ -12576,17 +12852,17 @@ async function runBlastRadius(client, input) {
12576
12852
  }
12577
12853
  }
12578
12854
  function formatBlastEntry(n) {
12579
- const tag = n.edgeProvenance === import_types31.Provenance.STALE ? " [STALE \u2014 last seen too long ago]" : "";
12855
+ const tag = n.edgeProvenance === import_types32.Provenance.STALE ? " [STALE \u2014 last seen too long ago]" : "";
12580
12856
  return ` \u2022 ${n.nodeId} (distance ${n.distance}, ${n.edgeProvenance})${tag}`;
12581
12857
  }
12582
12858
  async function runDependencies(client, input) {
12583
12859
  const depth = input.depth ?? 3;
12584
- const path55 = projectPath(
12860
+ const path56 = projectPath(
12585
12861
  input.project,
12586
12862
  `/graph/dependencies/${encodeURIComponent(input.nodeId)}?depth=${depth}`
12587
12863
  );
12588
12864
  try {
12589
- const result = await client.get(path55);
12865
+ const result = await client.get(path56);
12590
12866
  if (result.total === 0) {
12591
12867
  return {
12592
12868
  summary: depth === 1 ? `${input.nodeId} has no direct dependencies in the graph.` : `${input.nodeId} has no dependencies (BFS to depth ${depth}).`
@@ -12633,7 +12909,7 @@ async function runObservedDependencies(client, input) {
12633
12909
  if (result.observed) {
12634
12910
  return {
12635
12911
  summary: `${input.nodeId} makes no outbound runtime calls, but OTel has observed it receiving traffic on ${result.inboundObservedCount} inbound call path${result.inboundObservedCount === 1 ? "" : "s"} \u2014 it's a pure receiver.`,
12636
- provenance: import_types31.Provenance.OBSERVED
12912
+ provenance: import_types32.Provenance.OBSERVED
12637
12913
  };
12638
12914
  }
12639
12915
  const note = result.hasExtractedOutbound ? " Static (EXTRACTED) dependencies exist but no runtime traffic has been seen \u2014 is OTel running?" : "";
@@ -12643,7 +12919,7 @@ async function runObservedDependencies(client, input) {
12643
12919
  return {
12644
12920
  summary: `${input.nodeId} has ${result.dependencies.length} runtime dependenc${result.dependencies.length === 1 ? "y" : "ies"} confirmed by OTel.`,
12645
12921
  block: blockLines.join("\n"),
12646
- provenance: import_types31.Provenance.OBSERVED
12922
+ provenance: import_types32.Provenance.OBSERVED
12647
12923
  };
12648
12924
  } catch (err) {
12649
12925
  if (err instanceof HttpError && err.status === 404) {
@@ -12678,9 +12954,9 @@ function formatDuration(ms) {
12678
12954
  return `${Math.round(h / 24)}d`;
12679
12955
  }
12680
12956
  async function runIncidents(client, input) {
12681
- const path55 = input.nodeId ? projectPath(input.project, `/incidents/${encodeURIComponent(input.nodeId)}`) : projectPath(input.project, "/incidents");
12957
+ const path56 = input.nodeId ? projectPath(input.project, `/incidents/${encodeURIComponent(input.nodeId)}`) : projectPath(input.project, "/incidents");
12682
12958
  try {
12683
- const body = await client.get(path55);
12959
+ const body = await client.get(path56);
12684
12960
  const events = body.events;
12685
12961
  if (events.length === 0) {
12686
12962
  return {
@@ -12697,7 +12973,7 @@ async function runIncidents(client, input) {
12697
12973
  return {
12698
12974
  summary: `${target} has ${body.total} recorded incident${body.total === 1 ? "" : "s"}; showing the ${ordered.length} most recent.`,
12699
12975
  block: blockLines.join("\n"),
12700
- provenance: import_types31.Provenance.OBSERVED
12976
+ provenance: import_types32.Provenance.OBSERVED
12701
12977
  };
12702
12978
  } catch (err) {
12703
12979
  if (err instanceof HttpError && err.status === 404) {
@@ -12806,7 +13082,7 @@ async function runStaleEdges(client, input) {
12806
13082
  return {
12807
13083
  summary: `${events.length} stale-edge transition${events.length === 1 ? "" : "s"} recorded${input.edgeType ? ` for ${input.edgeType}` : ""}.`,
12808
13084
  block: blockLines.join("\n"),
12809
- provenance: import_types31.Provenance.STALE
13085
+ provenance: import_types32.Provenance.STALE
12810
13086
  };
12811
13087
  }
12812
13088
  async function runPolicies(client, input) {
@@ -12970,7 +13246,7 @@ async function resolveProjectEntry(opts) {
12970
13246
  const cwd = opts.cwd ?? process.cwd();
12971
13247
  const resolvedCwd = await normalizeProjectPath(cwd);
12972
13248
  for (const entry2 of entries) {
12973
- if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${import_node_path53.default.sep}`)) {
13249
+ if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${import_node_path54.default.sep}`)) {
12974
13250
  return entry2;
12975
13251
  }
12976
13252
  }
@@ -13123,7 +13399,7 @@ async function runSync(opts) {
13123
13399
  }
13124
13400
 
13125
13401
  // src/cli.ts
13126
- var import_types32 = require("@neat.is/types");
13402
+ var import_types33 = require("@neat.is/types");
13127
13403
  function isNpxInvocation() {
13128
13404
  if (process.env.npm_command === "exec") return true;
13129
13405
  const execpath = process.env.npm_execpath ?? "";
@@ -13405,7 +13681,7 @@ async function buildPatchSections(services, project) {
13405
13681
  }
13406
13682
  async function runInit(opts) {
13407
13683
  const written = [];
13408
- const stat = await import_node_fs34.promises.stat(opts.scanPath).catch(() => null);
13684
+ const stat = await import_node_fs35.promises.stat(opts.scanPath).catch(() => null);
13409
13685
  if (!stat || !stat.isDirectory()) {
13410
13686
  console.error(`neat init: ${opts.scanPath} is not a directory`);
13411
13687
  return { exitCode: 2, writtenFiles: written };
@@ -13414,13 +13690,13 @@ async function runInit(opts) {
13414
13690
  printDiscoveryReport(opts, services);
13415
13691
  const sections = opts.noInstall ? [] : await buildPatchSections(services, opts.project);
13416
13692
  const patch = renderPatch(sections);
13417
- const patchPath = import_node_path54.default.join(opts.scanPath, "neat.patch");
13693
+ const patchPath = import_node_path55.default.join(opts.scanPath, "neat.patch");
13418
13694
  if (opts.dryRun) {
13419
- await import_node_fs34.promises.writeFile(patchPath, patch, "utf8");
13695
+ await import_node_fs35.promises.writeFile(patchPath, patch, "utf8");
13420
13696
  written.push(patchPath);
13421
13697
  console.log(`dry-run: patch written to ${patchPath}`);
13422
- const gitignorePath = import_node_path54.default.join(opts.scanPath, ".gitignore");
13423
- const gitignoreExists = await import_node_fs34.promises.stat(gitignorePath).then(() => true).catch(() => false);
13698
+ const gitignorePath = import_node_path55.default.join(opts.scanPath, ".gitignore");
13699
+ const gitignoreExists = await import_node_fs35.promises.stat(gitignorePath).then(() => true).catch(() => false);
13424
13700
  const verb = gitignoreExists ? "append" : "create";
13425
13701
  console.log(`dry-run: would ${verb} ${gitignorePath} (add neat-out/)`);
13426
13702
  console.log("rerun without --dry-run to register and snapshot.");
@@ -13431,9 +13707,9 @@ async function runInit(opts) {
13431
13707
  const graph = getGraph(graphKey);
13432
13708
  const projectPaths = pathsForProject(
13433
13709
  graphKey,
13434
- import_node_path54.default.join(opts.scanPath, "neat-out")
13710
+ import_node_path55.default.join(opts.scanPath, "neat-out")
13435
13711
  );
13436
- const errorsPath = import_node_path54.default.join(import_node_path54.default.dirname(opts.outPath), import_node_path54.default.basename(projectPaths.errorsPath));
13712
+ const errorsPath = import_node_path55.default.join(import_node_path55.default.dirname(opts.outPath), import_node_path55.default.basename(projectPaths.errorsPath));
13437
13713
  const result = await extractFromDirectory(graph, opts.scanPath, { errorsPath });
13438
13714
  await saveGraphToDisk(graph, opts.outPath);
13439
13715
  written.push(opts.outPath);
@@ -13512,7 +13788,7 @@ async function runInit(opts) {
13512
13788
  console.log("Run `npm install` (or your language equivalent) to refresh lockfiles.");
13513
13789
  }
13514
13790
  } else {
13515
- await import_node_fs34.promises.writeFile(patchPath, patch, "utf8");
13791
+ await import_node_fs35.promises.writeFile(patchPath, patch, "utf8");
13516
13792
  written.push(patchPath);
13517
13793
  }
13518
13794
  }
@@ -13552,9 +13828,9 @@ var CLAUDE_SKILL_CONFIG = {
13552
13828
  };
13553
13829
  function claudeConfigPath() {
13554
13830
  const override = process.env.NEAT_CLAUDE_CONFIG;
13555
- if (override && override.length > 0) return import_node_path54.default.resolve(override);
13831
+ if (override && override.length > 0) return import_node_path55.default.resolve(override);
13556
13832
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
13557
- return import_node_path54.default.join(home, ".claude.json");
13833
+ return import_node_path55.default.join(home, ".claude.json");
13558
13834
  }
13559
13835
  async function runSkill(opts) {
13560
13836
  const snippet2 = JSON.stringify(CLAUDE_SKILL_CONFIG, null, 2) + "\n";
@@ -13566,7 +13842,7 @@ async function runSkill(opts) {
13566
13842
  const target = claudeConfigPath();
13567
13843
  let existing = {};
13568
13844
  try {
13569
- existing = JSON.parse(await import_node_fs34.promises.readFile(target, "utf8"));
13845
+ existing = JSON.parse(await import_node_fs35.promises.readFile(target, "utf8"));
13570
13846
  } catch (err) {
13571
13847
  if (err.code !== "ENOENT") {
13572
13848
  console.error(`neat skill: failed to read ${target} \u2014 ${err.message}`);
@@ -13578,8 +13854,8 @@ async function runSkill(opts) {
13578
13854
  ...existing,
13579
13855
  mcpServers: { ...mcp, neat: CLAUDE_SKILL_CONFIG.mcpServers.neat }
13580
13856
  };
13581
- await import_node_fs34.promises.mkdir(import_node_path54.default.dirname(target), { recursive: true });
13582
- await import_node_fs34.promises.writeFile(target, JSON.stringify(merged, null, 2) + "\n", "utf8");
13857
+ await import_node_fs35.promises.mkdir(import_node_path55.default.dirname(target), { recursive: true });
13858
+ await import_node_fs35.promises.writeFile(target, JSON.stringify(merged, null, 2) + "\n", "utf8");
13583
13859
  console.log(`neat skill: wrote mcpServers.neat to ${target}`);
13584
13860
  console.log("restart Claude Code to pick up the new MCP server.");
13585
13861
  return { exitCode: 0 };
@@ -13628,12 +13904,12 @@ async function main() {
13628
13904
  console.error("neat init: --apply and --dry-run are mutually exclusive");
13629
13905
  process.exit(2);
13630
13906
  }
13631
- const scanPath = import_node_path54.default.resolve(target);
13907
+ const scanPath = import_node_path55.default.resolve(target);
13632
13908
  const projectExplicit = parsed.project !== null;
13633
- const projectName = projectExplicit ? project : import_node_path54.default.basename(scanPath);
13909
+ const projectName = projectExplicit ? project : import_node_path55.default.basename(scanPath);
13634
13910
  const projectKey = projectExplicit ? project : DEFAULT_PROJECT;
13635
- const fallback = pathsForProject(projectKey, import_node_path54.default.join(scanPath, "neat-out")).snapshotPath;
13636
- const outPath = import_node_path54.default.resolve(process.env.NEAT_OUT_PATH ?? fallback);
13911
+ const fallback = pathsForProject(projectKey, import_node_path55.default.join(scanPath, "neat-out")).snapshotPath;
13912
+ const outPath = import_node_path55.default.resolve(process.env.NEAT_OUT_PATH ?? fallback);
13637
13913
  const result = await runInit({
13638
13914
  scanPath,
13639
13915
  outPath,
@@ -13654,21 +13930,21 @@ async function main() {
13654
13930
  usage();
13655
13931
  process.exit(2);
13656
13932
  }
13657
- const scanPath = import_node_path54.default.resolve(target);
13658
- const stat = await import_node_fs34.promises.stat(scanPath).catch(() => null);
13933
+ const scanPath = import_node_path55.default.resolve(target);
13934
+ const stat = await import_node_fs35.promises.stat(scanPath).catch(() => null);
13659
13935
  if (!stat || !stat.isDirectory()) {
13660
13936
  console.error(`neat watch: ${scanPath} is not a directory`);
13661
13937
  process.exit(2);
13662
13938
  }
13663
- const projectPaths = pathsForProject(project, import_node_path54.default.join(scanPath, "neat-out"));
13664
- const outPath = import_node_path54.default.resolve(process.env.NEAT_OUT_PATH ?? projectPaths.snapshotPath);
13665
- const errorsPath = import_node_path54.default.resolve(
13666
- process.env.NEAT_ERRORS_PATH ?? import_node_path54.default.join(import_node_path54.default.dirname(outPath), import_node_path54.default.basename(projectPaths.errorsPath))
13939
+ const projectPaths = pathsForProject(project, import_node_path55.default.join(scanPath, "neat-out"));
13940
+ const outPath = import_node_path55.default.resolve(process.env.NEAT_OUT_PATH ?? projectPaths.snapshotPath);
13941
+ const errorsPath = import_node_path55.default.resolve(
13942
+ process.env.NEAT_ERRORS_PATH ?? import_node_path55.default.join(import_node_path55.default.dirname(outPath), import_node_path55.default.basename(projectPaths.errorsPath))
13667
13943
  );
13668
- const staleEventsPath = import_node_path54.default.resolve(
13669
- process.env.NEAT_STALE_EVENTS_PATH ?? import_node_path54.default.join(import_node_path54.default.dirname(outPath), import_node_path54.default.basename(projectPaths.staleEventsPath))
13944
+ const staleEventsPath = import_node_path55.default.resolve(
13945
+ process.env.NEAT_STALE_EVENTS_PATH ?? import_node_path55.default.join(import_node_path55.default.dirname(outPath), import_node_path55.default.basename(projectPaths.staleEventsPath))
13670
13946
  );
13671
- const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ? import_node_path54.default.resolve(process.env.NEAT_EMBEDDINGS_CACHE_PATH) : void 0;
13947
+ const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ? import_node_path55.default.resolve(process.env.NEAT_EMBEDDINGS_CACHE_PATH) : void 0;
13672
13948
  const handle = await startWatch(getGraph(project), {
13673
13949
  scanPath,
13674
13950
  outPath,
@@ -13851,11 +14127,11 @@ async function main() {
13851
14127
  process.exit(1);
13852
14128
  }
13853
14129
  async function tryOrchestrator(cmd, parsed) {
13854
- const scanPath = import_node_path54.default.resolve(cmd);
13855
- const stat = await import_node_fs34.promises.stat(scanPath).catch(() => null);
14130
+ const scanPath = import_node_path55.default.resolve(cmd);
14131
+ const stat = await import_node_fs35.promises.stat(scanPath).catch(() => null);
13856
14132
  if (!stat || !stat.isDirectory()) return null;
13857
14133
  const projectExplicit = parsed.project !== null;
13858
- const projectName = projectExplicit ? parsed.project : import_node_path54.default.basename(scanPath);
14134
+ const projectName = projectExplicit ? parsed.project : import_node_path55.default.basename(scanPath);
13859
14135
  const result = await runOrchestrator({
13860
14136
  scanPath,
13861
14137
  project: projectName,
@@ -14044,10 +14320,10 @@ async function runQueryVerb(cmd, parsed) {
14044
14320
  const parts = parsed.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
14045
14321
  const out = [];
14046
14322
  for (const p of parts) {
14047
- const r = import_types32.DivergenceTypeSchema.safeParse(p);
14323
+ const r = import_types33.DivergenceTypeSchema.safeParse(p);
14048
14324
  if (!r.success) {
14049
14325
  console.error(
14050
- `neat divergences: unknown --type "${p}". allowed: ${import_types32.DivergenceTypeSchema.options.join(", ")}`
14326
+ `neat divergences: unknown --type "${p}". allowed: ${import_types33.DivergenceTypeSchema.options.join(", ")}`
14051
14327
  );
14052
14328
  return 2;
14053
14329
  }