@neat.is/core 0.4.26-dev.20260702 → 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 path53 = (req.url.split("?")[0] ?? "").replace(/\/+$/, "");
61
+ const path56 = (req.url.split("?")[0] ?? "").replace(/\/+$/, "");
62
62
  for (const suffix of suffixes) {
63
- if (path53 === suffix || path53.endsWith(suffix)) {
63
+ if (path56 === suffix || path56.endsWith(suffix)) {
64
64
  done();
65
65
  return;
66
66
  }
@@ -71,11 +71,13 @@ function mountBearerAuth(app, opts) {
71
71
  }
72
72
  const header = req.headers.authorization;
73
73
  if (typeof header !== "string" || !header.startsWith("Bearer ")) {
74
+ opts.onReject?.();
74
75
  void reply.code(401).send({ error: "unauthorized" });
75
76
  return;
76
77
  }
77
78
  const provided = Buffer.from(header.slice("Bearer ".length).trim(), "utf8");
78
79
  if (provided.length !== expected.length || !(0, import_node_crypto.timingSafeEqual)(provided, expected)) {
80
+ opts.onReject?.();
79
81
  void reply.code(401).send({ error: "unauthorized" });
80
82
  return;
81
83
  }
@@ -188,8 +190,8 @@ function reshapeGrpcRequest(req) {
188
190
  };
189
191
  }
190
192
  function resolveProtoRoot() {
191
- const here = import_node_path41.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
192
- return import_node_path41.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");
193
195
  }
194
196
  function loadTraceService() {
195
197
  const protoRoot = resolveProtoRoot();
@@ -257,13 +259,13 @@ async function startOtelGrpcReceiver(opts) {
257
259
  })
258
260
  };
259
261
  }
260
- var import_node_url2, import_node_path41, import_node_crypto2, grpc, protoLoader;
262
+ var import_node_url2, import_node_path44, import_node_crypto2, grpc, protoLoader;
261
263
  var init_otel_grpc = __esm({
262
264
  "src/otel-grpc.ts"() {
263
265
  "use strict";
264
266
  init_cjs_shims();
265
267
  import_node_url2 = require("url");
266
- import_node_path41 = __toESM(require("path"), 1);
268
+ import_node_path44 = __toESM(require("path"), 1);
267
269
  import_node_crypto2 = require("crypto");
268
270
  grpc = __toESM(require("@grpc/grpc-js"), 1);
269
271
  protoLoader = __toESM(require("@grpc/proto-loader"), 1);
@@ -336,6 +338,36 @@ function pickEnv(spanAttrs, resourceAttrs) {
336
338
  }
337
339
  return ENV_FALLBACK;
338
340
  }
341
+ function messagingDestinationOf(attrs) {
342
+ for (const key of ["messaging.destination.name", "messaging.destination"]) {
343
+ const v = attrs[key];
344
+ if (typeof v === "string" && v.length > 0) return v;
345
+ }
346
+ return void 0;
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
+ }
339
371
  function parseOtlpRequest(body) {
340
372
  const out = [];
341
373
  for (const rs of body.resourceSpans ?? []) {
@@ -362,6 +394,14 @@ function parseOtlpRequest(body) {
362
394
  attributes: attrs,
363
395
  dbSystem: typeof attrs["db.system"] === "string" ? attrs["db.system"] : void 0,
364
396
  dbName: typeof attrs["db.name"] === "string" ? attrs["db.name"] : void 0,
397
+ messagingSystem: typeof attrs["messaging.system"] === "string" ? attrs["messaging.system"] : void 0,
398
+ messagingDestination: messagingDestinationOf(attrs),
399
+ graphqlOperationName: typeof attrs["graphql.operation.name"] === "string" && attrs["graphql.operation.name"].length > 0 ? attrs["graphql.operation.name"] : void 0,
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),
365
405
  statusCode: span.status?.code,
366
406
  errorMessage: span.status?.message,
367
407
  exception: extractExceptionFromEvents(span.events)
@@ -373,10 +413,10 @@ function parseOtlpRequest(body) {
373
413
  return out;
374
414
  }
375
415
  function loadProtoRoot() {
376
- const here = import_node_path42.default.dirname((0, import_node_url3.fileURLToPath)(importMetaUrl));
377
- const protoRoot = import_node_path42.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");
378
418
  const root = new import_protobufjs.default.Root();
379
- root.resolvePath = (_origin, target) => import_node_path42.default.resolve(protoRoot, target);
419
+ root.resolvePath = (_origin, target) => import_node_path45.default.resolve(protoRoot, target);
380
420
  root.loadSync(
381
421
  "opentelemetry/proto/collector/trace/v1/trace_service.proto",
382
422
  { keepCase: true }
@@ -421,7 +461,21 @@ async function buildOtelReceiver(opts) {
421
461
  logger: false,
422
462
  bodyLimit: opts.bodyLimit ?? 16 * 1024 * 1024
423
463
  });
424
- mountBearerAuth(app, { token: opts.authToken, trustProxy: opts.trustProxy });
464
+ const REJECT_WARN_INTERVAL_MS = 6e4;
465
+ let lastRejectWarnAt = 0;
466
+ const warnRejectedOtlp = () => {
467
+ const now = Date.now();
468
+ if (now - lastRejectWarnAt < REJECT_WARN_INTERVAL_MS) return;
469
+ lastRejectWarnAt = now;
470
+ console.warn(
471
+ "[neatd] rejecting OTLP spans on /v1/traces \u2014 missing or invalid bearer token (set NEAT_OTEL_TOKEN on the instrumented app)"
472
+ );
473
+ };
474
+ mountBearerAuth(app, {
475
+ token: opts.authToken,
476
+ trustProxy: opts.trustProxy,
477
+ onReject: warnRejectedOtlp
478
+ });
425
479
  const queue = [];
426
480
  let draining = false;
427
481
  let drainPromise = Promise.resolve();
@@ -590,12 +644,12 @@ async function listenSteppingOtlp(app, requestedPort, host) {
590
644
  }
591
645
  }
592
646
  }
593
- var import_node_path42, 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;
594
648
  var init_otel = __esm({
595
649
  "src/otel.ts"() {
596
650
  "use strict";
597
651
  init_cjs_shims();
598
- import_node_path42 = __toESM(require("path"), 1);
652
+ import_node_path45 = __toESM(require("path"), 1);
599
653
  import_node_url3 = require("url");
600
654
  import_fastify2 = __toESM(require("fastify"), 1);
601
655
  import_protobufjs = __toESM(require("protobufjs"), 1);
@@ -632,8 +686,8 @@ __export(cli_exports, {
632
686
  });
633
687
  module.exports = __toCommonJS(cli_exports);
634
688
  init_cjs_shims();
635
- var import_node_path52 = __toESM(require("path"), 1);
636
- var import_node_fs34 = require("fs");
689
+ var import_node_path55 = __toESM(require("path"), 1);
690
+ var import_node_fs35 = require("fs");
637
691
 
638
692
  // src/banner.ts
639
693
  init_cjs_shims();
@@ -1198,19 +1252,19 @@ function confidenceFromMix(edges, now = Date.now()) {
1198
1252
  function longestIncomingWalk(graph, start, maxDepth) {
1199
1253
  let best = { path: [start], edges: [] };
1200
1254
  const visited = /* @__PURE__ */ new Set([start]);
1201
- function step(node, path53, edges) {
1202
- if (path53.length > best.path.length) {
1203
- best = { path: [...path53], edges: [...edges] };
1255
+ function step(node, path56, edges) {
1256
+ if (path56.length > best.path.length) {
1257
+ best = { path: [...path56], edges: [...edges] };
1204
1258
  }
1205
- if (path53.length - 1 >= maxDepth) return;
1259
+ if (path56.length - 1 >= maxDepth) return;
1206
1260
  const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
1207
1261
  for (const [srcId, edge] of incoming) {
1208
1262
  if (visited.has(srcId)) continue;
1209
1263
  visited.add(srcId);
1210
- path53.push(srcId);
1264
+ path56.push(srcId);
1211
1265
  edges.push(edge);
1212
- step(srcId, path53, edges);
1213
- path53.pop();
1266
+ step(srcId, path56, edges);
1267
+ path56.pop();
1214
1268
  edges.pop();
1215
1269
  visited.delete(srcId);
1216
1270
  }
@@ -1218,14 +1272,14 @@ function longestIncomingWalk(graph, start, maxDepth) {
1218
1272
  step(start, [start], []);
1219
1273
  return best;
1220
1274
  }
1221
- function databaseRootCauseShape(graph, origin, walk) {
1275
+ function databaseRootCauseShape(graph, origin, walk3) {
1222
1276
  const targetDb = origin;
1223
1277
  const candidatePairs = compatPairs().filter((p) => p.engine === targetDb.engine);
1224
1278
  if (candidatePairs.length === 0) return null;
1225
- for (const id of walk.path) {
1279
+ for (const id of walk3.path) {
1226
1280
  const owner = resolveOwningService(graph, id);
1227
1281
  if (!owner) continue;
1228
- const { id: serviceId3, svc } = owner;
1282
+ const { id: serviceId4, svc } = owner;
1229
1283
  const deps = svc.dependencies ?? {};
1230
1284
  for (const pair of candidatePairs) {
1231
1285
  const declared = deps[pair.driver];
@@ -1238,7 +1292,7 @@ function databaseRootCauseShape(graph, origin, walk) {
1238
1292
  );
1239
1293
  if (!result.compatible) {
1240
1294
  return {
1241
- rootCauseNode: serviceId3,
1295
+ rootCauseNode: serviceId4,
1242
1296
  rootCauseReason: result.reason ?? "incompatible driver",
1243
1297
  ...result.minDriverVersion ? {
1244
1298
  fixRecommendation: `Upgrade ${svc.name} ${pair.driver} driver to >= ${result.minDriverVersion}`
@@ -1249,11 +1303,11 @@ function databaseRootCauseShape(graph, origin, walk) {
1249
1303
  }
1250
1304
  return null;
1251
1305
  }
1252
- function serviceRootCauseShape(graph, _origin, walk) {
1253
- for (const id of walk.path) {
1306
+ function serviceRootCauseShape(graph, _origin, walk3) {
1307
+ for (const id of walk3.path) {
1254
1308
  const owner = resolveOwningService(graph, id);
1255
1309
  if (!owner) continue;
1256
- const { id: serviceId3, svc } = owner;
1310
+ const { id: serviceId4, svc } = owner;
1257
1311
  const deps = svc.dependencies ?? {};
1258
1312
  const serviceNodeEngine = svc.nodeEngine;
1259
1313
  for (const constraint of nodeEngineConstraints()) {
@@ -1262,7 +1316,7 @@ function serviceRootCauseShape(graph, _origin, walk) {
1262
1316
  const result = checkNodeEngineConstraint(constraint, declared, serviceNodeEngine);
1263
1317
  if (!result.compatible && result.reason) {
1264
1318
  return {
1265
- rootCauseNode: serviceId3,
1319
+ rootCauseNode: serviceId4,
1266
1320
  rootCauseReason: result.reason,
1267
1321
  ...result.requiredNodeVersion ? {
1268
1322
  fixRecommendation: `Bump ${svc.name}'s engines.node to >= ${result.requiredNodeVersion}`
@@ -1277,7 +1331,7 @@ function serviceRootCauseShape(graph, _origin, walk) {
1277
1331
  const result = checkPackageConflict(conflict, declared, requiredDeclared);
1278
1332
  if (!result.compatible && result.reason) {
1279
1333
  return {
1280
- rootCauseNode: serviceId3,
1334
+ rootCauseNode: serviceId4,
1281
1335
  rootCauseReason: result.reason,
1282
1336
  fixRecommendation: `Upgrade ${svc.name}'s ${conflict.requires.name} to >= ${conflict.requires.minVersion}`
1283
1337
  };
@@ -1286,10 +1340,10 @@ function serviceRootCauseShape(graph, _origin, walk) {
1286
1340
  }
1287
1341
  return null;
1288
1342
  }
1289
- function fileRootCauseShape(graph, origin, walk) {
1343
+ function fileRootCauseShape(graph, origin, walk3) {
1290
1344
  const owner = resolveOwningService(graph, origin.id);
1291
1345
  if (!owner) return null;
1292
- return serviceRootCauseShape(graph, owner.svc, walk);
1346
+ return serviceRootCauseShape(graph, owner.svc, walk3);
1293
1347
  }
1294
1348
  var rootCauseShapes = {
1295
1349
  [import_types.NodeType.DatabaseNode]: databaseRootCauseShape,
@@ -1301,16 +1355,16 @@ function getRootCause(graph, errorNodeId, errorEvent, incidents) {
1301
1355
  const origin = graph.getNodeAttributes(errorNodeId);
1302
1356
  const shape = rootCauseShapes[origin.type];
1303
1357
  if (shape) {
1304
- const walk = longestIncomingWalk(graph, errorNodeId, ROOT_CAUSE_MAX_DEPTH);
1305
- const match = shape(graph, origin, walk);
1358
+ const walk3 = longestIncomingWalk(graph, errorNodeId, ROOT_CAUSE_MAX_DEPTH);
1359
+ const match = shape(graph, origin, walk3);
1306
1360
  if (match) {
1307
1361
  const reason = errorEvent ? `${match.rootCauseReason} (observed error: ${errorEvent.errorMessage})` : match.rootCauseReason;
1308
1362
  return import_types.RootCauseResultSchema.parse({
1309
1363
  rootCauseNode: match.rootCauseNode,
1310
1364
  rootCauseReason: reason,
1311
- traversalPath: walk.path,
1312
- edgeProvenances: walk.edges.map((e) => e.provenance),
1313
- confidence: confidenceFromMix(walk.edges),
1365
+ traversalPath: walk3.path,
1366
+ edgeProvenances: walk3.edges.map((e) => e.provenance),
1367
+ confidence: confidenceFromMix(walk3.edges),
1314
1368
  fixRecommendation: match.fixRecommendation
1315
1369
  });
1316
1370
  }
@@ -1368,9 +1422,9 @@ function rootCauseFromIncidents(nodeId, incidents, errorEvent) {
1368
1422
  function isFailingCallEdge(e) {
1369
1423
  return e.type === import_types.EdgeType.CALLS && (e.signal?.errorCount ?? 0) > 0;
1370
1424
  }
1371
- function callSourcesForService(graph, serviceId3) {
1372
- const ids = [serviceId3];
1373
- for (const edgeId of graph.outboundEdges(serviceId3)) {
1425
+ function callSourcesForService(graph, serviceId4) {
1426
+ const ids = [serviceId4];
1427
+ for (const edgeId of graph.outboundEdges(serviceId4)) {
1374
1428
  const e = graph.getEdgeAttributes(edgeId);
1375
1429
  if (e.type !== import_types.EdgeType.CONTAINS) continue;
1376
1430
  const tgt = graph.getNodeAttributes(e.target);
@@ -1387,9 +1441,9 @@ function failingCallDominates(e, id, curEdge, curId) {
1387
1441
  }
1388
1442
  return id < curId;
1389
1443
  }
1390
- function dominantFailingCall(graph, serviceId3, visited) {
1444
+ function dominantFailingCall(graph, serviceId4, visited) {
1391
1445
  let best = null;
1392
- for (const src of callSourcesForService(graph, serviceId3)) {
1446
+ for (const src of callSourcesForService(graph, serviceId4)) {
1393
1447
  for (const edgeId of graph.outboundEdges(src)) {
1394
1448
  const e = graph.getEdgeAttributes(edgeId);
1395
1449
  if (!isFailingCallEdge(e)) continue;
@@ -1404,26 +1458,26 @@ function dominantFailingCall(graph, serviceId3, visited) {
1404
1458
  return best;
1405
1459
  }
1406
1460
  function followFailingCallChain(graph, originServiceId, maxDepth) {
1407
- const path53 = [originServiceId];
1461
+ const path56 = [originServiceId];
1408
1462
  const edges = [];
1409
1463
  const visited = /* @__PURE__ */ new Set([originServiceId]);
1410
1464
  let current = originServiceId;
1411
1465
  for (let depth = 0; depth < maxDepth; depth++) {
1412
1466
  const hop = dominantFailingCall(graph, current, visited);
1413
1467
  if (!hop) break;
1414
- path53.push(hop.nextService);
1468
+ path56.push(hop.nextService);
1415
1469
  edges.push(hop.edge);
1416
1470
  visited.add(hop.nextService);
1417
1471
  current = hop.nextService;
1418
1472
  }
1419
1473
  if (edges.length === 0) return null;
1420
- return { path: path53, edges, culprit: current };
1474
+ return { path: path56, edges, culprit: current };
1421
1475
  }
1422
1476
  function crossServiceRootCause(graph, originId, incidents, errorEvent) {
1423
1477
  const chain = followFailingCallChain(graph, originId, ROOT_CAUSE_MAX_DEPTH);
1424
1478
  if (!chain) return null;
1425
1479
  const culprit = chain.culprit;
1426
- const path53 = [...chain.path];
1480
+ const path56 = [...chain.path];
1427
1481
  const edgeProvenances = chain.edges.map((e) => e.provenance);
1428
1482
  const baseConfidence = confidenceFromMix(chain.edges);
1429
1483
  const confidence = Math.max(0, Math.min(1, baseConfidence * INCIDENT_ROOT_CAUSE_CONFIDENCE));
@@ -1431,14 +1485,14 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
1431
1485
  if (loc) {
1432
1486
  let rootCauseNode = culprit;
1433
1487
  if (loc.fileNode) {
1434
- path53.push(loc.fileNode);
1488
+ path56.push(loc.fileNode);
1435
1489
  edgeProvenances.push(import_types.Provenance.OBSERVED);
1436
1490
  rootCauseNode = loc.fileNode;
1437
1491
  }
1438
1492
  return import_types.RootCauseResultSchema.parse({
1439
1493
  rootCauseNode,
1440
1494
  rootCauseReason: loc.rootCauseReason,
1441
- traversalPath: path53,
1495
+ traversalPath: path56,
1442
1496
  edgeProvenances,
1443
1497
  confidence,
1444
1498
  ...loc.fixRecommendation ? { fixRecommendation: loc.fixRecommendation } : {}
@@ -1450,7 +1504,7 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
1450
1504
  return import_types.RootCauseResultSchema.parse({
1451
1505
  rootCauseNode: culprit,
1452
1506
  rootCauseReason: `${culpritName} is failing downstream calls (${errs} observed error${errs === 1 ? "" : "s"})`,
1453
- traversalPath: path53,
1507
+ traversalPath: path56,
1454
1508
  edgeProvenances,
1455
1509
  confidence,
1456
1510
  fixRecommendation: `Inspect ${culpritName}'s failing handler`
@@ -2374,10 +2428,82 @@ var STITCH_EDGE_TYPES = /* @__PURE__ */ new Set([
2374
2428
  ]);
2375
2429
  var WIRE_SPAN_KIND_CLIENT = 3;
2376
2430
  var WIRE_SPAN_KIND_PRODUCER = 4;
2431
+ var WIRE_SPAN_KIND_CONSUMER = 5;
2377
2432
  function spanMintsObservedEdge(kind) {
2378
2433
  if (kind === void 0 || kind === 0) return true;
2379
2434
  return kind === WIRE_SPAN_KIND_CLIENT || kind === WIRE_SPAN_KIND_PRODUCER;
2380
2435
  }
2436
+ function spanMintsMessagingEdge(kind) {
2437
+ return kind === WIRE_SPAN_KIND_PRODUCER || kind === WIRE_SPAN_KIND_CONSUMER;
2438
+ }
2439
+ function spanServesGraphqlOperation(kind) {
2440
+ return kind !== WIRE_SPAN_KIND_CLIENT && kind !== WIRE_SPAN_KIND_PRODUCER && kind !== WIRE_SPAN_KIND_CONSUMER;
2441
+ }
2442
+ function ensureGraphqlOperationNode(graph, serviceName, operationType, operationName) {
2443
+ const id = (0, import_types3.graphqlOperationId)(serviceName, operationType, operationName);
2444
+ if (graph.hasNode(id)) return id;
2445
+ const node = {
2446
+ id,
2447
+ type: import_types3.NodeType.GraphQLOperationNode,
2448
+ name: operationName,
2449
+ service: serviceName,
2450
+ operationType: operationType.toLowerCase(),
2451
+ operationName,
2452
+ discoveredVia: "otel"
2453
+ };
2454
+ graph.addNode(id, node);
2455
+ return id;
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
+ }
2491
+ function messagingDestinationKind(system) {
2492
+ return `${system}-topic`;
2493
+ }
2494
+ function ensureMessagingDestinationNode(graph, system, destination) {
2495
+ const id = (0, import_types3.infraId)(messagingDestinationKind(system), destination);
2496
+ if (graph.hasNode(id)) return id;
2497
+ const node = {
2498
+ id,
2499
+ type: import_types3.NodeType.InfraNode,
2500
+ name: destination,
2501
+ provider: "self",
2502
+ kind: messagingDestinationKind(system)
2503
+ };
2504
+ graph.addNode(id, node);
2505
+ return id;
2506
+ }
2381
2507
  var PARENT_SPAN_CACHE_SIZE = 1e4;
2382
2508
  var PARENT_SPAN_CACHE_TTL_MS = 5 * 60 * 1e3;
2383
2509
  var parentSpanCache = /* @__PURE__ */ new Map();
@@ -2471,6 +2597,21 @@ function ensureDatabaseNode(graph, host, engine) {
2471
2597
  graph.addNode(id, node);
2472
2598
  return id;
2473
2599
  }
2600
+ function ensureLocalDatabaseNode(graph, serviceName, name, engine) {
2601
+ const id = (0, import_types3.localDatabaseId)(serviceName, name);
2602
+ if (graph.hasNode(id)) return id;
2603
+ const node = {
2604
+ id,
2605
+ type: import_types3.NodeType.DatabaseNode,
2606
+ name,
2607
+ engine,
2608
+ engineVersion: "unknown",
2609
+ compatibleDrivers: [],
2610
+ discoveredVia: "otel"
2611
+ };
2612
+ graph.addNode(id, node);
2613
+ return id;
2614
+ }
2474
2615
  function ensureFrontierNode(graph, host, ts) {
2475
2616
  const id = frontierIdFor(host);
2476
2617
  if (graph.hasNode(id)) {
@@ -2722,10 +2863,21 @@ async function handleSpan(ctx, span) {
2722
2863
  let affectedNode = sourceId;
2723
2864
  const mintsFromCallerSide = spanMintsObservedEdge(span.kind);
2724
2865
  if (span.dbSystem) {
2725
- const host = pickAddress(span);
2726
- if (mintsFromCallerSide && host) {
2727
- ensureDatabaseNode(ctx.graph, host, span.dbSystem);
2728
- const targetId = (0, import_types3.databaseId)(host);
2866
+ if (mintsFromCallerSide) {
2867
+ const host = pickAddress(span);
2868
+ let targetId;
2869
+ if (host) {
2870
+ ensureDatabaseNode(ctx.graph, host, span.dbSystem);
2871
+ targetId = (0, import_types3.databaseId)(host);
2872
+ } else {
2873
+ const localName = span.dbName ?? span.dbSystem;
2874
+ targetId = ensureLocalDatabaseNode(
2875
+ ctx.graph,
2876
+ span.service,
2877
+ localName,
2878
+ span.dbSystem
2879
+ );
2880
+ }
2729
2881
  const result = upsertObservedEdge(
2730
2882
  ctx.graph,
2731
2883
  import_types3.EdgeType.CONNECTS_TO,
@@ -2737,6 +2889,68 @@ async function handleSpan(ctx, span) {
2737
2889
  );
2738
2890
  if (result) affectedNode = targetId;
2739
2891
  }
2892
+ } else if (span.messagingSystem && span.messagingDestination && spanMintsMessagingEdge(span.kind)) {
2893
+ const targetId = ensureMessagingDestinationNode(
2894
+ ctx.graph,
2895
+ span.messagingSystem,
2896
+ span.messagingDestination
2897
+ );
2898
+ const edgeType = span.kind === WIRE_SPAN_KIND_CONSUMER ? import_types3.EdgeType.CONSUMES_FROM : import_types3.EdgeType.PUBLISHES_TO;
2899
+ const result = upsertObservedEdge(
2900
+ ctx.graph,
2901
+ edgeType,
2902
+ observedSource(),
2903
+ targetId,
2904
+ ts,
2905
+ isError,
2906
+ callSiteEvidence
2907
+ );
2908
+ if (result) affectedNode = targetId;
2909
+ } else if (span.graphqlOperationName && span.graphqlOperationType && spanServesGraphqlOperation(span.kind)) {
2910
+ const targetId = ensureGraphqlOperationNode(
2911
+ ctx.graph,
2912
+ span.service,
2913
+ span.graphqlOperationType,
2914
+ span.graphqlOperationName
2915
+ );
2916
+ const result = upsertObservedEdge(
2917
+ ctx.graph,
2918
+ import_types3.EdgeType.CONTAINS,
2919
+ observedSource(),
2920
+ targetId,
2921
+ ts,
2922
+ isError,
2923
+ callSiteEvidence
2924
+ );
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;
2740
2954
  } else {
2741
2955
  const host = pickAddress(span);
2742
2956
  let resolvedViaAddress = false;
@@ -2846,29 +3060,29 @@ function promoteFrontierNodes(graph, opts = {}) {
2846
3060
  toPromote.push({ frontierId: id, serviceId: target });
2847
3061
  });
2848
3062
  let promoted = 0;
2849
- for (const { frontierId: frontierId2, serviceId: serviceId3 } of toPromote) {
3063
+ for (const { frontierId: frontierId2, serviceId: serviceId4 } of toPromote) {
2850
3064
  if (opts.policies && opts.policies.length > 0 && opts.policyCtx) {
2851
3065
  const gate = canPromoteFrontier(graph, frontierId2, opts.policies, opts.policyCtx);
2852
3066
  if (!gate.allowed) {
2853
3067
  continue;
2854
3068
  }
2855
3069
  }
2856
- rewireFrontierEdges(graph, frontierId2, serviceId3);
3070
+ rewireFrontierEdges(graph, frontierId2, serviceId4);
2857
3071
  graph.dropNode(frontierId2);
2858
3072
  promoted++;
2859
3073
  }
2860
3074
  return promoted;
2861
3075
  }
2862
- function rewireFrontierEdges(graph, frontierId2, serviceId3) {
3076
+ function rewireFrontierEdges(graph, frontierId2, serviceId4) {
2863
3077
  const inbound = [...graph.inboundEdges(frontierId2)];
2864
3078
  const outbound = [...graph.outboundEdges(frontierId2)];
2865
3079
  for (const edgeId of inbound) {
2866
3080
  const edge = graph.getEdgeAttributes(edgeId);
2867
- rebuildEdge(graph, edge, edge.source, serviceId3, edgeId);
3081
+ rebuildEdge(graph, edge, edge.source, serviceId4, edgeId);
2868
3082
  }
2869
3083
  for (const edgeId of outbound) {
2870
3084
  const edge = graph.getEdgeAttributes(edgeId);
2871
- rebuildEdge(graph, edge, serviceId3, edge.target, edgeId);
3085
+ rebuildEdge(graph, edge, serviceId4, edge.target, edgeId);
2872
3086
  }
2873
3087
  }
2874
3088
  function rebuildEdge(graph, edge, newSource, newTarget, oldEdgeId) {
@@ -3642,9 +3856,9 @@ var K8S_KINDS_WITH_HOSTNAMES = /* @__PURE__ */ new Set([
3642
3856
  "StatefulSet",
3643
3857
  "DaemonSet"
3644
3858
  ]);
3645
- function addAliases(graph, serviceId3, candidates) {
3646
- if (!graph.hasNode(serviceId3)) return;
3647
- const node = graph.getNodeAttributes(serviceId3);
3859
+ function addAliases(graph, serviceId4, candidates) {
3860
+ if (!graph.hasNode(serviceId4)) return;
3861
+ const node = graph.getNodeAttributes(serviceId4);
3648
3862
  if (node.type !== import_types6.NodeType.ServiceNode) return;
3649
3863
  const set = new Set(node.aliases ?? []);
3650
3864
  for (const c of candidates) {
@@ -3654,7 +3868,7 @@ function addAliases(graph, serviceId3, candidates) {
3654
3868
  }
3655
3869
  if (set.size === 0) return;
3656
3870
  const updated = { ...node, aliases: [...set].sort() };
3657
- graph.replaceNodeAttributes(serviceId3, updated);
3871
+ graph.replaceNodeAttributes(serviceId4, updated);
3658
3872
  }
3659
3873
  function indexServicesByName(services) {
3660
3874
  const map = /* @__PURE__ */ new Map();
@@ -3687,12 +3901,12 @@ async function collectComposeAliases(graph, scanPath, serviceIndex) {
3687
3901
  }
3688
3902
  if (!compose?.services) return;
3689
3903
  for (const [composeName, svc] of Object.entries(compose.services)) {
3690
- const serviceId3 = serviceIndex.get(composeName);
3691
- if (!serviceId3) continue;
3904
+ const serviceId4 = serviceIndex.get(composeName);
3905
+ if (!serviceId4) continue;
3692
3906
  const aliases = /* @__PURE__ */ new Set([composeName]);
3693
3907
  if (svc.container_name) aliases.add(svc.container_name);
3694
3908
  if (svc.hostname) aliases.add(svc.hostname);
3695
- addAliases(graph, serviceId3, aliases);
3909
+ addAliases(graph, serviceId4, aliases);
3696
3910
  }
3697
3911
  }
3698
3912
  var LABEL_KEYS = /* @__PURE__ */ new Set([
@@ -3805,16 +4019,28 @@ init_cjs_shims();
3805
4019
  var import_node_fs11 = require("fs");
3806
4020
  var import_node_path11 = __toESM(require("path"), 1);
3807
4021
  var import_types7 = require("@neat.is/types");
4022
+ function buildServiceHostIndex(services) {
4023
+ const knownHosts = /* @__PURE__ */ new Set();
4024
+ const hostToNodeId = /* @__PURE__ */ new Map();
4025
+ for (const service of services) {
4026
+ const base = import_node_path11.default.basename(service.dir);
4027
+ knownHosts.add(base);
4028
+ knownHosts.add(service.pkg.name);
4029
+ hostToNodeId.set(base, service.node.id);
4030
+ hostToNodeId.set(service.pkg.name, service.node.id);
4031
+ }
4032
+ return { knownHosts, hostToNodeId };
4033
+ }
3808
4034
  async function walkSourceFiles(dir) {
3809
4035
  const out = [];
3810
- async function walk(current) {
4036
+ async function walk3(current) {
3811
4037
  const entries = await import_node_fs11.promises.readdir(current, { withFileTypes: true }).catch(() => []);
3812
4038
  for (const entry2 of entries) {
3813
4039
  const full = import_node_path11.default.join(current, entry2.name);
3814
4040
  if (entry2.isDirectory()) {
3815
4041
  if (IGNORED_DIRS.has(entry2.name)) continue;
3816
4042
  if (await isPythonVenvDir(full)) continue;
3817
- await walk(full);
4043
+ await walk3(full);
3818
4044
  } else if (entry2.isFile() && SERVICE_FILE_EXTENSIONS.has(import_node_path11.default.extname(entry2.name)) && // Skip NEAT's own generated `otel-init.*` bootstrap — extracting it
3819
4045
  // would attribute our instrumentation imports to the user's service.
3820
4046
  !isNeatAuthoredSourceFile(entry2.name)) {
@@ -3822,7 +4048,7 @@ async function walkSourceFiles(dir) {
3822
4048
  }
3823
4049
  }
3824
4050
  }
3825
- await walk(dir);
4051
+ await walk3(dir);
3826
4052
  return out;
3827
4053
  }
3828
4054
  async function loadSourceFiles(dir) {
@@ -4916,20 +5142,20 @@ var import_node_path22 = __toESM(require("path"), 1);
4916
5142
  var import_types10 = require("@neat.is/types");
4917
5143
  async function walkConfigFiles(dir) {
4918
5144
  const out = [];
4919
- async function walk(current) {
5145
+ async function walk3(current) {
4920
5146
  const entries = await import_node_fs15.promises.readdir(current, { withFileTypes: true });
4921
5147
  for (const entry2 of entries) {
4922
5148
  const full = import_node_path22.default.join(current, entry2.name);
4923
5149
  if (entry2.isDirectory()) {
4924
5150
  if (IGNORED_DIRS.has(entry2.name)) continue;
4925
5151
  if (await isPythonVenvDir(full)) continue;
4926
- await walk(full);
5152
+ await walk3(full);
4927
5153
  } else if (entry2.isFile() && isConfigFile(entry2.name).match) {
4928
5154
  out.push(full);
4929
5155
  }
4930
5156
  }
4931
5157
  }
4932
- await walk(dir);
5158
+ await walk3(dir);
4933
5159
  return out;
4934
5160
  }
4935
5161
  async function addConfigNodes(graph, services, scanPath) {
@@ -4977,17 +5203,468 @@ async function addConfigNodes(graph, services, scanPath) {
4977
5203
  return { nodesAdded, edgesAdded };
4978
5204
  }
4979
5205
 
5206
+ // src/extract/routes.ts
5207
+ init_cjs_shims();
5208
+ var import_node_path23 = __toESM(require("path"), 1);
5209
+ var import_tree_sitter2 = __toESM(require("tree-sitter"), 1);
5210
+ var import_tree_sitter_javascript2 = __toESM(require("tree-sitter-javascript"), 1);
5211
+ var import_types11 = require("@neat.is/types");
5212
+ var PARSE_CHUNK2 = 16384;
5213
+ function parseSource2(parser, source) {
5214
+ return parser.parse(
5215
+ (index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK2)
5216
+ );
5217
+ }
5218
+ function makeJsParser2() {
5219
+ const p = new import_tree_sitter2.default();
5220
+ p.setLanguage(import_tree_sitter_javascript2.default);
5221
+ return p;
5222
+ }
5223
+ var ROUTER_METHODS = /* @__PURE__ */ new Set([
5224
+ "get",
5225
+ "post",
5226
+ "put",
5227
+ "patch",
5228
+ "delete",
5229
+ "options",
5230
+ "head",
5231
+ "all"
5232
+ ]);
5233
+ var NEXT_APP_METHODS = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]);
5234
+ var JS_ROUTE_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx"]);
5235
+ function canonicalizeTemplate(raw) {
5236
+ let p = raw.split("?")[0].split("#")[0];
5237
+ if (!p.startsWith("/")) p = "/" + p;
5238
+ if (p.length > 1 && p.endsWith("/")) p = p.slice(0, -1);
5239
+ return p;
5240
+ }
5241
+ function isDynamicSegment(seg) {
5242
+ if (seg.length === 0) return false;
5243
+ if (seg.includes(":")) return true;
5244
+ if (seg.startsWith("{") || seg.startsWith("[")) return true;
5245
+ if (/^\d+$/.test(seg)) return true;
5246
+ if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(seg)) return true;
5247
+ if (/^[0-9a-f]{24,}$/i.test(seg)) return true;
5248
+ return false;
5249
+ }
5250
+ function normalizePathTemplate(raw) {
5251
+ const canonical = canonicalizeTemplate(raw);
5252
+ const segments = canonical.split("/").filter((s) => s.length > 0);
5253
+ const normalised = segments.map((seg) => isDynamicSegment(seg) ? ":param" : seg.toLowerCase());
5254
+ return "/" + normalised.join("/");
5255
+ }
5256
+ function walk(node, visit) {
5257
+ visit(node);
5258
+ for (let i = 0; i < node.namedChildCount; i++) {
5259
+ const child = node.namedChild(i);
5260
+ if (child) walk(child, visit);
5261
+ }
5262
+ }
5263
+ function staticStringText(node) {
5264
+ if (node.type === "string") {
5265
+ for (let i = 0; i < node.namedChildCount; i++) {
5266
+ const child = node.namedChild(i);
5267
+ if (child?.type === "string_fragment") return child.text;
5268
+ }
5269
+ return "";
5270
+ }
5271
+ if (node.type === "template_string") {
5272
+ for (let i = 0; i < node.namedChildCount; i++) {
5273
+ if (node.namedChild(i)?.type === "template_substitution") return null;
5274
+ }
5275
+ const raw = node.text;
5276
+ return raw.length >= 2 ? raw.slice(1, -1) : "";
5277
+ }
5278
+ return null;
5279
+ }
5280
+ function objectStringProp(objNode, key) {
5281
+ for (let i = 0; i < objNode.namedChildCount; i++) {
5282
+ const pair = objNode.namedChild(i);
5283
+ if (!pair || pair.type !== "pair") continue;
5284
+ const k = pair.childForFieldName("key");
5285
+ if (!k) continue;
5286
+ const kText = k.type === "string" ? staticStringText(k) : k.text;
5287
+ if (kText !== key) continue;
5288
+ const v = pair.childForFieldName("value");
5289
+ if (v) return staticStringText(v);
5290
+ }
5291
+ return null;
5292
+ }
5293
+ function fastifyRouteMethods(objNode) {
5294
+ for (let i = 0; i < objNode.namedChildCount; i++) {
5295
+ const pair = objNode.namedChild(i);
5296
+ if (!pair || pair.type !== "pair") continue;
5297
+ const k = pair.childForFieldName("key");
5298
+ const kText = k ? k.type === "string" ? staticStringText(k) : k.text : null;
5299
+ if (kText !== "method") continue;
5300
+ const v = pair.childForFieldName("value");
5301
+ if (!v) return [];
5302
+ if (v.type === "string" || v.type === "template_string") {
5303
+ const s = staticStringText(v);
5304
+ return s ? [s.toUpperCase()] : [];
5305
+ }
5306
+ if (v.type === "array") {
5307
+ const out = [];
5308
+ for (let j = 0; j < v.namedChildCount; j++) {
5309
+ const el = v.namedChild(j);
5310
+ if (el && (el.type === "string" || el.type === "template_string")) {
5311
+ const s = staticStringText(el);
5312
+ if (s) out.push(s.toUpperCase());
5313
+ }
5314
+ }
5315
+ return out;
5316
+ }
5317
+ }
5318
+ return [];
5319
+ }
5320
+ function serverRoutesFromSource(source, parser, hasExpress, hasFastify) {
5321
+ const tree = parseSource2(parser, source);
5322
+ const out = [];
5323
+ const framework = hasExpress ? "express" : "fastify";
5324
+ walk(tree.rootNode, (node) => {
5325
+ if (node.type !== "call_expression") return;
5326
+ const fn = node.childForFieldName("function");
5327
+ if (!fn || fn.type !== "member_expression") return;
5328
+ const prop = fn.childForFieldName("property");
5329
+ if (!prop) return;
5330
+ const method = prop.text.toLowerCase();
5331
+ const args = node.childForFieldName("arguments");
5332
+ const first = args?.namedChild(0);
5333
+ if (!first) return;
5334
+ const line = node.startPosition.row + 1;
5335
+ if (ROUTER_METHODS.has(method)) {
5336
+ const p = staticStringText(first);
5337
+ if (p && p.startsWith("/")) {
5338
+ out.push({
5339
+ method: method === "all" ? "ALL" : method.toUpperCase(),
5340
+ pathTemplate: canonicalizeTemplate(p),
5341
+ line,
5342
+ framework
5343
+ });
5344
+ }
5345
+ return;
5346
+ }
5347
+ if (method === "route" && hasFastify && first.type === "object") {
5348
+ const url = objectStringProp(first, "url");
5349
+ if (!url || !url.startsWith("/")) return;
5350
+ const methods = fastifyRouteMethods(first);
5351
+ const list = methods.length > 0 ? methods : ["ALL"];
5352
+ for (const m of list) {
5353
+ out.push({
5354
+ method: m === "ALL" ? "ALL" : m.toUpperCase(),
5355
+ pathTemplate: canonicalizeTemplate(url),
5356
+ line,
5357
+ framework: "fastify"
5358
+ });
5359
+ }
5360
+ }
5361
+ });
5362
+ return out;
5363
+ }
5364
+ function segmentsOf(relFile) {
5365
+ return toPosix2(relFile).split("/").filter((s) => s.length > 0);
5366
+ }
5367
+ function isNextAppRouteFile(relFile) {
5368
+ const segs = segmentsOf(relFile);
5369
+ if (!segs.includes("app")) return false;
5370
+ const base = segs[segs.length - 1] ?? "";
5371
+ return /^route\.(?:js|jsx|mjs|cjs|ts|tsx)$/.test(base);
5372
+ }
5373
+ function isNextPagesApiFile(relFile) {
5374
+ const segs = segmentsOf(relFile);
5375
+ const pagesIdx = segs.indexOf("pages");
5376
+ if (pagesIdx === -1 || segs[pagesIdx + 1] !== "api") return false;
5377
+ const base = segs[segs.length - 1] ?? "";
5378
+ if (/^_(app|document|middleware)\./.test(base)) return false;
5379
+ return JS_ROUTE_EXTENSIONS.has(import_node_path23.default.extname(base));
5380
+ }
5381
+ function nextSegment(seg) {
5382
+ if (seg.startsWith("(") && seg.endsWith(")")) return null;
5383
+ const catchAll = seg.match(/^\[\[?\.\.\.(.+?)\]?\]$/);
5384
+ if (catchAll) return ":" + catchAll[1];
5385
+ const dynamic = seg.match(/^\[(.+?)\]$/);
5386
+ if (dynamic) return ":" + dynamic[1];
5387
+ return seg;
5388
+ }
5389
+ function nextAppPathTemplate(relFile) {
5390
+ const segs = segmentsOf(relFile);
5391
+ const appIdx = segs.lastIndexOf("app");
5392
+ const between = segs.slice(appIdx + 1, segs.length - 1);
5393
+ const parts = [];
5394
+ for (const seg of between) {
5395
+ const mapped = nextSegment(seg);
5396
+ if (mapped !== null) parts.push(mapped);
5397
+ }
5398
+ return "/" + parts.join("/");
5399
+ }
5400
+ function nextPagesApiPathTemplate(relFile) {
5401
+ const segs = segmentsOf(relFile);
5402
+ const pagesIdx = segs.indexOf("pages");
5403
+ const rest = segs.slice(pagesIdx + 1);
5404
+ const parts = [];
5405
+ for (let i = 0; i < rest.length; i++) {
5406
+ let seg = rest[i];
5407
+ if (i === rest.length - 1) {
5408
+ seg = seg.replace(/\.(?:js|jsx|mjs|cjs|ts|tsx)$/, "");
5409
+ if (seg === "index") continue;
5410
+ }
5411
+ const mapped = nextSegment(seg);
5412
+ if (mapped !== null) parts.push(mapped);
5413
+ }
5414
+ return "/" + parts.join("/");
5415
+ }
5416
+ function nextAppMethods(root) {
5417
+ const out = [];
5418
+ walk(root, (node) => {
5419
+ if (node.type !== "export_statement") return;
5420
+ const decl = node.childForFieldName("declaration");
5421
+ if (!decl) return;
5422
+ const line = node.startPosition.row + 1;
5423
+ if (decl.type === "function_declaration") {
5424
+ const name = decl.childForFieldName("name")?.text;
5425
+ if (name && NEXT_APP_METHODS.has(name)) out.push({ method: name, line });
5426
+ return;
5427
+ }
5428
+ if (decl.type === "lexical_declaration" || decl.type === "variable_declaration") {
5429
+ for (let i = 0; i < decl.namedChildCount; i++) {
5430
+ const d = decl.namedChild(i);
5431
+ if (d?.type !== "variable_declarator") continue;
5432
+ const name = d.childForFieldName("name")?.text;
5433
+ if (name && NEXT_APP_METHODS.has(name)) out.push({ method: name, line });
5434
+ }
5435
+ }
5436
+ });
5437
+ return out;
5438
+ }
5439
+ function nextRoutesFromFile(source, relFile, parser) {
5440
+ if (isNextAppRouteFile(relFile)) {
5441
+ const tree = parseSource2(parser, source);
5442
+ const template = nextAppPathTemplate(relFile);
5443
+ return nextAppMethods(tree.rootNode).map(({ method, line }) => ({
5444
+ method,
5445
+ pathTemplate: canonicalizeTemplate(template),
5446
+ line,
5447
+ framework: "next"
5448
+ }));
5449
+ }
5450
+ if (isNextPagesApiFile(relFile)) {
5451
+ return [
5452
+ {
5453
+ method: "ALL",
5454
+ pathTemplate: canonicalizeTemplate(nextPagesApiPathTemplate(relFile)),
5455
+ line: 1,
5456
+ framework: "next"
5457
+ }
5458
+ ];
5459
+ }
5460
+ return [];
5461
+ }
5462
+ async function addRoutes(graph, services) {
5463
+ const jsParser = makeJsParser2();
5464
+ let nodesAdded = 0;
5465
+ let edgesAdded = 0;
5466
+ for (const service of services) {
5467
+ const deps = {
5468
+ ...service.pkg.dependencies ?? {},
5469
+ ...service.pkg.devDependencies ?? {}
5470
+ };
5471
+ const hasExpress = deps["express"] !== void 0;
5472
+ const hasFastify = deps["fastify"] !== void 0;
5473
+ const hasNext = deps["next"] !== void 0;
5474
+ if (!hasExpress && !hasFastify && !hasNext) continue;
5475
+ const files = await loadSourceFiles(service.dir);
5476
+ for (const file of files) {
5477
+ if (isTestPath(file.path)) continue;
5478
+ if (!JS_ROUTE_EXTENSIONS.has(import_node_path23.default.extname(file.path))) continue;
5479
+ const relFile = toPosix2(import_node_path23.default.relative(service.dir, file.path));
5480
+ let routes;
5481
+ try {
5482
+ if (hasNext && (isNextAppRouteFile(relFile) || isNextPagesApiFile(relFile))) {
5483
+ routes = nextRoutesFromFile(file.content, relFile, jsParser);
5484
+ } else if (hasExpress || hasFastify) {
5485
+ routes = serverRoutesFromSource(file.content, jsParser, hasExpress, hasFastify);
5486
+ } else {
5487
+ routes = [];
5488
+ }
5489
+ } catch (err) {
5490
+ recordExtractionError("route extraction", file.path, err);
5491
+ continue;
5492
+ }
5493
+ if (routes.length === 0) continue;
5494
+ for (const route of routes) {
5495
+ const rid = (0, import_types11.routeId)(service.pkg.name, route.method, route.pathTemplate);
5496
+ if (!graph.hasNode(rid)) {
5497
+ const node = {
5498
+ id: rid,
5499
+ type: import_types11.NodeType.RouteNode,
5500
+ name: `${route.method} ${route.pathTemplate}`,
5501
+ service: service.pkg.name,
5502
+ method: route.method,
5503
+ pathTemplate: route.pathTemplate,
5504
+ path: relFile,
5505
+ line: route.line,
5506
+ framework: route.framework,
5507
+ discoveredVia: "static"
5508
+ };
5509
+ graph.addNode(rid, node);
5510
+ nodesAdded++;
5511
+ }
5512
+ const containsId = (0, import_types11.extractedEdgeId)(service.node.id, rid, import_types11.EdgeType.CONTAINS);
5513
+ if (!graph.hasEdge(containsId)) {
5514
+ const edge = {
5515
+ id: containsId,
5516
+ source: service.node.id,
5517
+ target: rid,
5518
+ type: import_types11.EdgeType.CONTAINS,
5519
+ provenance: import_types11.Provenance.EXTRACTED,
5520
+ confidence: (0, import_types11.confidenceForExtracted)("structural"),
5521
+ evidence: {
5522
+ file: relFile,
5523
+ line: route.line,
5524
+ snippet: snippet(file.content, route.line)
5525
+ }
5526
+ };
5527
+ graph.addEdgeWithKey(containsId, service.node.id, rid, edge);
5528
+ edgesAdded++;
5529
+ }
5530
+ }
5531
+ }
5532
+ }
5533
+ return { nodesAdded, edgesAdded };
5534
+ }
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
+
4980
5657
  // src/extract/calls/index.ts
4981
5658
  init_cjs_shims();
4982
- var import_types17 = require("@neat.is/types");
5659
+ var import_types20 = require("@neat.is/types");
4983
5660
 
4984
5661
  // src/extract/calls/http.ts
4985
5662
  init_cjs_shims();
4986
- var import_node_path23 = __toESM(require("path"), 1);
4987
- var import_tree_sitter2 = __toESM(require("tree-sitter"), 1);
4988
- var import_tree_sitter_javascript2 = __toESM(require("tree-sitter-javascript"), 1);
5663
+ var import_node_path25 = __toESM(require("path"), 1);
5664
+ var import_tree_sitter3 = __toESM(require("tree-sitter"), 1);
5665
+ var import_tree_sitter_javascript3 = __toESM(require("tree-sitter-javascript"), 1);
4989
5666
  var import_tree_sitter_python2 = __toESM(require("tree-sitter-python"), 1);
4990
- var import_types11 = require("@neat.is/types");
5667
+ var import_types13 = require("@neat.is/types");
4991
5668
  var STRING_LITERAL_NODE_TYPES = /* @__PURE__ */ new Set(["string_fragment", "string_content"]);
4992
5669
  var JSX_EXTERNAL_LINK_TAGS = /* @__PURE__ */ new Set(["a", "Link", "NavLink", "ExternalLink", "Anchor"]);
4993
5670
  function isInsideJsxExternalLink(node) {
@@ -5006,57 +5683,312 @@ function isInsideJsxExternalLink(node) {
5006
5683
  }
5007
5684
  cursor = cursor.parent;
5008
5685
  }
5009
- return false;
5686
+ return false;
5687
+ }
5688
+ function collectStringLiterals(node, out) {
5689
+ if (STRING_LITERAL_NODE_TYPES.has(node.type)) out.push({ text: node.text, node });
5690
+ for (let i = 0; i < node.namedChildCount; i++) {
5691
+ const child = node.namedChild(i);
5692
+ if (child) collectStringLiterals(child, out);
5693
+ }
5694
+ }
5695
+ var PARSE_CHUNK3 = 16384;
5696
+ function parseSource3(parser, source) {
5697
+ return parser.parse(
5698
+ (index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK3)
5699
+ );
5700
+ }
5701
+ function callsFromSource(source, parser, knownHosts) {
5702
+ const tree = parseSource3(parser, source);
5703
+ const literals = [];
5704
+ collectStringLiterals(tree.rootNode, literals);
5705
+ const out = [];
5706
+ for (const lit of literals) {
5707
+ if (isInsideJsxExternalLink(lit.node)) continue;
5708
+ for (const host of knownHosts) {
5709
+ if (urlMatchesHost(lit.text, host)) {
5710
+ out.push({ host, line: lit.node.startPosition.row + 1 });
5711
+ }
5712
+ }
5713
+ }
5714
+ return out;
5715
+ }
5716
+ function makeJsParser3() {
5717
+ const p = new import_tree_sitter3.default();
5718
+ p.setLanguage(import_tree_sitter_javascript3.default);
5719
+ return p;
5720
+ }
5721
+ function makePyParser2() {
5722
+ const p = new import_tree_sitter3.default();
5723
+ p.setLanguage(import_tree_sitter_python2.default);
5724
+ return p;
5725
+ }
5726
+ async function addHttpCallEdges(graph, services) {
5727
+ const jsParser = makeJsParser3();
5728
+ const pyParser = makePyParser2();
5729
+ const { knownHosts, hostToNodeId } = buildServiceHostIndex(services);
5730
+ let nodesAdded = 0;
5731
+ let edgesAdded = 0;
5732
+ for (const service of services) {
5733
+ const files = await loadSourceFiles(service.dir);
5734
+ const seen = /* @__PURE__ */ new Set();
5735
+ for (const file of files) {
5736
+ if (isTestPath(file.path)) continue;
5737
+ const parser = import_node_path25.default.extname(file.path) === ".py" ? pyParser : jsParser;
5738
+ let sites;
5739
+ try {
5740
+ sites = callsFromSource(file.content, parser, knownHosts);
5741
+ } catch (err) {
5742
+ recordExtractionError("http call extraction", file.path, err);
5743
+ continue;
5744
+ }
5745
+ if (sites.length === 0) continue;
5746
+ const relFile = toPosix2(import_node_path25.default.relative(service.dir, file.path));
5747
+ for (const site of sites) {
5748
+ const targetId = hostToNodeId.get(site.host);
5749
+ if (!targetId || targetId === service.node.id) continue;
5750
+ const dedupKey = `${relFile}|${targetId}`;
5751
+ if (seen.has(dedupKey)) continue;
5752
+ seen.add(dedupKey);
5753
+ const confidence = (0, import_types13.confidenceForExtracted)("url-literal-service-target");
5754
+ const ev = {
5755
+ file: relFile,
5756
+ line: site.line,
5757
+ snippet: snippet(file.content, site.line)
5758
+ };
5759
+ const { fileNodeId, nodesAdded: n, edgesAdded: e } = ensureFileNode(
5760
+ graph,
5761
+ service.pkg.name,
5762
+ service.node.id,
5763
+ relFile
5764
+ );
5765
+ nodesAdded += n;
5766
+ edgesAdded += e;
5767
+ if (!(0, import_types13.passesExtractedFloor)(confidence)) {
5768
+ noteExtractedDropped({
5769
+ source: fileNodeId,
5770
+ target: targetId,
5771
+ type: import_types13.EdgeType.CALLS,
5772
+ confidence,
5773
+ confidenceKind: "url-literal-service-target",
5774
+ evidence: ev
5775
+ });
5776
+ continue;
5777
+ }
5778
+ const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, targetId, import_types13.EdgeType.CALLS);
5779
+ if (!graph.hasEdge(edgeId)) {
5780
+ const edge = {
5781
+ id: edgeId,
5782
+ source: fileNodeId,
5783
+ target: targetId,
5784
+ type: import_types13.EdgeType.CALLS,
5785
+ provenance: import_types13.Provenance.EXTRACTED,
5786
+ confidence,
5787
+ evidence: ev
5788
+ };
5789
+ graph.addEdgeWithKey(edgeId, fileNodeId, targetId, edge);
5790
+ edgesAdded++;
5791
+ }
5792
+ }
5793
+ }
5794
+ }
5795
+ return { nodesAdded, edgesAdded };
5796
+ }
5797
+
5798
+ // src/extract/calls/route-match.ts
5799
+ init_cjs_shims();
5800
+ var import_node_path26 = __toESM(require("path"), 1);
5801
+ var import_tree_sitter4 = __toESM(require("tree-sitter"), 1);
5802
+ var import_tree_sitter_javascript4 = __toESM(require("tree-sitter-javascript"), 1);
5803
+ var import_types14 = require("@neat.is/types");
5804
+ var PARSE_CHUNK4 = 16384;
5805
+ function parseSource4(parser, source) {
5806
+ return parser.parse(
5807
+ (index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK4)
5808
+ );
5809
+ }
5810
+ function makeJsParser4() {
5811
+ const p = new import_tree_sitter4.default();
5812
+ p.setLanguage(import_tree_sitter_javascript4.default);
5813
+ return p;
5814
+ }
5815
+ var JS_CLIENT_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx"]);
5816
+ var AXIOS_METHODS = /* @__PURE__ */ new Set(["get", "post", "put", "patch", "delete", "head", "options", "request"]);
5817
+ function walk2(node, visit) {
5818
+ visit(node);
5819
+ for (let i = 0; i < node.namedChildCount; i++) {
5820
+ const child = node.namedChild(i);
5821
+ if (child) walk2(child, visit);
5822
+ }
5823
+ }
5824
+ function reconstructUrl(node) {
5825
+ if (node.type === "string") {
5826
+ for (let i = 0; i < node.namedChildCount; i++) {
5827
+ const child = node.namedChild(i);
5828
+ if (child?.type === "string_fragment") return child.text;
5829
+ }
5830
+ return "";
5831
+ }
5832
+ if (node.type === "template_string") {
5833
+ let out = "";
5834
+ for (let i = 0; i < node.namedChildCount; i++) {
5835
+ const child = node.namedChild(i);
5836
+ if (!child) continue;
5837
+ if (child.type === "string_fragment") out += child.text;
5838
+ else if (child.type === "template_substitution") out += ":param";
5839
+ }
5840
+ if (out.length === 0) {
5841
+ const raw = node.text;
5842
+ return raw.length >= 2 ? raw.slice(1, -1) : "";
5843
+ }
5844
+ return out;
5845
+ }
5846
+ return null;
5847
+ }
5848
+ function methodFromOptions(objNode) {
5849
+ for (let i = 0; i < objNode.namedChildCount; i++) {
5850
+ const pair = objNode.namedChild(i);
5851
+ if (!pair || pair.type !== "pair") continue;
5852
+ const k = pair.childForFieldName("key");
5853
+ const kText = k ? k.type === "string" ? stringText(k) : k.text : null;
5854
+ if (kText !== "method") continue;
5855
+ const v = pair.childForFieldName("value");
5856
+ if (v && (v.type === "string" || v.type === "template_string")) {
5857
+ const s = stringText(v);
5858
+ return s ? s.toUpperCase() : void 0;
5859
+ }
5860
+ }
5861
+ return void 0;
5862
+ }
5863
+ function stringText(node) {
5864
+ if (node.type === "string") {
5865
+ for (let i = 0; i < node.namedChildCount; i++) {
5866
+ const child = node.namedChild(i);
5867
+ if (child?.type === "string_fragment") return child.text;
5868
+ }
5869
+ return "";
5870
+ }
5871
+ return null;
5872
+ }
5873
+ function urlNodeFromConfig(objNode) {
5874
+ for (let i = 0; i < objNode.namedChildCount; i++) {
5875
+ const pair = objNode.namedChild(i);
5876
+ if (!pair || pair.type !== "pair") continue;
5877
+ const k = pair.childForFieldName("key");
5878
+ const kText = k ? k.type === "string" ? stringText(k) : k.text : null;
5879
+ if (kText === "url") return pair.childForFieldName("value");
5880
+ }
5881
+ return null;
5010
5882
  }
5011
- function collectStringLiterals(node, out) {
5012
- if (STRING_LITERAL_NODE_TYPES.has(node.type)) out.push({ text: node.text, node });
5013
- for (let i = 0; i < node.namedChildCount; i++) {
5014
- const child = node.namedChild(i);
5015
- if (child) collectStringLiterals(child, out);
5883
+ function pathOf(urlStr) {
5884
+ try {
5885
+ const candidate = urlStr.startsWith("//") ? `http:${urlStr}` : urlStr;
5886
+ const parsed = new URL(candidate);
5887
+ return parsed.pathname || "/";
5888
+ } catch {
5889
+ return null;
5016
5890
  }
5017
5891
  }
5018
- var PARSE_CHUNK2 = 16384;
5019
- function parseSource2(parser, source) {
5020
- return parser.parse(
5021
- (index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK2)
5022
- );
5892
+ function matchHost(urlStr, knownHosts) {
5893
+ for (const host of knownHosts) {
5894
+ if (urlMatchesHost(urlStr, host)) return host;
5895
+ }
5896
+ return null;
5023
5897
  }
5024
- function callsFromSource(source, parser, knownHosts) {
5025
- const tree = parseSource2(parser, source);
5026
- const literals = [];
5027
- collectStringLiterals(tree.rootNode, literals);
5898
+ function clientCallSitesFromSource(source, parser, knownHosts) {
5899
+ const tree = parseSource4(parser, source);
5028
5900
  const out = [];
5029
- for (const lit of literals) {
5030
- if (isInsideJsxExternalLink(lit.node)) continue;
5031
- for (const host of knownHosts) {
5032
- if (urlMatchesHost(lit.text, host)) {
5033
- out.push({ host, line: lit.node.startPosition.row + 1 });
5901
+ const push = (urlNode, method, callNode) => {
5902
+ const urlStr = reconstructUrl(urlNode);
5903
+ if (!urlStr) return;
5904
+ const host = matchHost(urlStr, knownHosts);
5905
+ if (!host) return;
5906
+ const p = pathOf(urlStr);
5907
+ if (p === null) return;
5908
+ const line = callNode.startPosition.row + 1;
5909
+ out.push({
5910
+ host,
5911
+ method,
5912
+ pathTemplate: p,
5913
+ line,
5914
+ snippet: snippet(source, line)
5915
+ });
5916
+ };
5917
+ walk2(tree.rootNode, (node) => {
5918
+ if (node.type !== "call_expression") return;
5919
+ const fn = node.childForFieldName("function");
5920
+ if (!fn) return;
5921
+ const args = node.childForFieldName("arguments");
5922
+ const first = args?.namedChild(0);
5923
+ if (!first) return;
5924
+ const fnName = fn.type === "identifier" ? fn.text : fn.type === "member_expression" ? fn.childForFieldName("property")?.text ?? "" : "";
5925
+ if (fn.type === "identifier" && fnName === "fetch") {
5926
+ const opts = args?.namedChild(1);
5927
+ const method = opts && opts.type === "object" ? methodFromOptions(opts) ?? "GET" : "GET";
5928
+ push(first, method, node);
5929
+ return;
5930
+ }
5931
+ if (fn.type === "identifier" && fnName === "axios") {
5932
+ if (first.type === "object") {
5933
+ const urlNode = urlNodeFromConfig(first);
5934
+ if (urlNode) push(urlNode, methodFromOptions(first) ?? "GET", node);
5935
+ } else {
5936
+ const opts = args?.namedChild(1);
5937
+ const method = opts && opts.type === "object" ? methodFromOptions(opts) ?? "GET" : "GET";
5938
+ push(first, method, node);
5034
5939
  }
5940
+ return;
5035
5941
  }
5036
- }
5942
+ if (fn.type === "member_expression") {
5943
+ const obj = fn.childForFieldName("object");
5944
+ const objName = obj?.text ?? "";
5945
+ if (objName === "axios" && AXIOS_METHODS.has(fnName)) {
5946
+ if (fnName === "request" && first.type === "object") {
5947
+ const urlNode = urlNodeFromConfig(first);
5948
+ if (urlNode) push(urlNode, methodFromOptions(first) ?? "GET", node);
5949
+ } else {
5950
+ push(first, fnName.toUpperCase(), node);
5951
+ }
5952
+ return;
5953
+ }
5954
+ if ((objName === "http" || objName === "https") && (fnName === "request" || fnName === "get")) {
5955
+ const opts = args?.namedChild(1);
5956
+ const method = opts && opts.type === "object" ? methodFromOptions(opts) ?? (fnName === "get" ? "GET" : "GET") : "GET";
5957
+ push(first, method, node);
5958
+ return;
5959
+ }
5960
+ }
5961
+ });
5037
5962
  return out;
5038
5963
  }
5039
- function makeJsParser2() {
5040
- const p = new import_tree_sitter2.default();
5041
- p.setLanguage(import_tree_sitter_javascript2.default);
5042
- return p;
5964
+ function buildRouteIndex(graph) {
5965
+ const index = /* @__PURE__ */ new Map();
5966
+ graph.forEachNode((_id, attrs) => {
5967
+ const node = attrs;
5968
+ if (node.type !== import_types14.NodeType.RouteNode) return;
5969
+ const route = attrs;
5970
+ const owner = (0, import_types14.serviceId)(route.service);
5971
+ const entry2 = {
5972
+ method: route.method.toUpperCase(),
5973
+ normalizedPath: normalizePathTemplate(route.pathTemplate),
5974
+ routeNodeId: route.id
5975
+ };
5976
+ const list = index.get(owner);
5977
+ if (list) list.push(entry2);
5978
+ else index.set(owner, [entry2]);
5979
+ });
5980
+ return index;
5043
5981
  }
5044
- function makePyParser2() {
5045
- const p = new import_tree_sitter2.default();
5046
- p.setLanguage(import_tree_sitter_python2.default);
5047
- return p;
5982
+ function findRoute(entries, method, normalizedPath) {
5983
+ return entries.find(
5984
+ (e) => e.normalizedPath === normalizedPath && (e.method === "ALL" || method === void 0 || e.method === method)
5985
+ );
5048
5986
  }
5049
- async function addHttpCallEdges(graph, services) {
5050
- const jsParser = makeJsParser2();
5051
- const pyParser = makePyParser2();
5052
- const knownHosts = /* @__PURE__ */ new Set();
5053
- const hostToNodeId = /* @__PURE__ */ new Map();
5054
- for (const service of services) {
5055
- knownHosts.add(import_node_path23.default.basename(service.dir));
5056
- knownHosts.add(service.pkg.name);
5057
- hostToNodeId.set(import_node_path23.default.basename(service.dir), service.node.id);
5058
- hostToNodeId.set(service.pkg.name, service.node.id);
5059
- }
5987
+ async function addRouteCallEdges(graph, services) {
5988
+ const jsParser = makeJsParser4();
5989
+ const { knownHosts, hostToNodeId } = buildServiceHostIndex(services);
5990
+ const routeIndex = buildRouteIndex(graph);
5991
+ if (routeIndex.size === 0) return { nodesAdded: 0, edgesAdded: 0 };
5060
5992
  let nodesAdded = 0;
5061
5993
  let edgesAdded = 0;
5062
5994
  for (const service of services) {
@@ -5064,28 +5996,27 @@ async function addHttpCallEdges(graph, services) {
5064
5996
  const seen = /* @__PURE__ */ new Set();
5065
5997
  for (const file of files) {
5066
5998
  if (isTestPath(file.path)) continue;
5067
- const parser = import_node_path23.default.extname(file.path) === ".py" ? pyParser : jsParser;
5999
+ if (!JS_CLIENT_EXTENSIONS.has(import_node_path26.default.extname(file.path))) continue;
5068
6000
  let sites;
5069
6001
  try {
5070
- sites = callsFromSource(file.content, parser, knownHosts);
6002
+ sites = clientCallSitesFromSource(file.content, jsParser, knownHosts);
5071
6003
  } catch (err) {
5072
- recordExtractionError("http call extraction", file.path, err);
6004
+ recordExtractionError("route-match call extraction", file.path, err);
5073
6005
  continue;
5074
6006
  }
5075
6007
  if (sites.length === 0) continue;
5076
- const relFile = toPosix2(import_node_path23.default.relative(service.dir, file.path));
6008
+ const relFile = toPosix2(import_node_path26.default.relative(service.dir, file.path));
5077
6009
  for (const site of sites) {
5078
- const targetId = hostToNodeId.get(site.host);
5079
- if (!targetId || targetId === service.node.id) continue;
5080
- const dedupKey = `${relFile}|${targetId}`;
6010
+ const serverServiceId = hostToNodeId.get(site.host);
6011
+ if (!serverServiceId || serverServiceId === service.node.id) continue;
6012
+ const entries = routeIndex.get(serverServiceId);
6013
+ if (!entries) continue;
6014
+ const normalizedPath = normalizePathTemplate(site.pathTemplate);
6015
+ const match = findRoute(entries, site.method, normalizedPath);
6016
+ if (!match) continue;
6017
+ const dedupKey = `${relFile}|${match.routeNodeId}`;
5081
6018
  if (seen.has(dedupKey)) continue;
5082
6019
  seen.add(dedupKey);
5083
- const confidence = (0, import_types11.confidenceForExtracted)("url-literal-service-target");
5084
- const ev = {
5085
- file: relFile,
5086
- line: site.line,
5087
- snippet: snippet(file.content, site.line)
5088
- };
5089
6020
  const { fileNodeId, nodesAdded: n, edgesAdded: e } = ensureFileNode(
5090
6021
  graph,
5091
6022
  service.pkg.name,
@@ -5094,29 +6025,37 @@ async function addHttpCallEdges(graph, services) {
5094
6025
  );
5095
6026
  nodesAdded += n;
5096
6027
  edgesAdded += e;
5097
- if (!(0, import_types11.passesExtractedFloor)(confidence)) {
6028
+ const confidence = (0, import_types14.confidenceForExtracted)("verified-call-site");
6029
+ const ev = {
6030
+ file: relFile,
6031
+ line: site.line,
6032
+ snippet: site.snippet,
6033
+ method: site.method ?? match.method,
6034
+ pathTemplate: site.pathTemplate
6035
+ };
6036
+ if (!(0, import_types14.passesExtractedFloor)(confidence)) {
5098
6037
  noteExtractedDropped({
5099
6038
  source: fileNodeId,
5100
- target: targetId,
5101
- type: import_types11.EdgeType.CALLS,
6039
+ target: match.routeNodeId,
6040
+ type: import_types14.EdgeType.CALLS,
5102
6041
  confidence,
5103
- confidenceKind: "url-literal-service-target",
6042
+ confidenceKind: "verified-call-site",
5104
6043
  evidence: ev
5105
6044
  });
5106
6045
  continue;
5107
6046
  }
5108
- const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, targetId, import_types11.EdgeType.CALLS);
6047
+ const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, match.routeNodeId, import_types14.EdgeType.CALLS);
5109
6048
  if (!graph.hasEdge(edgeId)) {
5110
6049
  const edge = {
5111
6050
  id: edgeId,
5112
6051
  source: fileNodeId,
5113
- target: targetId,
5114
- type: import_types11.EdgeType.CALLS,
5115
- provenance: import_types11.Provenance.EXTRACTED,
6052
+ target: match.routeNodeId,
6053
+ type: import_types14.EdgeType.CALLS,
6054
+ provenance: import_types14.Provenance.EXTRACTED,
5116
6055
  confidence,
5117
6056
  evidence: ev
5118
6057
  };
5119
- graph.addEdgeWithKey(edgeId, fileNodeId, targetId, edge);
6058
+ graph.addEdgeWithKey(edgeId, fileNodeId, match.routeNodeId, edge);
5120
6059
  edgesAdded++;
5121
6060
  }
5122
6061
  }
@@ -5127,8 +6066,8 @@ async function addHttpCallEdges(graph, services) {
5127
6066
 
5128
6067
  // src/extract/calls/kafka.ts
5129
6068
  init_cjs_shims();
5130
- var import_node_path24 = __toESM(require("path"), 1);
5131
- var import_types12 = require("@neat.is/types");
6069
+ var import_node_path27 = __toESM(require("path"), 1);
6070
+ var import_types15 = require("@neat.is/types");
5132
6071
  var PRODUCER_TOPIC_RE = /(?:producer|kafkaProducer)[\s\S]{0,40}?\.send\s*\(\s*\{[\s\S]{0,200}?topic\s*:\s*['"`]([^'"`]+)['"`]/g;
5133
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;
5134
6073
  function findAll(re, text) {
@@ -5149,7 +6088,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
5149
6088
  seen.add(key);
5150
6089
  const line = lineOf(file.content, topic);
5151
6090
  out.push({
5152
- infraId: (0, import_types12.infraId)("kafka-topic", topic),
6091
+ infraId: (0, import_types15.infraId)("kafka-topic", topic),
5153
6092
  name: topic,
5154
6093
  kind: "kafka-topic",
5155
6094
  edgeType,
@@ -5158,7 +6097,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
5158
6097
  // tier (ADR-066).
5159
6098
  confidenceKind: "verified-call-site",
5160
6099
  evidence: {
5161
- file: import_node_path24.default.relative(serviceDir, file.path),
6100
+ file: import_node_path27.default.relative(serviceDir, file.path),
5162
6101
  line,
5163
6102
  snippet: snippet(file.content, line)
5164
6103
  }
@@ -5171,8 +6110,8 @@ function kafkaEndpointsFromFile(file, serviceDir) {
5171
6110
 
5172
6111
  // src/extract/calls/redis.ts
5173
6112
  init_cjs_shims();
5174
- var import_node_path25 = __toESM(require("path"), 1);
5175
- var import_types13 = require("@neat.is/types");
6113
+ var import_node_path28 = __toESM(require("path"), 1);
6114
+ var import_types16 = require("@neat.is/types");
5176
6115
  var REDIS_URL_RE = /redis(?:s)?:\/\/(?:[^@'"`\s]+@)?([^:/'"`\s]+)(?::(\d+))?/g;
5177
6116
  function redisEndpointsFromFile(file, serviceDir) {
5178
6117
  const out = [];
@@ -5185,7 +6124,7 @@ function redisEndpointsFromFile(file, serviceDir) {
5185
6124
  seen.add(host);
5186
6125
  const line = lineOf(file.content, host);
5187
6126
  out.push({
5188
- infraId: (0, import_types13.infraId)("redis", host),
6127
+ infraId: (0, import_types16.infraId)("redis", host),
5189
6128
  name: host,
5190
6129
  kind: "redis",
5191
6130
  edgeType: "CALLS",
@@ -5194,7 +6133,7 @@ function redisEndpointsFromFile(file, serviceDir) {
5194
6133
  // support tier (ADR-066).
5195
6134
  confidenceKind: "url-with-structural-support",
5196
6135
  evidence: {
5197
- file: import_node_path25.default.relative(serviceDir, file.path),
6136
+ file: import_node_path28.default.relative(serviceDir, file.path),
5198
6137
  line,
5199
6138
  snippet: snippet(file.content, line)
5200
6139
  }
@@ -5205,8 +6144,8 @@ function redisEndpointsFromFile(file, serviceDir) {
5205
6144
 
5206
6145
  // src/extract/calls/aws.ts
5207
6146
  init_cjs_shims();
5208
- var import_node_path26 = __toESM(require("path"), 1);
5209
- var import_types14 = require("@neat.is/types");
6147
+ var import_node_path29 = __toESM(require("path"), 1);
6148
+ var import_types17 = require("@neat.is/types");
5210
6149
  var S3_BUCKET_RE = /Bucket\s*:\s*['"`]([^'"`]+)['"`]/g;
5211
6150
  var DYNAMO_TABLE_RE = /TableName\s*:\s*['"`]([^'"`]+)['"`]/g;
5212
6151
  function hasMarker(text, markers) {
@@ -5230,7 +6169,7 @@ function awsEndpointsFromFile(file, serviceDir) {
5230
6169
  seen.add(key);
5231
6170
  const line = lineOf(file.content, name);
5232
6171
  out.push({
5233
- infraId: (0, import_types14.infraId)(kind, name),
6172
+ infraId: (0, import_types17.infraId)(kind, name),
5234
6173
  name,
5235
6174
  kind,
5236
6175
  edgeType: "CALLS",
@@ -5239,7 +6178,7 @@ function awsEndpointsFromFile(file, serviceDir) {
5239
6178
  // (ADR-066).
5240
6179
  confidenceKind: "verified-call-site",
5241
6180
  evidence: {
5242
- file: import_node_path26.default.relative(serviceDir, file.path),
6181
+ file: import_node_path29.default.relative(serviceDir, file.path),
5243
6182
  line,
5244
6183
  snippet: snippet(file.content, line)
5245
6184
  }
@@ -5264,8 +6203,8 @@ function awsEndpointsFromFile(file, serviceDir) {
5264
6203
 
5265
6204
  // src/extract/calls/grpc.ts
5266
6205
  init_cjs_shims();
5267
- var import_node_path27 = __toESM(require("path"), 1);
5268
- var import_types15 = require("@neat.is/types");
6206
+ var import_node_path30 = __toESM(require("path"), 1);
6207
+ var import_types18 = require("@neat.is/types");
5269
6208
  var GRPC_CLIENT_RE = /new\s+([A-Z][A-Za-z0-9_]*)Client\s*\(\s*['"`]?([^,'"`)]+)?/g;
5270
6209
  var AWS_SDK_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@aws-sdk\/client-([a-z0-9-]+)['"`]/g;
5271
6210
  var GRPC_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@grpc\/grpc-js['"`]|_grpc_pb['"`]/;
@@ -5314,7 +6253,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
5314
6253
  const { kind } = classified;
5315
6254
  const line = lineOf(file.content, m[0]);
5316
6255
  out.push({
5317
- infraId: (0, import_types15.infraId)(kind, name),
6256
+ infraId: (0, import_types18.infraId)(kind, name),
5318
6257
  name,
5319
6258
  kind,
5320
6259
  edgeType: "CALLS",
@@ -5323,7 +6262,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
5323
6262
  // tier (ADR-066).
5324
6263
  confidenceKind: "verified-call-site",
5325
6264
  evidence: {
5326
- file: import_node_path27.default.relative(serviceDir, file.path),
6265
+ file: import_node_path30.default.relative(serviceDir, file.path),
5327
6266
  line,
5328
6267
  snippet: snippet(file.content, line)
5329
6268
  }
@@ -5334,8 +6273,8 @@ function grpcEndpointsFromFile(file, serviceDir) {
5334
6273
 
5335
6274
  // src/extract/calls/supabase.ts
5336
6275
  init_cjs_shims();
5337
- var import_node_path28 = __toESM(require("path"), 1);
5338
- var import_types16 = require("@neat.is/types");
6276
+ var import_node_path31 = __toESM(require("path"), 1);
6277
+ var import_types19 = require("@neat.is/types");
5339
6278
  var SUPABASE_JS_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@supabase\/supabase-js['"`]/;
5340
6279
  var SUPABASE_SSR_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@supabase\/ssr['"`]/;
5341
6280
  var SUPABASE_CLIENT_RE = /\b(createClient|createServerClient|createBrowserClient)\s*\(\s*(?:['"`]([^'"`]*)['"`])?/g;
@@ -5373,7 +6312,7 @@ function supabaseEndpointsFromFile(file, serviceDir) {
5373
6312
  seen.add(name);
5374
6313
  const line = lineOf(file.content, m[0]);
5375
6314
  out.push({
5376
- infraId: (0, import_types16.infraId)("supabase", name),
6315
+ infraId: (0, import_types19.infraId)("supabase", name),
5377
6316
  name,
5378
6317
  kind: "supabase",
5379
6318
  edgeType: "CALLS",
@@ -5383,7 +6322,7 @@ function supabaseEndpointsFromFile(file, serviceDir) {
5383
6322
  // tier (ADR-066), the same grade aws.ts / grpc.ts emit at.
5384
6323
  confidenceKind: "verified-call-site",
5385
6324
  evidence: {
5386
- file: import_node_path28.default.relative(serviceDir, file.path),
6325
+ file: import_node_path31.default.relative(serviceDir, file.path),
5387
6326
  line,
5388
6327
  snippet: snippet(file.content, line)
5389
6328
  }
@@ -5396,11 +6335,11 @@ function supabaseEndpointsFromFile(file, serviceDir) {
5396
6335
  function edgeTypeFromEndpoint(ep) {
5397
6336
  switch (ep.edgeType) {
5398
6337
  case "PUBLISHES_TO":
5399
- return import_types17.EdgeType.PUBLISHES_TO;
6338
+ return import_types20.EdgeType.PUBLISHES_TO;
5400
6339
  case "CONSUMES_FROM":
5401
- return import_types17.EdgeType.CONSUMES_FROM;
6340
+ return import_types20.EdgeType.CONSUMES_FROM;
5402
6341
  default:
5403
- return import_types17.EdgeType.CALLS;
6342
+ return import_types20.EdgeType.CALLS;
5404
6343
  }
5405
6344
  }
5406
6345
  function isAwsKind(kind) {
@@ -5428,7 +6367,7 @@ async function addExternalEndpointEdges(graph, services) {
5428
6367
  if (!graph.hasNode(ep.infraId)) {
5429
6368
  const node = {
5430
6369
  id: ep.infraId,
5431
- type: import_types17.NodeType.InfraNode,
6370
+ type: import_types20.NodeType.InfraNode,
5432
6371
  name: ep.name,
5433
6372
  // #238 — `aws-*` covers AWS-SDK client kinds (aws-s3, aws-dynamodb,
5434
6373
  // aws-cognito-identity-provider, …); `s3-` / `dynamodb-` cover the
@@ -5440,7 +6379,7 @@ async function addExternalEndpointEdges(graph, services) {
5440
6379
  nodesAdded++;
5441
6380
  }
5442
6381
  const edgeType = edgeTypeFromEndpoint(ep);
5443
- const confidence = (0, import_types17.confidenceForExtracted)(ep.confidenceKind);
6382
+ const confidence = (0, import_types20.confidenceForExtracted)(ep.confidenceKind);
5444
6383
  const relFile = toPosix2(ep.evidence.file);
5445
6384
  const { fileNodeId, nodesAdded: n, edgesAdded: e } = ensureFileNode(
5446
6385
  graph,
@@ -5450,7 +6389,7 @@ async function addExternalEndpointEdges(graph, services) {
5450
6389
  );
5451
6390
  nodesAdded += n;
5452
6391
  edgesAdded += e;
5453
- if (!(0, import_types17.passesExtractedFloor)(confidence)) {
6392
+ if (!(0, import_types20.passesExtractedFloor)(confidence)) {
5454
6393
  noteExtractedDropped({
5455
6394
  source: fileNodeId,
5456
6395
  target: ep.infraId,
@@ -5470,7 +6409,7 @@ async function addExternalEndpointEdges(graph, services) {
5470
6409
  source: fileNodeId,
5471
6410
  target: ep.infraId,
5472
6411
  type: edgeType,
5473
- provenance: import_types17.Provenance.EXTRACTED,
6412
+ provenance: import_types20.Provenance.EXTRACTED,
5474
6413
  confidence,
5475
6414
  evidence: ep.evidence
5476
6415
  };
@@ -5484,9 +6423,10 @@ async function addExternalEndpointEdges(graph, services) {
5484
6423
  async function addCallEdges(graph, services) {
5485
6424
  const http2 = await addHttpCallEdges(graph, services);
5486
6425
  const ext = await addExternalEndpointEdges(graph, services);
6426
+ const routes = await addRouteCallEdges(graph, services);
5487
6427
  return {
5488
- nodesAdded: http2.nodesAdded + ext.nodesAdded,
5489
- edgesAdded: http2.edgesAdded + ext.edgesAdded
6428
+ nodesAdded: http2.nodesAdded + ext.nodesAdded + routes.nodesAdded,
6429
+ edgesAdded: http2.edgesAdded + ext.edgesAdded + routes.edgesAdded
5490
6430
  };
5491
6431
  }
5492
6432
 
@@ -5495,16 +6435,16 @@ init_cjs_shims();
5495
6435
 
5496
6436
  // src/extract/infra/docker-compose.ts
5497
6437
  init_cjs_shims();
5498
- var import_node_path29 = __toESM(require("path"), 1);
5499
- var import_types19 = require("@neat.is/types");
6438
+ var import_node_path32 = __toESM(require("path"), 1);
6439
+ var import_types22 = require("@neat.is/types");
5500
6440
 
5501
6441
  // src/extract/infra/shared.ts
5502
6442
  init_cjs_shims();
5503
- var import_types18 = require("@neat.is/types");
6443
+ var import_types21 = require("@neat.is/types");
5504
6444
  function makeInfraNode(kind, name, provider = "self", extras) {
5505
6445
  return {
5506
- id: (0, import_types18.infraId)(kind, name),
5507
- type: import_types18.NodeType.InfraNode,
6446
+ id: (0, import_types21.infraId)(kind, name),
6447
+ type: import_types21.NodeType.InfraNode,
5508
6448
  name,
5509
6449
  provider,
5510
6450
  kind,
@@ -5533,7 +6473,7 @@ function dependsOnList(value) {
5533
6473
  }
5534
6474
  function serviceNameToServiceNode(name, services) {
5535
6475
  for (const s of services) {
5536
- if (s.node.name === name || import_node_path29.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;
5537
6477
  }
5538
6478
  return null;
5539
6479
  }
@@ -5542,7 +6482,7 @@ async function addComposeInfra(graph, scanPath, services) {
5542
6482
  let edgesAdded = 0;
5543
6483
  let composePath = null;
5544
6484
  for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
5545
- const abs = import_node_path29.default.join(scanPath, name);
6485
+ const abs = import_node_path32.default.join(scanPath, name);
5546
6486
  if (await exists(abs)) {
5547
6487
  composePath = abs;
5548
6488
  break;
@@ -5555,13 +6495,13 @@ async function addComposeInfra(graph, scanPath, services) {
5555
6495
  } catch (err) {
5556
6496
  recordExtractionError(
5557
6497
  "infra docker-compose",
5558
- import_node_path29.default.relative(scanPath, composePath),
6498
+ import_node_path32.default.relative(scanPath, composePath),
5559
6499
  err
5560
6500
  );
5561
6501
  return { nodesAdded, edgesAdded };
5562
6502
  }
5563
6503
  if (!compose?.services) return { nodesAdded, edgesAdded };
5564
- const evidenceFile = import_node_path29.default.relative(scanPath, composePath).split(import_node_path29.default.sep).join("/");
6504
+ const evidenceFile = import_node_path32.default.relative(scanPath, composePath).split(import_node_path32.default.sep).join("/");
5565
6505
  const composeNameToNodeId = /* @__PURE__ */ new Map();
5566
6506
  for (const [composeName, svc] of Object.entries(compose.services)) {
5567
6507
  const matchedServiceId = serviceNameToServiceNode(composeName, services);
@@ -5583,15 +6523,15 @@ async function addComposeInfra(graph, scanPath, services) {
5583
6523
  for (const dep of dependsOnList(svc.depends_on)) {
5584
6524
  const targetId = composeNameToNodeId.get(dep);
5585
6525
  if (!targetId) continue;
5586
- const edgeId = (0, import_types4.extractedEdgeId)(sourceId, targetId, import_types19.EdgeType.DEPENDS_ON);
6526
+ const edgeId = (0, import_types4.extractedEdgeId)(sourceId, targetId, import_types22.EdgeType.DEPENDS_ON);
5587
6527
  if (graph.hasEdge(edgeId)) continue;
5588
6528
  const edge = {
5589
6529
  id: edgeId,
5590
6530
  source: sourceId,
5591
6531
  target: targetId,
5592
- type: import_types19.EdgeType.DEPENDS_ON,
5593
- provenance: import_types19.Provenance.EXTRACTED,
5594
- confidence: (0, import_types19.confidenceForExtracted)("structural"),
6532
+ type: import_types22.EdgeType.DEPENDS_ON,
6533
+ provenance: import_types22.Provenance.EXTRACTED,
6534
+ confidence: (0, import_types22.confidenceForExtracted)("structural"),
5595
6535
  evidence: { file: evidenceFile }
5596
6536
  };
5597
6537
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -5603,9 +6543,9 @@ async function addComposeInfra(graph, scanPath, services) {
5603
6543
 
5604
6544
  // src/extract/infra/dockerfile.ts
5605
6545
  init_cjs_shims();
5606
- var import_node_path30 = __toESM(require("path"), 1);
5607
- var import_node_fs16 = require("fs");
5608
- var import_types20 = 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");
5609
6549
  function readDockerfile(content) {
5610
6550
  let image = null;
5611
6551
  const ports = [];
@@ -5634,15 +6574,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
5634
6574
  let nodesAdded = 0;
5635
6575
  let edgesAdded = 0;
5636
6576
  for (const service of services) {
5637
- const dockerfilePath = import_node_path30.default.join(service.dir, "Dockerfile");
6577
+ const dockerfilePath = import_node_path33.default.join(service.dir, "Dockerfile");
5638
6578
  if (!await exists(dockerfilePath)) continue;
5639
6579
  let content;
5640
6580
  try {
5641
- content = await import_node_fs16.promises.readFile(dockerfilePath, "utf8");
6581
+ content = await import_node_fs17.promises.readFile(dockerfilePath, "utf8");
5642
6582
  } catch (err) {
5643
6583
  recordExtractionError(
5644
6584
  "infra dockerfile",
5645
- import_node_path30.default.relative(scanPath, dockerfilePath),
6585
+ import_node_path33.default.relative(scanPath, dockerfilePath),
5646
6586
  err
5647
6587
  );
5648
6588
  continue;
@@ -5654,8 +6594,8 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
5654
6594
  graph.addNode(node.id, node);
5655
6595
  nodesAdded++;
5656
6596
  }
5657
- const relDockerfile = toPosix2(import_node_path30.default.relative(service.dir, dockerfilePath));
5658
- const evidenceFile = toPosix2(import_node_path30.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));
5659
6599
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
5660
6600
  graph,
5661
6601
  service.pkg.name,
@@ -5664,15 +6604,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
5664
6604
  );
5665
6605
  nodesAdded += fn;
5666
6606
  edgesAdded += fe;
5667
- const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, node.id, import_types20.EdgeType.RUNS_ON);
6607
+ const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, node.id, import_types23.EdgeType.RUNS_ON);
5668
6608
  if (!graph.hasEdge(edgeId)) {
5669
6609
  const edge = {
5670
6610
  id: edgeId,
5671
6611
  source: fileNodeId,
5672
6612
  target: node.id,
5673
- type: import_types20.EdgeType.RUNS_ON,
5674
- provenance: import_types20.Provenance.EXTRACTED,
5675
- confidence: (0, import_types20.confidenceForExtracted)("structural"),
6613
+ type: import_types23.EdgeType.RUNS_ON,
6614
+ provenance: import_types23.Provenance.EXTRACTED,
6615
+ confidence: (0, import_types23.confidenceForExtracted)("structural"),
5676
6616
  evidence: {
5677
6617
  file: evidenceFile,
5678
6618
  ...facts.entrypoint ? { snippet: facts.entrypoint.slice(0, 120) } : {}
@@ -5687,15 +6627,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
5687
6627
  graph.addNode(portNode.id, portNode);
5688
6628
  nodesAdded++;
5689
6629
  }
5690
- const portEdgeId = (0, import_types4.extractedEdgeId)(fileNodeId, portNode.id, import_types20.EdgeType.CONNECTS_TO);
6630
+ const portEdgeId = (0, import_types4.extractedEdgeId)(fileNodeId, portNode.id, import_types23.EdgeType.CONNECTS_TO);
5691
6631
  if (graph.hasEdge(portEdgeId)) continue;
5692
6632
  const portEdge = {
5693
6633
  id: portEdgeId,
5694
6634
  source: fileNodeId,
5695
6635
  target: portNode.id,
5696
- type: import_types20.EdgeType.CONNECTS_TO,
5697
- provenance: import_types20.Provenance.EXTRACTED,
5698
- confidence: (0, import_types20.confidenceForExtracted)("structural"),
6636
+ type: import_types23.EdgeType.CONNECTS_TO,
6637
+ provenance: import_types23.Provenance.EXTRACTED,
6638
+ confidence: (0, import_types23.confidenceForExtracted)("structural"),
5699
6639
  evidence: { file: evidenceFile, snippet: `EXPOSE ${port}` }
5700
6640
  };
5701
6641
  graph.addEdgeWithKey(portEdgeId, portEdge.source, portEdge.target, portEdge);
@@ -5707,23 +6647,23 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
5707
6647
 
5708
6648
  // src/extract/infra/terraform.ts
5709
6649
  init_cjs_shims();
5710
- var import_node_fs17 = require("fs");
5711
- var import_node_path31 = __toESM(require("path"), 1);
5712
- var import_types21 = 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");
5713
6653
  var RESOURCE_RE = /resource\s+"(aws_[A-Za-z0-9_]+)"\s+"([A-Za-z0-9_-]+)"/g;
5714
6654
  var REFERENCE_RE = /(?<![\w.])(aws_[A-Za-z0-9_]+)\.([A-Za-z0-9_-]+)/g;
5715
6655
  async function walkTfFiles(start, depth = 0, max = 5) {
5716
6656
  if (depth > max) return [];
5717
6657
  const out = [];
5718
- 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(() => []);
5719
6659
  for (const entry2 of entries) {
5720
6660
  if (entry2.isDirectory()) {
5721
6661
  if (IGNORED_DIRS.has(entry2.name) || entry2.name === ".terraform") continue;
5722
- const child = import_node_path31.default.join(start, entry2.name);
6662
+ const child = import_node_path34.default.join(start, entry2.name);
5723
6663
  if (await isPythonVenvDir(child)) continue;
5724
6664
  out.push(...await walkTfFiles(child, depth + 1, max));
5725
6665
  } else if (entry2.isFile() && entry2.name.endsWith(".tf")) {
5726
- out.push(import_node_path31.default.join(start, entry2.name));
6666
+ out.push(import_node_path34.default.join(start, entry2.name));
5727
6667
  }
5728
6668
  }
5729
6669
  return out;
@@ -5742,7 +6682,7 @@ function blockBody(content, from) {
5742
6682
  }
5743
6683
  return null;
5744
6684
  }
5745
- function lineAt(content, index) {
6685
+ function lineAt2(content, index) {
5746
6686
  let line = 1;
5747
6687
  for (let i = 0; i < index && i < content.length; i++) {
5748
6688
  if (content[i] === "\n") line++;
@@ -5754,8 +6694,8 @@ async function addTerraformResources(graph, scanPath) {
5754
6694
  let edgesAdded = 0;
5755
6695
  const files = await walkTfFiles(scanPath);
5756
6696
  for (const file of files) {
5757
- const content = await import_node_fs17.promises.readFile(file, "utf8");
5758
- const evidenceFile = toPosix2(import_node_path31.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));
5759
6699
  const resources = [];
5760
6700
  const byKey = /* @__PURE__ */ new Map();
5761
6701
  RESOURCE_RE.lastIndex = 0;
@@ -5790,16 +6730,16 @@ async function addTerraformResources(graph, scanPath) {
5790
6730
  if (!target) continue;
5791
6731
  if (seen.has(target.nodeId)) continue;
5792
6732
  seen.add(target.nodeId);
5793
- const edgeId = (0, import_types4.extractedEdgeId)(resource.nodeId, target.nodeId, import_types21.EdgeType.DEPENDS_ON);
6733
+ const edgeId = (0, import_types4.extractedEdgeId)(resource.nodeId, target.nodeId, import_types24.EdgeType.DEPENDS_ON);
5794
6734
  if (graph.hasEdge(edgeId)) continue;
5795
- const line = lineAt(content, resource.bodyOffset + ref.index);
6735
+ const line = lineAt2(content, resource.bodyOffset + ref.index);
5796
6736
  const edge = {
5797
6737
  id: edgeId,
5798
6738
  source: resource.nodeId,
5799
6739
  target: target.nodeId,
5800
- type: import_types21.EdgeType.DEPENDS_ON,
5801
- provenance: import_types21.Provenance.EXTRACTED,
5802
- confidence: (0, import_types21.confidenceForExtracted)("structural"),
6740
+ type: import_types24.EdgeType.DEPENDS_ON,
6741
+ provenance: import_types24.Provenance.EXTRACTED,
6742
+ confidence: (0, import_types24.confidenceForExtracted)("structural"),
5803
6743
  evidence: { file: evidenceFile, line, snippet: key }
5804
6744
  };
5805
6745
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -5812,8 +6752,8 @@ async function addTerraformResources(graph, scanPath) {
5812
6752
 
5813
6753
  // src/extract/infra/k8s.ts
5814
6754
  init_cjs_shims();
5815
- var import_node_fs18 = require("fs");
5816
- var import_node_path32 = __toESM(require("path"), 1);
6755
+ var import_node_fs19 = require("fs");
6756
+ var import_node_path35 = __toESM(require("path"), 1);
5817
6757
  var import_yaml3 = require("yaml");
5818
6758
  var K8S_KIND_TO_INFRA_KIND = {
5819
6759
  Service: "k8s-service",
@@ -5827,15 +6767,15 @@ var K8S_KIND_TO_INFRA_KIND = {
5827
6767
  async function walkYamlFiles2(start, depth = 0, max = 5) {
5828
6768
  if (depth > max) return [];
5829
6769
  const out = [];
5830
- 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(() => []);
5831
6771
  for (const entry2 of entries) {
5832
6772
  if (entry2.isDirectory()) {
5833
6773
  if (IGNORED_DIRS.has(entry2.name)) continue;
5834
- const child = import_node_path32.default.join(start, entry2.name);
6774
+ const child = import_node_path35.default.join(start, entry2.name);
5835
6775
  if (await isPythonVenvDir(child)) continue;
5836
6776
  out.push(...await walkYamlFiles2(child, depth + 1, max));
5837
- } else if (entry2.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path32.default.extname(entry2.name))) {
5838
- out.push(import_node_path32.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));
5839
6779
  }
5840
6780
  }
5841
6781
  return out;
@@ -5844,7 +6784,7 @@ async function addK8sResources(graph, scanPath) {
5844
6784
  let nodesAdded = 0;
5845
6785
  const files = await walkYamlFiles2(scanPath);
5846
6786
  for (const file of files) {
5847
- const content = await import_node_fs18.promises.readFile(file, "utf8");
6787
+ const content = await import_node_fs19.promises.readFile(file, "utf8");
5848
6788
  let docs;
5849
6789
  try {
5850
6790
  docs = (0, import_yaml3.parseAllDocuments)(content).map((d) => d.toJSON());
@@ -5879,17 +6819,17 @@ async function addInfra(graph, scanPath, services) {
5879
6819
  }
5880
6820
 
5881
6821
  // src/extract/index.ts
5882
- var import_node_path34 = __toESM(require("path"), 1);
6822
+ var import_node_path37 = __toESM(require("path"), 1);
5883
6823
 
5884
6824
  // src/extract/retire.ts
5885
6825
  init_cjs_shims();
5886
- var import_node_fs19 = require("fs");
5887
- var import_node_path33 = __toESM(require("path"), 1);
5888
- var import_types22 = 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");
5889
6829
  function dropOrphanedFileNodes(graph) {
5890
6830
  const orphans = [];
5891
6831
  graph.forEachNode((id, attrs) => {
5892
- if (attrs.type !== import_types22.NodeType.FileNode) return;
6832
+ if (attrs.type !== import_types25.NodeType.FileNode) return;
5893
6833
  if (graph.inboundEdges(id).length === 0 && graph.outboundEdges(id).length === 0) {
5894
6834
  orphans.push(id);
5895
6835
  }
@@ -5902,7 +6842,7 @@ function retireEdgesByFile(graph, file) {
5902
6842
  const toDrop = [];
5903
6843
  graph.forEachEdge((id, attrs) => {
5904
6844
  const edge = attrs;
5905
- if (edge.provenance !== import_types22.Provenance.EXTRACTED) return;
6845
+ if (edge.provenance !== import_types25.Provenance.EXTRACTED) return;
5906
6846
  if (!edge.evidence?.file) return;
5907
6847
  if (edge.evidence.file === normalized) toDrop.push(id);
5908
6848
  });
@@ -5915,14 +6855,14 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
5915
6855
  const bases = [scanPath, ...serviceDirs];
5916
6856
  graph.forEachEdge((id, attrs) => {
5917
6857
  const edge = attrs;
5918
- if (edge.provenance !== import_types22.Provenance.EXTRACTED) return;
6858
+ if (edge.provenance !== import_types25.Provenance.EXTRACTED) return;
5919
6859
  const evidenceFile = edge.evidence?.file;
5920
6860
  if (!evidenceFile) return;
5921
- if (import_node_path33.default.isAbsolute(evidenceFile)) {
5922
- 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);
5923
6863
  return;
5924
6864
  }
5925
- const found = bases.some((base) => (0, import_node_fs19.existsSync)(import_node_path33.default.join(base, evidenceFile)));
6865
+ const found = bases.some((base) => (0, import_node_fs20.existsSync)(import_node_path36.default.join(base, evidenceFile)));
5926
6866
  if (!found) toDrop.push(id);
5927
6867
  });
5928
6868
  for (const id of toDrop) graph.dropEdge(id);
@@ -5941,6 +6881,8 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
5941
6881
  const importGraph = await addImports(graph, services);
5942
6882
  const phase2 = await addDatabasesAndCompat(graph, services, scanPath);
5943
6883
  const phase3 = await addConfigNodes(graph, services, scanPath);
6884
+ const routePhase = await addRoutes(graph, services);
6885
+ const grpcPhase = await addGrpcMethods(graph, services);
5944
6886
  const phase4 = await addCallEdges(graph, services);
5945
6887
  const phase5 = await addInfra(graph, scanPath, services);
5946
6888
  const ghostsRetired = retireExtractedEdgesByMissingFile(
@@ -5962,7 +6904,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
5962
6904
  }
5963
6905
  const droppedEntries = drainDroppedExtracted();
5964
6906
  if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
5965
- const rejectedPath = import_node_path34.default.join(import_node_path34.default.dirname(opts.errorsPath), "rejected.ndjson");
6907
+ const rejectedPath = import_node_path37.default.join(import_node_path37.default.dirname(opts.errorsPath), "rejected.ndjson");
5966
6908
  try {
5967
6909
  await writeRejectedExtracted(droppedEntries, rejectedPath);
5968
6910
  } catch (err) {
@@ -5972,8 +6914,8 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
5972
6914
  }
5973
6915
  }
5974
6916
  const result = {
5975
- nodesAdded: phase1Nodes + fileEnum.nodesAdded + importGraph.nodesAdded + phase2.nodesAdded + phase3.nodesAdded + phase4.nodesAdded + phase5.nodesAdded,
5976
- edgesAdded: fileEnum.edgesAdded + importGraph.edgesAdded + phase2.edgesAdded + phase3.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,
5977
6919
  frontiersPromoted,
5978
6920
  extractionErrors: errorEntries.length,
5979
6921
  errorEntries,
@@ -5996,7 +6938,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
5996
6938
 
5997
6939
  // src/divergences.ts
5998
6940
  init_cjs_shims();
5999
- var import_types23 = require("@neat.is/types");
6941
+ var import_types26 = require("@neat.is/types");
6000
6942
  function bucketKey(source, target, type) {
6001
6943
  return `${type}|${source}|${target}`;
6002
6944
  }
@@ -6004,22 +6946,22 @@ function bucketEdges(graph) {
6004
6946
  const buckets = /* @__PURE__ */ new Map();
6005
6947
  graph.forEachEdge((id, attrs) => {
6006
6948
  const e = attrs;
6007
- const parsed = (0, import_types23.parseEdgeId)(id);
6949
+ const parsed = (0, import_types26.parseEdgeId)(id);
6008
6950
  const provenance = parsed?.provenance ?? e.provenance;
6009
6951
  const key = bucketKey(e.source, e.target, e.type);
6010
6952
  const cur = buckets.get(key) ?? { source: e.source, target: e.target, type: e.type };
6011
6953
  switch (provenance) {
6012
- case import_types23.Provenance.EXTRACTED:
6954
+ case import_types26.Provenance.EXTRACTED:
6013
6955
  cur.extracted = e;
6014
6956
  break;
6015
- case import_types23.Provenance.OBSERVED:
6957
+ case import_types26.Provenance.OBSERVED:
6016
6958
  cur.observed = e;
6017
6959
  break;
6018
- case import_types23.Provenance.INFERRED:
6960
+ case import_types26.Provenance.INFERRED:
6019
6961
  cur.inferred = e;
6020
6962
  break;
6021
6963
  default:
6022
- if (e.provenance === import_types23.Provenance.STALE) cur.stale = e;
6964
+ if (e.provenance === import_types26.Provenance.STALE) cur.stale = e;
6023
6965
  }
6024
6966
  buckets.set(key, cur);
6025
6967
  });
@@ -6028,7 +6970,12 @@ function bucketEdges(graph) {
6028
6970
  function nodeIsFrontier(graph, nodeId) {
6029
6971
  if (!graph.hasNode(nodeId)) return false;
6030
6972
  const attrs = graph.getNodeAttributes(nodeId);
6031
- return attrs.type === import_types23.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;
6032
6979
  }
6033
6980
  function clampConfidence(n) {
6034
6981
  if (!Number.isFinite(n)) return 0;
@@ -6048,14 +6995,14 @@ function gradedConfidence(edge) {
6048
6995
  return clampConfidence(confidenceForEdge(edge));
6049
6996
  }
6050
6997
  var OBSERVABLE_EDGE_TYPES = /* @__PURE__ */ new Set([
6051
- import_types23.EdgeType.CALLS,
6052
- import_types23.EdgeType.CONNECTS_TO,
6053
- import_types23.EdgeType.PUBLISHES_TO,
6054
- import_types23.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
6055
7002
  ]);
6056
7003
  function detectMissingDivergences(graph, bucket) {
6057
7004
  const out = [];
6058
- if (bucket.type === import_types23.EdgeType.CONTAINS) return out;
7005
+ if (bucket.type === import_types26.EdgeType.CONTAINS) return out;
6059
7006
  if (bucket.extracted && !bucket.observed && OBSERVABLE_EDGE_TYPES.has(bucket.type)) {
6060
7007
  if (!nodeIsFrontier(graph, bucket.target)) {
6061
7008
  out.push({
@@ -6070,7 +7017,7 @@ function detectMissingDivergences(graph, bucket) {
6070
7017
  });
6071
7018
  }
6072
7019
  }
6073
- if (bucket.observed && !bucket.extracted) {
7020
+ if (bucket.observed && !bucket.extracted && !nodeIsWebsocketChannel(graph, bucket.target)) {
6074
7021
  out.push({
6075
7022
  type: "missing-extracted",
6076
7023
  source: bucket.source,
@@ -6096,7 +7043,7 @@ function declaredHostFor(svc) {
6096
7043
  function hasExtractedConfiguredBy(graph, svcId) {
6097
7044
  for (const edgeId of graph.outboundEdges(svcId)) {
6098
7045
  const e = graph.getEdgeAttributes(edgeId);
6099
- if (e.type === import_types23.EdgeType.CONFIGURED_BY && e.provenance === import_types23.Provenance.EXTRACTED) {
7046
+ if (e.type === import_types26.EdgeType.CONFIGURED_BY && e.provenance === import_types26.Provenance.EXTRACTED) {
6100
7047
  return true;
6101
7048
  }
6102
7049
  }
@@ -6109,10 +7056,10 @@ function detectHostMismatch(graph, svcId, svc) {
6109
7056
  const out = [];
6110
7057
  for (const edgeId of graph.outboundEdges(svcId)) {
6111
7058
  const edge = graph.getEdgeAttributes(edgeId);
6112
- if (edge.type !== import_types23.EdgeType.CONNECTS_TO) continue;
6113
- if (edge.provenance !== import_types23.Provenance.OBSERVED) continue;
7059
+ if (edge.type !== import_types26.EdgeType.CONNECTS_TO) continue;
7060
+ if (edge.provenance !== import_types26.Provenance.OBSERVED) continue;
6114
7061
  const target = graph.getNodeAttributes(edge.target);
6115
- if (target.type !== import_types23.NodeType.DatabaseNode) continue;
7062
+ if (target.type !== import_types26.NodeType.DatabaseNode) continue;
6116
7063
  const observedHost = target.host?.trim();
6117
7064
  if (!observedHost) continue;
6118
7065
  if (observedHost === declaredHost) continue;
@@ -6134,10 +7081,10 @@ function detectCompatDivergences(graph, svcId, svc) {
6134
7081
  const deps = svc.dependencies ?? {};
6135
7082
  for (const edgeId of graph.outboundEdges(svcId)) {
6136
7083
  const edge = graph.getEdgeAttributes(edgeId);
6137
- if (edge.type !== import_types23.EdgeType.CONNECTS_TO) continue;
6138
- if (edge.provenance !== import_types23.Provenance.OBSERVED) continue;
7084
+ if (edge.type !== import_types26.EdgeType.CONNECTS_TO) continue;
7085
+ if (edge.provenance !== import_types26.Provenance.OBSERVED) continue;
6139
7086
  const target = graph.getNodeAttributes(edge.target);
6140
- if (target.type !== import_types23.NodeType.DatabaseNode) continue;
7087
+ if (target.type !== import_types26.NodeType.DatabaseNode) continue;
6141
7088
  for (const pair of compatPairs()) {
6142
7089
  if (pair.engine !== target.engine) continue;
6143
7090
  const declared = deps[pair.driver];
@@ -6196,7 +7143,7 @@ function suppressHostMismatchHalves(all) {
6196
7143
  for (const d of all) {
6197
7144
  if (d.type !== "host-mismatch") continue;
6198
7145
  observedHalf.add(`${d.source}->${d.target}`);
6199
- declaredHalf.add((0, import_types23.databaseId)(d.extractedHost));
7146
+ declaredHalf.add((0, import_types26.databaseId)(d.extractedHost));
6200
7147
  }
6201
7148
  if (observedHalf.size === 0) return all;
6202
7149
  return all.filter((d) => {
@@ -6215,7 +7162,7 @@ function computeDivergences(graph, opts = {}) {
6215
7162
  }
6216
7163
  graph.forEachNode((nodeId, attrs) => {
6217
7164
  const n = attrs;
6218
- if (n.type !== import_types23.NodeType.ServiceNode) return;
7165
+ if (n.type !== import_types26.NodeType.ServiceNode) return;
6219
7166
  const svc = n;
6220
7167
  for (const d of detectHostMismatch(graph, nodeId, svc)) all.push(d);
6221
7168
  for (const d of detectCompatDivergences(graph, nodeId, svc)) all.push(d);
@@ -6249,7 +7196,7 @@ function computeDivergences(graph, opts = {}) {
6249
7196
  if (a.source !== b.source) return a.source.localeCompare(b.source);
6250
7197
  return a.target.localeCompare(b.target);
6251
7198
  });
6252
- return import_types23.DivergenceResultSchema.parse({
7199
+ return import_types26.DivergenceResultSchema.parse({
6253
7200
  divergences: filtered,
6254
7201
  totalAffected: filtered.length,
6255
7202
  computedAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -6258,9 +7205,9 @@ function computeDivergences(graph, opts = {}) {
6258
7205
 
6259
7206
  // src/persist.ts
6260
7207
  init_cjs_shims();
6261
- var import_node_fs20 = require("fs");
6262
- var import_node_path35 = __toESM(require("path"), 1);
6263
- var import_types24 = 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");
6264
7211
  var SCHEMA_VERSION = 4;
6265
7212
  function migrateV1ToV2(payload) {
6266
7213
  const nodes = payload.graph.nodes;
@@ -6282,12 +7229,12 @@ function migrateV2ToV3(payload) {
6282
7229
  for (const edge of edges) {
6283
7230
  const attrs = edge.attributes;
6284
7231
  if (!attrs || attrs.provenance !== "FRONTIER") continue;
6285
- attrs.provenance = import_types24.Provenance.OBSERVED;
7232
+ attrs.provenance = import_types27.Provenance.OBSERVED;
6286
7233
  const type = typeof attrs.type === "string" ? attrs.type : void 0;
6287
7234
  const source = typeof attrs.source === "string" ? attrs.source : void 0;
6288
7235
  const target = typeof attrs.target === "string" ? attrs.target : void 0;
6289
7236
  if (type && source && target) {
6290
- const newId = (0, import_types24.observedEdgeId)(source, target, type);
7237
+ const newId = (0, import_types27.observedEdgeId)(source, target, type);
6291
7238
  attrs.id = newId;
6292
7239
  if (edge.key) edge.key = newId;
6293
7240
  }
@@ -6296,7 +7243,7 @@ function migrateV2ToV3(payload) {
6296
7243
  return { ...payload, schemaVersion: 3 };
6297
7244
  }
6298
7245
  async function ensureDir(filePath) {
6299
- await import_node_fs20.promises.mkdir(import_node_path35.default.dirname(filePath), { recursive: true });
7246
+ await import_node_fs21.promises.mkdir(import_node_path38.default.dirname(filePath), { recursive: true });
6300
7247
  }
6301
7248
  async function saveGraphToDisk(graph, outPath) {
6302
7249
  await ensureDir(outPath);
@@ -6306,13 +7253,13 @@ async function saveGraphToDisk(graph, outPath) {
6306
7253
  graph: graph.export()
6307
7254
  };
6308
7255
  const tmp = `${outPath}.tmp`;
6309
- await import_node_fs20.promises.writeFile(tmp, JSON.stringify(payload), "utf8");
6310
- 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);
6311
7258
  }
6312
7259
  async function loadGraphFromDisk(graph, outPath) {
6313
7260
  let raw;
6314
7261
  try {
6315
- raw = await import_node_fs20.promises.readFile(outPath, "utf8");
7262
+ raw = await import_node_fs21.promises.readFile(outPath, "utf8");
6316
7263
  } catch (err) {
6317
7264
  if (err.code === "ENOENT") return;
6318
7265
  throw err;
@@ -6377,8 +7324,8 @@ function startPersistLoop(graph, outPath, opts = {}) {
6377
7324
 
6378
7325
  // src/gitignore.ts
6379
7326
  init_cjs_shims();
6380
- var import_node_fs21 = require("fs");
6381
- var import_node_path36 = __toESM(require("path"), 1);
7327
+ var import_node_fs22 = require("fs");
7328
+ var import_node_path39 = __toESM(require("path"), 1);
6382
7329
  var NEAT_OUT_LINE = "neat-out/";
6383
7330
  var NEAT_HEADER = "# NEAT \u2014 machine-local snapshots and events";
6384
7331
  function isNeatOutLine(line) {
@@ -6386,15 +7333,15 @@ function isNeatOutLine(line) {
6386
7333
  return trimmed === "neat-out/" || trimmed === "neat-out";
6387
7334
  }
6388
7335
  async function ensureNeatOutIgnored(projectDir) {
6389
- const file = import_node_path36.default.join(projectDir, ".gitignore");
7336
+ const file = import_node_path39.default.join(projectDir, ".gitignore");
6390
7337
  let existing = null;
6391
7338
  try {
6392
- existing = await import_node_fs21.promises.readFile(file, "utf8");
7339
+ existing = await import_node_fs22.promises.readFile(file, "utf8");
6393
7340
  } catch (err) {
6394
7341
  if (err.code !== "ENOENT") throw err;
6395
7342
  }
6396
7343
  if (existing === null) {
6397
- await import_node_fs21.promises.writeFile(file, `${NEAT_HEADER}
7344
+ await import_node_fs22.promises.writeFile(file, `${NEAT_HEADER}
6398
7345
  ${NEAT_OUT_LINE}
6399
7346
  `, "utf8");
6400
7347
  return { action: "created", file };
@@ -6407,13 +7354,13 @@ ${NEAT_OUT_LINE}
6407
7354
  ${NEAT_HEADER}
6408
7355
  ${NEAT_OUT_LINE}
6409
7356
  `;
6410
- await import_node_fs21.promises.writeFile(file, existing + appended, "utf8");
7357
+ await import_node_fs22.promises.writeFile(file, existing + appended, "utf8");
6411
7358
  return { action: "added", file };
6412
7359
  }
6413
7360
 
6414
7361
  // src/summary.ts
6415
7362
  init_cjs_shims();
6416
- var import_types25 = require("@neat.is/types");
7363
+ var import_types28 = require("@neat.is/types");
6417
7364
  function renderOtelEnvBlock() {
6418
7365
  return [
6419
7366
  "for prod OTel routing, set these in your deploy platform's env:",
@@ -6423,19 +7370,19 @@ function renderOtelEnvBlock() {
6423
7370
  }
6424
7371
  function findIncompatServices(nodes) {
6425
7372
  return nodes.filter(
6426
- (n) => n.type === import_types25.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
6427
7374
  );
6428
7375
  }
6429
7376
  function servicesWithoutObserved(nodes, edges) {
6430
7377
  const seen = /* @__PURE__ */ new Set();
6431
7378
  for (const e of edges) {
6432
- if (e.provenance === import_types25.Provenance.OBSERVED) {
7379
+ if (e.provenance === import_types28.Provenance.OBSERVED) {
6433
7380
  seen.add(e.source);
6434
7381
  seen.add(e.target);
6435
7382
  }
6436
7383
  }
6437
7384
  return nodes.filter(
6438
- (n) => n.type === import_types25.NodeType.ServiceNode && !seen.has(n.id)
7385
+ (n) => n.type === import_types28.NodeType.ServiceNode && !seen.has(n.id)
6439
7386
  );
6440
7387
  }
6441
7388
  function formatDivergence(d) {
@@ -6509,27 +7456,27 @@ function formatIncompat(inc) {
6509
7456
 
6510
7457
  // src/watch.ts
6511
7458
  init_cjs_shims();
6512
- var import_node_fs29 = __toESM(require("fs"), 1);
6513
- var import_node_path46 = __toESM(require("path"), 1);
7459
+ var import_node_fs30 = __toESM(require("fs"), 1);
7460
+ var import_node_path49 = __toESM(require("path"), 1);
6514
7461
  var import_chokidar = __toESM(require("chokidar"), 1);
6515
7462
 
6516
7463
  // src/api.ts
6517
7464
  init_cjs_shims();
6518
7465
  var import_fastify = __toESM(require("fastify"), 1);
6519
7466
  var import_cors = __toESM(require("@fastify/cors"), 1);
6520
- var import_types27 = require("@neat.is/types");
7467
+ var import_types30 = require("@neat.is/types");
6521
7468
 
6522
7469
  // src/extend/index.ts
6523
7470
  init_cjs_shims();
6524
- var import_node_fs23 = require("fs");
6525
- var import_node_path38 = __toESM(require("path"), 1);
7471
+ var import_node_fs24 = require("fs");
7472
+ var import_node_path41 = __toESM(require("path"), 1);
6526
7473
  var import_node_os2 = __toESM(require("os"), 1);
6527
7474
  var import_instrumentation_registry = require("@neat.is/instrumentation-registry");
6528
7475
 
6529
7476
  // src/installers/package-manager.ts
6530
7477
  init_cjs_shims();
6531
- var import_node_fs22 = require("fs");
6532
- var import_node_path37 = __toESM(require("path"), 1);
7478
+ var import_node_fs23 = require("fs");
7479
+ var import_node_path40 = __toESM(require("path"), 1);
6533
7480
  var import_node_child_process = require("child_process");
6534
7481
  var LOCKFILE_PRIORITY = [
6535
7482
  { lockfile: "bun.lockb", pm: "bun", args: ["install", "--no-summary"] },
@@ -6544,29 +7491,29 @@ var LOCKFILE_PRIORITY = [
6544
7491
  var NPM_FALLBACK_ARGS = ["install", "--no-audit", "--no-fund", "--prefer-offline"];
6545
7492
  async function exists2(p) {
6546
7493
  try {
6547
- await import_node_fs22.promises.access(p);
7494
+ await import_node_fs23.promises.access(p);
6548
7495
  return true;
6549
7496
  } catch {
6550
7497
  return false;
6551
7498
  }
6552
7499
  }
6553
7500
  async function detectPackageManager(serviceDir) {
6554
- let dir = import_node_path37.default.resolve(serviceDir);
7501
+ let dir = import_node_path40.default.resolve(serviceDir);
6555
7502
  const stops = /* @__PURE__ */ new Set();
6556
7503
  for (let i = 0; i < 64; i++) {
6557
7504
  if (stops.has(dir)) break;
6558
7505
  stops.add(dir);
6559
7506
  for (const candidate of LOCKFILE_PRIORITY) {
6560
- const lockPath = import_node_path37.default.join(dir, candidate.lockfile);
7507
+ const lockPath = import_node_path40.default.join(dir, candidate.lockfile);
6561
7508
  if (await exists2(lockPath)) {
6562
7509
  return { pm: candidate.pm, cwd: dir, args: [...candidate.args] };
6563
7510
  }
6564
7511
  }
6565
- const parent = import_node_path37.default.dirname(dir);
7512
+ const parent = import_node_path40.default.dirname(dir);
6566
7513
  if (parent === dir) break;
6567
7514
  dir = parent;
6568
7515
  }
6569
- return { pm: "npm", cwd: import_node_path37.default.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
7516
+ return { pm: "npm", cwd: import_node_path40.default.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
6570
7517
  }
6571
7518
  async function runPackageManagerInstall(cmd) {
6572
7519
  return new Promise((resolve) => {
@@ -6608,30 +7555,30 @@ ${err.message}`
6608
7555
  // src/extend/index.ts
6609
7556
  async function fileExists2(p) {
6610
7557
  try {
6611
- await import_node_fs23.promises.access(p);
7558
+ await import_node_fs24.promises.access(p);
6612
7559
  return true;
6613
7560
  } catch {
6614
7561
  return false;
6615
7562
  }
6616
7563
  }
6617
7564
  async function readPackageJson(scanPath) {
6618
- const pkgPath = import_node_path38.default.join(scanPath, "package.json");
6619
- 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");
6620
7567
  return JSON.parse(raw);
6621
7568
  }
6622
7569
  async function findHookFiles(scanPath) {
6623
- const entries = await import_node_fs23.promises.readdir(scanPath);
7570
+ const entries = await import_node_fs24.promises.readdir(scanPath);
6624
7571
  return entries.filter(
6625
7572
  (e) => (e.startsWith("instrumentation") || e.startsWith("otel-init")) && /\.(ts|js)$/.test(e)
6626
7573
  ).sort();
6627
7574
  }
6628
7575
  function extendLogPath() {
6629
- return process.env.NEAT_EXTEND_LOG ?? import_node_path38.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");
6630
7577
  }
6631
7578
  async function appendExtendLog(entry2) {
6632
7579
  const logPath = extendLogPath();
6633
- await import_node_fs23.promises.mkdir(import_node_path38.default.dirname(logPath), { recursive: true });
6634
- 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");
6635
7582
  }
6636
7583
  function splicedContent(fileContent, snippet2) {
6637
7584
  if (fileContent.includes("__INSTRUMENTATION_BLOCK__")) {
@@ -6689,7 +7636,7 @@ function lookupInstrumentation(library, installedVersion) {
6689
7636
  }
6690
7637
  async function describeProjectInstrumentation(ctx) {
6691
7638
  const hookFiles = await findHookFiles(ctx.scanPath);
6692
- const envNeat = await fileExists2(import_node_path38.default.join(ctx.scanPath, ".env.neat"));
7639
+ const envNeat = await fileExists2(import_node_path41.default.join(ctx.scanPath, ".env.neat"));
6693
7640
  const registryInstrPackages = new Set(
6694
7641
  (0, import_instrumentation_registry.list)().map((e) => e.instrumentation_package).filter((p) => !!p)
6695
7642
  );
@@ -6711,31 +7658,31 @@ async function applyExtension(ctx, args, options) {
6711
7658
  );
6712
7659
  }
6713
7660
  for (const file of hookFiles) {
6714
- const content = await import_node_fs23.promises.readFile(import_node_path38.default.join(ctx.scanPath, file), "utf8");
7661
+ const content = await import_node_fs24.promises.readFile(import_node_path41.default.join(ctx.scanPath, file), "utf8");
6715
7662
  if (content.includes(args.registration_snippet)) {
6716
7663
  return { library: args.library, filesTouched: [], depsAdded: [], installOutput: "", alreadyApplied: true };
6717
7664
  }
6718
7665
  }
6719
7666
  const primaryFile = hookFiles[0];
6720
- const primaryPath = import_node_path38.default.join(ctx.scanPath, primaryFile);
7667
+ const primaryPath = import_node_path41.default.join(ctx.scanPath, primaryFile);
6721
7668
  const filesTouched = [];
6722
7669
  const depsAdded = [];
6723
- const pkgPath = import_node_path38.default.join(ctx.scanPath, "package.json");
7670
+ const pkgPath = import_node_path41.default.join(ctx.scanPath, "package.json");
6724
7671
  const pkg = await readPackageJson(ctx.scanPath);
6725
7672
  if (!(pkg.dependencies ?? {})[args.instrumentation_package]) {
6726
7673
  pkg.dependencies = { ...pkg.dependencies ?? {}, [args.instrumentation_package]: args.version };
6727
- 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");
6728
7675
  filesTouched.push("package.json");
6729
7676
  depsAdded.push(`${args.instrumentation_package}@${args.version}`);
6730
7677
  }
6731
- const hookContent = await import_node_fs23.promises.readFile(primaryPath, "utf8");
7678
+ const hookContent = await import_node_fs24.promises.readFile(primaryPath, "utf8");
6732
7679
  const patched = splicedContent(hookContent, args.registration_snippet);
6733
7680
  if (!patched) {
6734
7681
  throw new Error(
6735
7682
  `Could not find instrumentation insertion point in ${primaryFile}. Expected __INSTRUMENTATION_BLOCK__, instrumentations.push(, or new NodeSDK(.`
6736
7683
  );
6737
7684
  }
6738
- await import_node_fs23.promises.writeFile(primaryPath, patched, "utf8");
7685
+ await import_node_fs24.promises.writeFile(primaryPath, patched, "utf8");
6739
7686
  filesTouched.push(primaryFile);
6740
7687
  const cmd = await detectPackageManager(ctx.scanPath);
6741
7688
  const installer = options?.runInstall ?? runPackageManagerInstall;
@@ -6766,7 +7713,7 @@ async function dryRunExtension(ctx, args) {
6766
7713
  };
6767
7714
  }
6768
7715
  for (const file of hookFiles) {
6769
- const content = await import_node_fs23.promises.readFile(import_node_path38.default.join(ctx.scanPath, file), "utf8");
7716
+ const content = await import_node_fs24.promises.readFile(import_node_path41.default.join(ctx.scanPath, file), "utf8");
6770
7717
  if (content.includes(args.registration_snippet)) {
6771
7718
  return {
6772
7719
  library: args.library,
@@ -6788,7 +7735,7 @@ async function dryRunExtension(ctx, args) {
6788
7735
  depsToAdd.push(`${args.instrumentation_package}@${args.version}`);
6789
7736
  filesTouched.push("package.json");
6790
7737
  }
6791
- const hookContent = await import_node_fs23.promises.readFile(import_node_path38.default.join(ctx.scanPath, primaryFile), "utf8");
7738
+ const hookContent = await import_node_fs24.promises.readFile(import_node_path41.default.join(ctx.scanPath, primaryFile), "utf8");
6792
7739
  const patched = splicedContent(hookContent, args.registration_snippet);
6793
7740
  if (patched) {
6794
7741
  filesTouched.push(primaryFile);
@@ -6803,28 +7750,28 @@ async function rollbackExtension(ctx, args) {
6803
7750
  if (!await fileExists2(logPath)) {
6804
7751
  return { undone: false, message: "no apply found for library" };
6805
7752
  }
6806
- const raw = await import_node_fs23.promises.readFile(logPath, "utf8");
7753
+ const raw = await import_node_fs24.promises.readFile(logPath, "utf8");
6807
7754
  const entries = raw.trim().split("\n").filter(Boolean).map((line) => JSON.parse(line));
6808
7755
  const match = [...entries].reverse().find((e) => e.project === ctx.project && e.library === args.library);
6809
7756
  if (!match) {
6810
7757
  return { undone: false, message: "no apply found for library" };
6811
7758
  }
6812
- const pkgPath = import_node_path38.default.join(ctx.scanPath, "package.json");
7759
+ const pkgPath = import_node_path41.default.join(ctx.scanPath, "package.json");
6813
7760
  if (await fileExists2(pkgPath)) {
6814
7761
  const pkg = await readPackageJson(ctx.scanPath);
6815
7762
  if (pkg.dependencies?.[match.instrumentation_package]) {
6816
7763
  const { [match.instrumentation_package]: _removed, ...rest } = pkg.dependencies;
6817
7764
  pkg.dependencies = rest;
6818
- 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");
6819
7766
  }
6820
7767
  }
6821
7768
  const hookFiles = await findHookFiles(ctx.scanPath);
6822
7769
  for (const file of hookFiles) {
6823
- const filePath = import_node_path38.default.join(ctx.scanPath, file);
6824
- 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");
6825
7772
  if (content.includes(match.registration_snippet)) {
6826
7773
  const filtered = content.split("\n").filter((line) => !line.includes(match.registration_snippet)).join("\n");
6827
- await import_node_fs23.promises.writeFile(filePath, filtered, "utf8");
7774
+ await import_node_fs24.promises.writeFile(filePath, filtered, "utf8");
6828
7775
  break;
6829
7776
  }
6830
7777
  }
@@ -6836,7 +7783,7 @@ async function rollbackExtension(ctx, args) {
6836
7783
 
6837
7784
  // src/diff.ts
6838
7785
  init_cjs_shims();
6839
- var import_node_fs24 = require("fs");
7786
+ var import_node_fs25 = require("fs");
6840
7787
  async function loadSnapshotForDiff(target) {
6841
7788
  if (/^https?:\/\//i.test(target)) {
6842
7789
  const res = await fetch(target);
@@ -6845,7 +7792,7 @@ async function loadSnapshotForDiff(target) {
6845
7792
  }
6846
7793
  return await res.json();
6847
7794
  }
6848
- const raw = await import_node_fs24.promises.readFile(target, "utf8");
7795
+ const raw = await import_node_fs25.promises.readFile(target, "utf8");
6849
7796
  return JSON.parse(raw);
6850
7797
  }
6851
7798
  function indexEntries(entries) {
@@ -6913,23 +7860,23 @@ function canonicalJson(value) {
6913
7860
 
6914
7861
  // src/projects.ts
6915
7862
  init_cjs_shims();
6916
- var import_node_path39 = __toESM(require("path"), 1);
7863
+ var import_node_path42 = __toESM(require("path"), 1);
6917
7864
  function pathsForProject(project, baseDir) {
6918
7865
  if (project === DEFAULT_PROJECT) {
6919
7866
  return {
6920
- snapshotPath: import_node_path39.default.join(baseDir, "graph.json"),
6921
- errorsPath: import_node_path39.default.join(baseDir, "errors.ndjson"),
6922
- staleEventsPath: import_node_path39.default.join(baseDir, "stale-events.ndjson"),
6923
- embeddingsCachePath: import_node_path39.default.join(baseDir, "embeddings.json"),
6924
- policyViolationsPath: import_node_path39.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")
6925
7872
  };
6926
7873
  }
6927
7874
  return {
6928
- snapshotPath: import_node_path39.default.join(baseDir, `${project}.json`),
6929
- errorsPath: import_node_path39.default.join(baseDir, `errors.${project}.ndjson`),
6930
- staleEventsPath: import_node_path39.default.join(baseDir, `stale-events.${project}.ndjson`),
6931
- embeddingsCachePath: import_node_path39.default.join(baseDir, `embeddings.${project}.json`),
6932
- policyViolationsPath: import_node_path39.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`)
6933
7880
  };
6934
7881
  }
6935
7882
  var Projects = class {
@@ -6965,28 +7912,28 @@ var Projects = class {
6965
7912
 
6966
7913
  // src/registry.ts
6967
7914
  init_cjs_shims();
6968
- var import_node_fs25 = require("fs");
7915
+ var import_node_fs26 = require("fs");
6969
7916
  var import_node_os3 = __toESM(require("os"), 1);
6970
- var import_node_path40 = __toESM(require("path"), 1);
6971
- var import_types26 = require("@neat.is/types");
7917
+ var import_node_path43 = __toESM(require("path"), 1);
7918
+ var import_types29 = require("@neat.is/types");
6972
7919
  var LOCK_TIMEOUT_MS = 5e3;
6973
7920
  var LOCK_RETRY_MS = 50;
6974
7921
  function neatHome() {
6975
7922
  const override = process.env.NEAT_HOME;
6976
- if (override && override.length > 0) return import_node_path40.default.resolve(override);
6977
- return import_node_path40.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");
6978
7925
  }
6979
7926
  function registryPath() {
6980
- return import_node_path40.default.join(neatHome(), "projects.json");
7927
+ return import_node_path43.default.join(neatHome(), "projects.json");
6981
7928
  }
6982
7929
  function registryLockPath() {
6983
- return import_node_path40.default.join(neatHome(), "projects.json.lock");
7930
+ return import_node_path43.default.join(neatHome(), "projects.json.lock");
6984
7931
  }
6985
7932
  function daemonPidPath() {
6986
- return import_node_path40.default.join(neatHome(), "neatd.pid");
7933
+ return import_node_path43.default.join(neatHome(), "neatd.pid");
6987
7934
  }
6988
7935
  function daemonsDir() {
6989
- return import_node_path40.default.join(neatHome(), "daemons");
7936
+ return import_node_path43.default.join(neatHome(), "daemons");
6990
7937
  }
6991
7938
  function isFiniteInt(v) {
6992
7939
  return typeof v === "number" && Number.isFinite(v);
@@ -7019,7 +7966,7 @@ async function discoverDaemons(probe = defaultDiscoveryProbe) {
7019
7966
  const dir = daemonsDir();
7020
7967
  let names;
7021
7968
  try {
7022
- names = await import_node_fs25.promises.readdir(dir);
7969
+ names = await import_node_fs26.promises.readdir(dir);
7023
7970
  } catch (err) {
7024
7971
  if (err.code === "ENOENT") return [];
7025
7972
  throw err;
@@ -7027,10 +7974,10 @@ async function discoverDaemons(probe = defaultDiscoveryProbe) {
7027
7974
  const out = [];
7028
7975
  for (const name of names) {
7029
7976
  if (!name.endsWith(".json")) continue;
7030
- const file = import_node_path40.default.join(dir, name);
7977
+ const file = import_node_path43.default.join(dir, name);
7031
7978
  let raw;
7032
7979
  try {
7033
- raw = await import_node_fs25.promises.readFile(file, "utf8");
7980
+ raw = await import_node_fs26.promises.readFile(file, "utf8");
7034
7981
  } catch {
7035
7982
  continue;
7036
7983
  }
@@ -7043,7 +7990,7 @@ async function discoverDaemons(probe = defaultDiscoveryProbe) {
7043
7990
  return out;
7044
7991
  }
7045
7992
  async function removeDaemonRecord(source) {
7046
- await import_node_fs25.promises.unlink(source).catch(() => {
7993
+ await import_node_fs26.promises.unlink(source).catch(() => {
7047
7994
  });
7048
7995
  }
7049
7996
  async function listMachineProjects(probe = defaultDiscoveryProbe) {
@@ -7100,7 +8047,7 @@ function isPidAliveDefault(pid) {
7100
8047
  }
7101
8048
  async function readPidFile(file) {
7102
8049
  try {
7103
- const raw = await import_node_fs25.promises.readFile(file, "utf8");
8050
+ const raw = await import_node_fs26.promises.readFile(file, "utf8");
7104
8051
  const pid = Number.parseInt(raw.trim(), 10);
7105
8052
  return Number.isInteger(pid) && pid > 0 ? pid : void 0;
7106
8053
  } catch {
@@ -7148,32 +8095,32 @@ function lockHolderMessage(holder, lockPath, timeoutMs) {
7148
8095
  }
7149
8096
  }
7150
8097
  async function normalizeProjectPath(input) {
7151
- const resolved = import_node_path40.default.resolve(input);
8098
+ const resolved = import_node_path43.default.resolve(input);
7152
8099
  try {
7153
- return await import_node_fs25.promises.realpath(resolved);
8100
+ return await import_node_fs26.promises.realpath(resolved);
7154
8101
  } catch {
7155
8102
  return resolved;
7156
8103
  }
7157
8104
  }
7158
8105
  async function writeAtomically(target, contents) {
7159
- await import_node_fs25.promises.mkdir(import_node_path40.default.dirname(target), { recursive: true });
8106
+ await import_node_fs26.promises.mkdir(import_node_path43.default.dirname(target), { recursive: true });
7160
8107
  const tmp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
7161
- const fd = await import_node_fs25.promises.open(tmp, "w");
8108
+ const fd = await import_node_fs26.promises.open(tmp, "w");
7162
8109
  try {
7163
8110
  await fd.writeFile(contents, "utf8");
7164
8111
  await fd.sync();
7165
8112
  } finally {
7166
8113
  await fd.close();
7167
8114
  }
7168
- await import_node_fs25.promises.rename(tmp, target);
8115
+ await import_node_fs26.promises.rename(tmp, target);
7169
8116
  }
7170
8117
  async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaultLockHolderProbe) {
7171
8118
  const deadline = Date.now() + timeoutMs;
7172
- await import_node_fs25.promises.mkdir(import_node_path40.default.dirname(lockPath), { recursive: true });
8119
+ await import_node_fs26.promises.mkdir(import_node_path43.default.dirname(lockPath), { recursive: true });
7173
8120
  let probedHolder = false;
7174
8121
  while (true) {
7175
8122
  try {
7176
- const fd = await import_node_fs25.promises.open(lockPath, "wx");
8123
+ const fd = await import_node_fs26.promises.open(lockPath, "wx");
7177
8124
  try {
7178
8125
  await fd.writeFile(`${process.pid}
7179
8126
  `, "utf8");
@@ -7198,7 +8145,7 @@ async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaul
7198
8145
  }
7199
8146
  }
7200
8147
  async function releaseLock(lockPath) {
7201
- await import_node_fs25.promises.unlink(lockPath).catch(() => {
8148
+ await import_node_fs26.promises.unlink(lockPath).catch(() => {
7202
8149
  });
7203
8150
  }
7204
8151
  async function withLock(fn) {
@@ -7214,7 +8161,7 @@ async function readRegistry() {
7214
8161
  const file = registryPath();
7215
8162
  let raw;
7216
8163
  try {
7217
- raw = await import_node_fs25.promises.readFile(file, "utf8");
8164
+ raw = await import_node_fs26.promises.readFile(file, "utf8");
7218
8165
  } catch (err) {
7219
8166
  if (err.code === "ENOENT") {
7220
8167
  return { version: 1, projects: [] };
@@ -7222,10 +8169,10 @@ async function readRegistry() {
7222
8169
  throw err;
7223
8170
  }
7224
8171
  const parsed = JSON.parse(raw);
7225
- return import_types26.RegistryFileSchema.parse(parsed);
8172
+ return import_types29.RegistryFileSchema.parse(parsed);
7226
8173
  }
7227
8174
  async function writeRegistry(reg) {
7228
- const validated = import_types26.RegistryFileSchema.parse(reg);
8175
+ const validated = import_types29.RegistryFileSchema.parse(reg);
7229
8176
  await writeAtomically(registryPath(), JSON.stringify(validated, null, 2) + "\n");
7230
8177
  }
7231
8178
  var ProjectNameCollisionError = class extends Error {
@@ -7310,7 +8257,7 @@ function pruneTtlMs() {
7310
8257
  }
7311
8258
  async function statPathStatus(p) {
7312
8259
  try {
7313
- const stat = await import_node_fs25.promises.stat(p);
8260
+ const stat = await import_node_fs26.promises.stat(p);
7314
8261
  return stat.isDirectory() ? "present" : "unknown";
7315
8262
  } catch (err) {
7316
8263
  return err.code === "ENOENT" ? "gone" : "unknown";
@@ -7557,11 +8504,11 @@ function registerRoutes(scope, ctx) {
7557
8504
  const candidates = req.query.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
7558
8505
  const parsed = [];
7559
8506
  for (const c of candidates) {
7560
- const r = import_types27.DivergenceTypeSchema.safeParse(c);
8507
+ const r = import_types30.DivergenceTypeSchema.safeParse(c);
7561
8508
  if (!r.success) {
7562
8509
  return reply.code(400).send({
7563
8510
  error: `unknown divergence type "${c}"`,
7564
- allowed: import_types27.DivergenceTypeSchema.options
8511
+ allowed: import_types30.DivergenceTypeSchema.options
7565
8512
  });
7566
8513
  }
7567
8514
  parsed.push(r.data);
@@ -7780,7 +8727,7 @@ function registerRoutes(scope, ctx) {
7780
8727
  const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
7781
8728
  let violations = await log.readAll();
7782
8729
  if (req.query.severity) {
7783
- const sev = import_types27.PolicySeveritySchema.safeParse(req.query.severity);
8730
+ const sev = import_types30.PolicySeveritySchema.safeParse(req.query.severity);
7784
8731
  if (!sev.success) {
7785
8732
  return reply.code(400).send({
7786
8733
  error: "invalid severity",
@@ -7819,7 +8766,7 @@ function registerRoutes(scope, ctx) {
7819
8766
  scope.post("/policies/check", async (req, reply) => {
7820
8767
  const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
7821
8768
  if (!proj) return;
7822
- const parsed = import_types27.PoliciesCheckBodySchema.safeParse(req.body ?? {});
8769
+ const parsed = import_types30.PoliciesCheckBodySchema.safeParse(req.body ?? {});
7823
8770
  if (!parsed.success) {
7824
8771
  return reply.code(400).send({
7825
8772
  error: "invalid /policies/check body",
@@ -8081,41 +9028,46 @@ init_otel();
8081
9028
 
8082
9029
  // src/daemon.ts
8083
9030
  init_cjs_shims();
8084
- var import_node_fs27 = require("fs");
8085
- var import_node_path44 = __toESM(require("path"), 1);
9031
+ var import_node_fs28 = require("fs");
9032
+ var import_node_path47 = __toESM(require("path"), 1);
8086
9033
  var import_node_module = require("module");
8087
9034
  init_otel();
9035
+
9036
+ // src/connectors/index.ts
9037
+ init_cjs_shims();
9038
+
9039
+ // src/daemon.ts
8088
9040
  init_auth();
8089
9041
 
8090
9042
  // src/unrouted.ts
8091
9043
  init_cjs_shims();
8092
- var import_node_fs26 = require("fs");
8093
- var import_node_path43 = __toESM(require("path"), 1);
9044
+ var import_node_fs27 = require("fs");
9045
+ var import_node_path46 = __toESM(require("path"), 1);
8094
9046
 
8095
9047
  // src/daemon.ts
8096
- var import_types28 = require("@neat.is/types");
9048
+ var import_types31 = require("@neat.is/types");
8097
9049
  function daemonJsonPath(scanPath) {
8098
- return import_node_path44.default.join(scanPath, "neat-out", "daemon.json");
9050
+ return import_node_path47.default.join(scanPath, "neat-out", "daemon.json");
8099
9051
  }
8100
9052
  function daemonsDiscoveryDir(home) {
8101
9053
  const base = home && home.length > 0 ? home : neatHomeFromEnv();
8102
- return import_node_path44.default.join(base, "daemons");
9054
+ return import_node_path47.default.join(base, "daemons");
8103
9055
  }
8104
9056
  function daemonDiscoveryPath(project, home) {
8105
- return import_node_path44.default.join(daemonsDiscoveryDir(home), `${sanitizeDiscoveryName(project)}.json`);
9057
+ return import_node_path47.default.join(daemonsDiscoveryDir(home), `${sanitizeDiscoveryName(project)}.json`);
8106
9058
  }
8107
9059
  function sanitizeDiscoveryName(project) {
8108
9060
  return project.replace(/[^A-Za-z0-9._-]/g, "_");
8109
9061
  }
8110
9062
  function neatHomeFromEnv() {
8111
9063
  const env = process.env.NEAT_HOME;
8112
- if (env && env.length > 0) return import_node_path44.default.resolve(env);
9064
+ if (env && env.length > 0) return import_node_path47.default.resolve(env);
8113
9065
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
8114
- return import_node_path44.default.join(home, ".neat");
9066
+ return import_node_path47.default.join(home, ".neat");
8115
9067
  }
8116
9068
  async function readDaemonRecord(scanPath) {
8117
9069
  try {
8118
- const raw = await import_node_fs27.promises.readFile(daemonJsonPath(scanPath), "utf8");
9070
+ const raw = await import_node_fs28.promises.readFile(daemonJsonPath(scanPath), "utf8");
8119
9071
  const parsed = JSON.parse(raw);
8120
9072
  if (typeof parsed.project === "string" && parsed.ports && typeof parsed.ports.rest === "number" && typeof parsed.ports.otlp === "number" && typeof parsed.ports.web === "number") {
8121
9073
  return parsed;
@@ -8155,7 +9107,7 @@ async function clearDaemonRecord(record, home) {
8155
9107
  } catch {
8156
9108
  }
8157
9109
  try {
8158
- await import_node_fs27.promises.unlink(daemonDiscoveryPath(record.project, home));
9110
+ await import_node_fs28.promises.unlink(daemonDiscoveryPath(record.project, home));
8159
9111
  } catch {
8160
9112
  }
8161
9113
  }
@@ -8181,8 +9133,8 @@ init_otel_grpc();
8181
9133
 
8182
9134
  // src/search.ts
8183
9135
  init_cjs_shims();
8184
- var import_node_fs28 = require("fs");
8185
- var import_node_path45 = __toESM(require("path"), 1);
9136
+ var import_node_fs29 = require("fs");
9137
+ var import_node_path48 = __toESM(require("path"), 1);
8186
9138
  var import_node_crypto3 = require("crypto");
8187
9139
  var DEFAULT_LIMIT = 10;
8188
9140
  var NOMIC_DIM = 768;
@@ -8217,6 +9169,30 @@ function embedText(node) {
8217
9169
  if (filePath) parts.push(`path=${filePath}`);
8218
9170
  break;
8219
9171
  }
9172
+ case "RouteNode": {
9173
+ const method = node.method;
9174
+ const tmpl = node.pathTemplate;
9175
+ if (method) parts.push(`method=${method}`);
9176
+ if (tmpl) parts.push(`path=${tmpl}`);
9177
+ break;
9178
+ }
9179
+ case "GraphQLOperationNode": {
9180
+ const opType = node.operationType;
9181
+ if (opType) parts.push(`operationType=${opType}`);
9182
+ break;
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
+ }
8220
9196
  default:
8221
9197
  break;
8222
9198
  }
@@ -8312,7 +9288,7 @@ async function pickEmbedder() {
8312
9288
  }
8313
9289
  async function readCache(cachePath) {
8314
9290
  try {
8315
- const raw = await import_node_fs28.promises.readFile(cachePath, "utf8");
9291
+ const raw = await import_node_fs29.promises.readFile(cachePath, "utf8");
8316
9292
  const parsed = JSON.parse(raw);
8317
9293
  if (parsed.version !== 1) return null;
8318
9294
  return parsed;
@@ -8321,8 +9297,8 @@ async function readCache(cachePath) {
8321
9297
  }
8322
9298
  }
8323
9299
  async function writeCache(cachePath, cache) {
8324
- await import_node_fs28.promises.mkdir(import_node_path45.default.dirname(cachePath), { recursive: true });
8325
- 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));
8326
9302
  }
8327
9303
  var VectorIndex = class {
8328
9304
  constructor(embedder, cachePath) {
@@ -8479,8 +9455,8 @@ var ALL_PHASES = [
8479
9455
  ];
8480
9456
  function classifyChange(relPath) {
8481
9457
  const phases = /* @__PURE__ */ new Set();
8482
- const base = import_node_path46.default.basename(relPath).toLowerCase();
8483
- const segments = relPath.split(import_node_path46.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());
8484
9460
  if (base === "package.json" || base === "requirements.txt" || base === "pyproject.toml" || base === "setup.py") {
8485
9461
  phases.add("services");
8486
9462
  phases.add("aliases");
@@ -8601,16 +9577,16 @@ function countWatchableDirs(scanPath, limit) {
8601
9577
  if (count >= limit) return;
8602
9578
  let entries;
8603
9579
  try {
8604
- entries = import_node_fs29.default.readdirSync(dir, { withFileTypes: true });
9580
+ entries = import_node_fs30.default.readdirSync(dir, { withFileTypes: true });
8605
9581
  } catch {
8606
9582
  return;
8607
9583
  }
8608
9584
  for (const e of entries) {
8609
9585
  if (count >= limit) return;
8610
9586
  if (!e.isDirectory()) continue;
8611
- if (IGNORED_WATCH_PATHS.some((re) => re.test(import_node_path46.default.join(dir, e.name) + import_node_path46.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;
8612
9588
  count++;
8613
- if (depth < 2) visit(import_node_path46.default.join(dir, e.name), depth + 1);
9589
+ if (depth < 2) visit(import_node_path49.default.join(dir, e.name), depth + 1);
8614
9590
  }
8615
9591
  };
8616
9592
  visit(scanPath, 0);
@@ -8628,8 +9604,8 @@ async function startWatch(graph, opts) {
8628
9604
  const projectName = opts.project ?? DEFAULT_PROJECT;
8629
9605
  await loadGraphFromDisk(graph, opts.outPath);
8630
9606
  const detachEventBus = attachGraphToEventBus(graph, { project: projectName });
8631
- const policyFilePath = import_node_path46.default.join(opts.scanPath, "policy.json");
8632
- const policyViolationsPath = import_node_path46.default.join(import_node_path46.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");
8633
9609
  let policies = [];
8634
9610
  try {
8635
9611
  policies = await loadPolicyFile(policyFilePath);
@@ -8680,7 +9656,7 @@ async function startWatch(graph, opts) {
8680
9656
  assertBindAuthority(host, auth.authToken);
8681
9657
  const port = opts.port ?? 8080;
8682
9658
  const otelPort = opts.otelPort ?? 4318;
8683
- const cachePath = opts.embeddingsCachePath ?? import_node_path46.default.join(import_node_path46.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");
8684
9660
  let searchIndex;
8685
9661
  try {
8686
9662
  searchIndex = await buildSearchIndex(graph, { cachePath });
@@ -8698,7 +9674,7 @@ async function startWatch(graph, opts) {
8698
9674
  // Paths are derived from the explicit options the watch caller passes
8699
9675
  // — pathsForProject is only used to fill in the embeddings/snapshot
8700
9676
  // fields so the registry shape is complete.
8701
- ...pathsForProject(projectName, import_node_path46.default.dirname(opts.outPath)),
9677
+ ...pathsForProject(projectName, import_node_path49.default.dirname(opts.outPath)),
8702
9678
  snapshotPath: opts.outPath,
8703
9679
  errorsPath: opts.errorsPath,
8704
9680
  staleEventsPath: opts.staleEventsPath
@@ -8816,9 +9792,9 @@ async function startWatch(graph, opts) {
8816
9792
  };
8817
9793
  const onPath = (absPath) => {
8818
9794
  if (shouldIgnore(absPath)) return;
8819
- const rel = import_node_path46.default.relative(opts.scanPath, absPath);
9795
+ const rel = import_node_path49.default.relative(opts.scanPath, absPath);
8820
9796
  if (!rel || rel.startsWith("..")) return;
8821
- pendingPaths.add(rel.split(import_node_path46.default.sep).join("/"));
9797
+ pendingPaths.add(rel.split(import_node_path49.default.sep).join("/"));
8822
9798
  const phases = classifyChange(rel);
8823
9799
  if (phases.size === 0) {
8824
9800
  for (const p of ALL_PHASES) pending.add(p);
@@ -8874,8 +9850,8 @@ async function startWatch(graph, opts) {
8874
9850
 
8875
9851
  // src/deploy/detect.ts
8876
9852
  init_cjs_shims();
8877
- var import_node_fs30 = require("fs");
8878
- var import_node_path47 = __toESM(require("path"), 1);
9853
+ var import_node_fs31 = require("fs");
9854
+ var import_node_path50 = __toESM(require("path"), 1);
8879
9855
  var import_node_child_process2 = require("child_process");
8880
9856
  var import_node_crypto4 = require("crypto");
8881
9857
  function generateToken() {
@@ -8975,21 +9951,21 @@ async function runDeploy(opts = {}) {
8975
9951
  const token = generateToken();
8976
9952
  switch (substrate) {
8977
9953
  case "docker-compose": {
8978
- const artifactPath = import_node_path47.default.join(cwd, "docker-compose.neat.yml");
9954
+ const artifactPath = import_node_path50.default.join(cwd, "docker-compose.neat.yml");
8979
9955
  const contents = emitDockerCompose(cwd);
8980
- await import_node_fs30.promises.writeFile(artifactPath, contents, "utf8");
9956
+ await import_node_fs31.promises.writeFile(artifactPath, contents, "utf8");
8981
9957
  return {
8982
9958
  substrate,
8983
9959
  artifactPath,
8984
9960
  token,
8985
9961
  contents,
8986
- startCommand: `NEAT_AUTH_TOKEN=${token} docker compose -f ${import_node_path47.default.basename(artifactPath)} up -d`
9962
+ startCommand: `NEAT_AUTH_TOKEN=${token} docker compose -f ${import_node_path50.default.basename(artifactPath)} up -d`
8987
9963
  };
8988
9964
  }
8989
9965
  case "systemd": {
8990
- const artifactPath = import_node_path47.default.join(cwd, "neat.service");
9966
+ const artifactPath = import_node_path50.default.join(cwd, "neat.service");
8991
9967
  const contents = emitSystemdUnit(cwd);
8992
- await import_node_fs30.promises.writeFile(artifactPath, contents, "utf8");
9968
+ await import_node_fs31.promises.writeFile(artifactPath, contents, "utf8");
8993
9969
  return {
8994
9970
  substrate,
8995
9971
  artifactPath,
@@ -9022,8 +9998,8 @@ init_cjs_shims();
9022
9998
 
9023
9999
  // src/installers/javascript.ts
9024
10000
  init_cjs_shims();
9025
- var import_node_fs31 = require("fs");
9026
- var import_node_path48 = __toESM(require("path"), 1);
10001
+ var import_node_fs32 = require("fs");
10002
+ var import_node_path51 = __toESM(require("path"), 1);
9027
10003
  var import_semver2 = __toESM(require("semver"), 1);
9028
10004
 
9029
10005
  // src/installers/templates.ts
@@ -9448,6 +10424,9 @@ export async function register() {
9448
10424
  if (process.env.NEXT_RUNTIME === 'nodejs') {
9449
10425
  await import('./instrumentation.node')
9450
10426
  }
10427
+ if (process.env.NEXT_RUNTIME === 'edge') {
10428
+ await import('./instrumentation.edge')
10429
+ }
9451
10430
  }
9452
10431
  `;
9453
10432
  var NEXT_INSTRUMENTATION_JS = `${NEXT_INSTRUMENTATION_HEADER}
@@ -9455,6 +10434,9 @@ export async function register() {
9455
10434
  if (process.env.NEXT_RUNTIME === 'nodejs') {
9456
10435
  await import('./instrumentation.node')
9457
10436
  }
10437
+ if (process.env.NEXT_RUNTIME === 'edge') {
10438
+ await import('./instrumentation.edge')
10439
+ }
9458
10440
  }
9459
10441
  `;
9460
10442
  var NEXT_INSTRUMENTATION_NODE_TS = `${NEXT_INSTRUMENTATION_HEADER}
@@ -9490,6 +10472,18 @@ ${registrations.join("\n")}
9490
10472
  `;
9491
10473
  return template.replace(/__SERVICE_NAME__/g, serviceName).replace(/__PROJECT__/g, projectName).replace(/__INSTRUMENTATION_BLOCK__\n?/g, block);
9492
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;
9493
10487
  var FRAMEWORK_OTEL_INIT_HEADER = "// Generated by `neat init --apply` (ADR-074). OpenTelemetry SDK hook.";
9494
10488
  var FRAMEWORK_OTEL_INIT_TS_BODY = `${FRAMEWORK_OTEL_INIT_HEADER}
9495
10489
  ${OTEL_ESM_NODE_IMPORTS}
@@ -9590,6 +10584,7 @@ var SDK_PACKAGES = [
9590
10584
  { name: "@opentelemetry/sdk-node", version: "^0.57.0" },
9591
10585
  { name: "@opentelemetry/auto-instrumentations-node", version: "^0.55.0" }
9592
10586
  ];
10587
+ var NEXT_EDGE_PACKAGES = [{ name: "@vercel/otel", version: "^2.1.3" }];
9593
10588
  function getMajor(versionRange) {
9594
10589
  if (!versionRange) return 0;
9595
10590
  const match = versionRange.match(/(\d+)/);
@@ -9619,15 +10614,15 @@ var OTEL_ENV = {
9619
10614
  value: "http://localhost:4318/projects/<project>/v1/traces"
9620
10615
  };
9621
10616
  function serviceNodeName(pkg, serviceDir) {
9622
- return pkg.name ?? import_node_path48.default.basename(serviceDir);
10617
+ return pkg.name ?? import_node_path51.default.basename(serviceDir);
9623
10618
  }
9624
10619
  function projectToken(pkg, serviceDir, project) {
9625
10620
  if (project && project.length > 0) return project;
9626
- return pkg.name ?? import_node_path48.default.basename(serviceDir);
10621
+ return pkg.name ?? import_node_path51.default.basename(serviceDir);
9627
10622
  }
9628
10623
  async function readJsonFile(p) {
9629
10624
  try {
9630
- const raw = await import_node_fs31.promises.readFile(p, "utf8");
10625
+ const raw = await import_node_fs32.promises.readFile(p, "utf8");
9631
10626
  return JSON.parse(raw);
9632
10627
  } catch {
9633
10628
  return null;
@@ -9636,16 +10631,16 @@ async function readJsonFile(p) {
9636
10631
  async function detectRuntimeKind(pkgRoot, pkg) {
9637
10632
  const deps = allDeps(pkg);
9638
10633
  if ("react-native" in deps || "expo" in deps) return "react-native";
9639
- const appJson = await readJsonFile(import_node_path48.default.join(pkgRoot, "app.json"));
10634
+ const appJson = await readJsonFile(import_node_path51.default.join(pkgRoot, "app.json"));
9640
10635
  if (appJson && typeof appJson === "object" && "expo" in appJson) {
9641
10636
  return "react-native";
9642
10637
  }
9643
- if (await exists3(import_node_path48.default.join(pkgRoot, "vite.config.js")) || await exists3(import_node_path48.default.join(pkgRoot, "vite.config.ts")) || await exists3(import_node_path48.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) {
9644
10639
  return "browser-bundle";
9645
10640
  }
9646
- if (await exists3(import_node_path48.default.join(pkgRoot, "wrangler.toml"))) return "cloudflare-workers";
9647
- if (await exists3(import_node_path48.default.join(pkgRoot, "bun.lockb"))) return "bun";
9648
- if (await exists3(import_node_path48.default.join(pkgRoot, "deno.json")) || await exists3(import_node_path48.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"))) {
9649
10644
  return "deno";
9650
10645
  }
9651
10646
  const engines = pkg.engines ?? {};
@@ -9654,7 +10649,7 @@ async function detectRuntimeKind(pkgRoot, pkg) {
9654
10649
  }
9655
10650
  async function readPackageJson2(serviceDir) {
9656
10651
  try {
9657
- const raw = await import_node_fs31.promises.readFile(import_node_path48.default.join(serviceDir, "package.json"), "utf8");
10652
+ const raw = await import_node_fs32.promises.readFile(import_node_path51.default.join(serviceDir, "package.json"), "utf8");
9658
10653
  return JSON.parse(raw);
9659
10654
  } catch {
9660
10655
  return null;
@@ -9662,7 +10657,7 @@ async function readPackageJson2(serviceDir) {
9662
10657
  }
9663
10658
  async function exists3(p) {
9664
10659
  try {
9665
- await import_node_fs31.promises.stat(p);
10660
+ await import_node_fs32.promises.stat(p);
9666
10661
  return true;
9667
10662
  } catch {
9668
10663
  return false;
@@ -9670,7 +10665,7 @@ async function exists3(p) {
9670
10665
  }
9671
10666
  async function readFileMaybe(p) {
9672
10667
  try {
9673
- return await import_node_fs31.promises.readFile(p, "utf8");
10668
+ return await import_node_fs32.promises.readFile(p, "utf8");
9674
10669
  } catch {
9675
10670
  return null;
9676
10671
  }
@@ -9698,7 +10693,7 @@ function needsVersionUpgrade(installed, expected) {
9698
10693
  var NEXT_CONFIG_CANDIDATES = ["next.config.js", "next.config.ts", "next.config.mjs"];
9699
10694
  async function findNextConfig(serviceDir) {
9700
10695
  for (const name of NEXT_CONFIG_CANDIDATES) {
9701
- const candidate = import_node_path48.default.join(serviceDir, name);
10696
+ const candidate = import_node_path51.default.join(serviceDir, name);
9702
10697
  if (await exists3(candidate)) return candidate;
9703
10698
  }
9704
10699
  return null;
@@ -9767,7 +10762,7 @@ function hasRemixDependency(pkg) {
9767
10762
  }
9768
10763
  async function findRemixEntry(serviceDir) {
9769
10764
  for (const rel of REMIX_ENTRY_CANDIDATES) {
9770
- const candidate = import_node_path48.default.join(serviceDir, rel);
10765
+ const candidate = import_node_path51.default.join(serviceDir, rel);
9771
10766
  if (await exists3(candidate)) return candidate;
9772
10767
  }
9773
10768
  return null;
@@ -9779,14 +10774,14 @@ function hasSvelteKitDependency(pkg) {
9779
10774
  }
9780
10775
  async function findSvelteKitHooks(serviceDir) {
9781
10776
  for (const rel of SVELTEKIT_HOOKS_CANDIDATES) {
9782
- const candidate = import_node_path48.default.join(serviceDir, rel);
10777
+ const candidate = import_node_path51.default.join(serviceDir, rel);
9783
10778
  if (await exists3(candidate)) return candidate;
9784
10779
  }
9785
10780
  return null;
9786
10781
  }
9787
10782
  async function findSvelteKitConfig(serviceDir) {
9788
10783
  for (const rel of SVELTEKIT_CONFIG_CANDIDATES) {
9789
- const candidate = import_node_path48.default.join(serviceDir, rel);
10784
+ const candidate = import_node_path51.default.join(serviceDir, rel);
9790
10785
  if (await exists3(candidate)) return candidate;
9791
10786
  }
9792
10787
  return null;
@@ -9797,7 +10792,7 @@ function hasNuxtDependency(pkg) {
9797
10792
  }
9798
10793
  async function findNuxtConfig(serviceDir) {
9799
10794
  for (const name of NUXT_CONFIG_CANDIDATES) {
9800
- const candidate = import_node_path48.default.join(serviceDir, name);
10795
+ const candidate = import_node_path51.default.join(serviceDir, name);
9801
10796
  if (await exists3(candidate)) return candidate;
9802
10797
  }
9803
10798
  return null;
@@ -9808,7 +10803,7 @@ function hasAstroDependency(pkg) {
9808
10803
  }
9809
10804
  async function findAstroConfig(serviceDir) {
9810
10805
  for (const name of ASTRO_CONFIG_CANDIDATES) {
9811
- const candidate = import_node_path48.default.join(serviceDir, name);
10806
+ const candidate = import_node_path51.default.join(serviceDir, name);
9812
10807
  if (await exists3(candidate)) return candidate;
9813
10808
  }
9814
10809
  return null;
@@ -9822,7 +10817,7 @@ function parseNextMajor(range) {
9822
10817
  return Number.isFinite(n) ? n : null;
9823
10818
  }
9824
10819
  async function isTypeScriptProject(serviceDir) {
9825
- return exists3(import_node_path48.default.join(serviceDir, "tsconfig.json"));
10820
+ return exists3(import_node_path51.default.join(serviceDir, "tsconfig.json"));
9826
10821
  }
9827
10822
  var INDEX_EXTENSIONS = [".ts", ".tsx", ".js", ".mjs", ".cjs"];
9828
10823
  var INDEX_CANDIDATES = INDEX_EXTENSIONS.map((ext) => `index${ext}`);
@@ -9871,7 +10866,7 @@ function entryFromScript(script) {
9871
10866
  }
9872
10867
  async function resolveEntry(serviceDir, pkg) {
9873
10868
  if (typeof pkg.main === "string" && pkg.main.length > 0) {
9874
- const candidate = import_node_path48.default.resolve(serviceDir, pkg.main);
10869
+ const candidate = import_node_path51.default.resolve(serviceDir, pkg.main);
9875
10870
  if (await exists3(candidate)) return candidate;
9876
10871
  }
9877
10872
  if (pkg.bin) {
@@ -9885,40 +10880,40 @@ async function resolveEntry(serviceDir, pkg) {
9885
10880
  if (typeof first === "string") binEntry = first;
9886
10881
  }
9887
10882
  if (binEntry) {
9888
- const candidate = import_node_path48.default.resolve(serviceDir, binEntry);
10883
+ const candidate = import_node_path51.default.resolve(serviceDir, binEntry);
9889
10884
  if (await exists3(candidate)) return candidate;
9890
10885
  }
9891
10886
  }
9892
10887
  const startEntry = entryFromScript(pkg.scripts?.start);
9893
10888
  if (startEntry) {
9894
- const candidate = import_node_path48.default.resolve(serviceDir, startEntry);
10889
+ const candidate = import_node_path51.default.resolve(serviceDir, startEntry);
9895
10890
  if (await exists3(candidate)) return candidate;
9896
10891
  }
9897
10892
  const devEntry = entryFromScript(pkg.scripts?.dev);
9898
10893
  if (devEntry) {
9899
- const candidate = import_node_path48.default.resolve(serviceDir, devEntry);
10894
+ const candidate = import_node_path51.default.resolve(serviceDir, devEntry);
9900
10895
  if (await exists3(candidate)) return candidate;
9901
10896
  }
9902
10897
  for (const rel of SRC_INDEX_CANDIDATES) {
9903
- const candidate = import_node_path48.default.join(serviceDir, rel);
10898
+ const candidate = import_node_path51.default.join(serviceDir, rel);
9904
10899
  if (await exists3(candidate)) return candidate;
9905
10900
  }
9906
10901
  for (const rel of SRC_NAMED_CANDIDATES) {
9907
- const candidate = import_node_path48.default.join(serviceDir, rel);
10902
+ const candidate = import_node_path51.default.join(serviceDir, rel);
9908
10903
  if (await exists3(candidate)) return candidate;
9909
10904
  }
9910
10905
  for (const rel of ROOT_NAMED_CANDIDATES) {
9911
- const candidate = import_node_path48.default.join(serviceDir, rel);
10906
+ const candidate = import_node_path51.default.join(serviceDir, rel);
9912
10907
  if (await exists3(candidate)) return candidate;
9913
10908
  }
9914
10909
  for (const name of INDEX_CANDIDATES) {
9915
- const candidate = import_node_path48.default.join(serviceDir, name);
10910
+ const candidate = import_node_path51.default.join(serviceDir, name);
9916
10911
  if (await exists3(candidate)) return candidate;
9917
10912
  }
9918
10913
  return null;
9919
10914
  }
9920
10915
  function dispatchEntry(entryFile, pkg) {
9921
- const ext = import_node_path48.default.extname(entryFile).toLowerCase();
10916
+ const ext = import_node_path51.default.extname(entryFile).toLowerCase();
9922
10917
  if (ext === ".ts" || ext === ".tsx") return "ts";
9923
10918
  if (ext === ".mjs") return "esm";
9924
10919
  if (ext === ".cjs") return "cjs";
@@ -9935,9 +10930,9 @@ function otelInitContents(flavor) {
9935
10930
  return OTEL_INIT_CJS;
9936
10931
  }
9937
10932
  function injectionLine(flavor, entryFile, otelInitFile) {
9938
- let rel = import_node_path48.default.relative(import_node_path48.default.dirname(entryFile), otelInitFile);
10933
+ let rel = import_node_path51.default.relative(import_node_path51.default.dirname(entryFile), otelInitFile);
9939
10934
  if (!rel.startsWith(".")) rel = `./${rel}`;
9940
- rel = rel.split(import_node_path48.default.sep).join("/");
10935
+ rel = rel.split(import_node_path51.default.sep).join("/");
9941
10936
  if (flavor === "cjs") return `require('${rel}')`;
9942
10937
  if (flavor === "esm") return `import '${rel}'`;
9943
10938
  const tsRel = rel.replace(/\.ts$/, "");
@@ -9950,23 +10945,27 @@ function lineIsOtelInjection(line) {
9950
10945
  }
9951
10946
  async function detectsSrcLayout(serviceDir) {
9952
10947
  const [hasSrcApp, hasSrcPages, hasRootApp, hasRootPages] = await Promise.all([
9953
- exists3(import_node_path48.default.join(serviceDir, "src", "app")),
9954
- exists3(import_node_path48.default.join(serviceDir, "src", "pages")),
9955
- exists3(import_node_path48.default.join(serviceDir, "app")),
9956
- exists3(import_node_path48.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"))
9957
10952
  ]);
9958
10953
  return (hasSrcApp || hasSrcPages) && !hasRootApp && !hasRootPages;
9959
10954
  }
9960
10955
  async function planNext(serviceDir, pkg, manifestPath, nextConfigPath, project) {
9961
10956
  const useTs = await isTypeScriptProject(serviceDir);
9962
10957
  const srcLayout = await detectsSrcLayout(serviceDir);
9963
- const baseDir = srcLayout ? import_node_path48.default.join(serviceDir, "src") : serviceDir;
9964
- const instrumentationFile = import_node_path48.default.join(baseDir, useTs ? "instrumentation.ts" : "instrumentation.js");
9965
- const instrumentationNodeFile = import_node_path48.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(
9966
10961
  baseDir,
9967
10962
  useTs ? "instrumentation.node.ts" : "instrumentation.node.js"
9968
10963
  );
9969
- const envNeatFile = import_node_path48.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");
9970
10969
  const existingDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
9971
10970
  const dependencyEdits = [];
9972
10971
  for (const sdk of SDK_PACKAGES) {
@@ -9978,6 +10977,15 @@ async function planNext(serviceDir, pkg, manifestPath, nextConfigPath, project)
9978
10977
  }
9979
10978
  dependencyEdits.push({ file: manifestPath, kind: "add", name: sdk.name, version: sdk.version });
9980
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
+ }
9981
10989
  const nonBundled = detectNonBundledInstrumentations(pkg);
9982
10990
  for (const inst of nonBundled) {
9983
10991
  if (inst.pkg in existingDeps) {
@@ -10011,6 +11019,17 @@ async function planNext(serviceDir, pkg, manifestPath, nextConfigPath, project)
10011
11019
  skipIfExists: true
10012
11020
  });
10013
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
+ }
10014
11033
  if (!await exists3(envNeatFile)) {
10015
11034
  generatedFiles.push({
10016
11035
  file: envNeatFile,
@@ -10023,7 +11042,7 @@ async function planNext(serviceDir, pkg, manifestPath, nextConfigPath, project)
10023
11042
  const nextMajor = parseNextMajor(nextRange);
10024
11043
  if (nextMajor !== null && nextMajor < 15) {
10025
11044
  try {
10026
- const raw = await import_node_fs31.promises.readFile(nextConfigPath, "utf8");
11045
+ const raw = await import_node_fs32.promises.readFile(nextConfigPath, "utf8");
10027
11046
  if (!raw.includes("instrumentationHook")) {
10028
11047
  nextConfigEdit = {
10029
11048
  file: nextConfigPath,
@@ -10071,7 +11090,7 @@ function buildDependencyEdits(pkg, manifestPath) {
10071
11090
  return edits;
10072
11091
  }
10073
11092
  async function queueEnvNeat(serviceDir, pkg, project, generatedFiles) {
10074
- const envNeatFile = import_node_path48.default.join(serviceDir, ".env.neat");
11093
+ const envNeatFile = import_node_path51.default.join(serviceDir, ".env.neat");
10075
11094
  if (!await exists3(envNeatFile)) {
10076
11095
  generatedFiles.push({
10077
11096
  file: envNeatFile,
@@ -10106,7 +11125,7 @@ function fileImportsOtelHook(raw, specifiers) {
10106
11125
  }
10107
11126
  async function planRemix(serviceDir, pkg, manifestPath, entryFile, project) {
10108
11127
  const useTs = await isTypeScriptProject(serviceDir);
10109
- const otelServerFile = import_node_path48.default.join(
11128
+ const otelServerFile = import_node_path51.default.join(
10110
11129
  serviceDir,
10111
11130
  useTs ? "app/otel.server.ts" : "app/otel.server.js"
10112
11131
  );
@@ -10127,7 +11146,7 @@ async function planRemix(serviceDir, pkg, manifestPath, entryFile, project) {
10127
11146
  await queueEnvNeat(serviceDir, pkg, project, generatedFiles);
10128
11147
  const entrypointEdits = [];
10129
11148
  try {
10130
- const raw = await import_node_fs31.promises.readFile(entryFile, "utf8");
11149
+ const raw = await import_node_fs32.promises.readFile(entryFile, "utf8");
10131
11150
  if (!fileImportsOtelHook(raw, ["./otel.server"])) {
10132
11151
  const lines = raw.split(/\r?\n/);
10133
11152
  const firstReal = lines[0]?.startsWith("#!") ? lines[1] ?? "" : lines[0] ?? "";
@@ -10163,11 +11182,11 @@ async function planRemix(serviceDir, pkg, manifestPath, entryFile, project) {
10163
11182
  }
10164
11183
  async function planSvelteKit(serviceDir, pkg, manifestPath, hooksFile, project) {
10165
11184
  const useTs = await isTypeScriptProject(serviceDir);
10166
- const otelInitFile = import_node_path48.default.join(
11185
+ const otelInitFile = import_node_path51.default.join(
10167
11186
  serviceDir,
10168
11187
  useTs ? "src/otel-init.ts" : "src/otel-init.js"
10169
11188
  );
10170
- const resolvedHooksFile = hooksFile ?? import_node_path48.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");
10171
11190
  const dependencyEdits = buildDependencyEdits(pkg, manifestPath);
10172
11191
  const generatedFiles = [];
10173
11192
  const entrypointEdits = [];
@@ -10192,7 +11211,7 @@ async function planSvelteKit(serviceDir, pkg, manifestPath, hooksFile, project)
10192
11211
  });
10193
11212
  } else {
10194
11213
  try {
10195
- const raw = await import_node_fs31.promises.readFile(hooksFile, "utf8");
11214
+ const raw = await import_node_fs32.promises.readFile(hooksFile, "utf8");
10196
11215
  if (!fileImportsOtelHook(raw, ["./otel-init"])) {
10197
11216
  const lines = raw.split(/\r?\n/);
10198
11217
  const firstReal = lines[0]?.startsWith("#!") ? lines[1] ?? "" : lines[0] ?? "";
@@ -10229,11 +11248,11 @@ async function planSvelteKit(serviceDir, pkg, manifestPath, hooksFile, project)
10229
11248
  }
10230
11249
  async function planNuxt(serviceDir, pkg, manifestPath, project) {
10231
11250
  const useTs = await isTypeScriptProject(serviceDir);
10232
- const otelPluginFile = import_node_path48.default.join(
11251
+ const otelPluginFile = import_node_path51.default.join(
10233
11252
  serviceDir,
10234
11253
  useTs ? "server/plugins/otel.ts" : "server/plugins/otel.js"
10235
11254
  );
10236
- const otelInitFile = import_node_path48.default.join(
11255
+ const otelInitFile = import_node_path51.default.join(
10237
11256
  serviceDir,
10238
11257
  useTs ? "server/plugins/otel-init.ts" : "server/plugins/otel-init.js"
10239
11258
  );
@@ -10284,19 +11303,19 @@ async function planNuxt(serviceDir, pkg, manifestPath, project) {
10284
11303
  var ASTRO_MIDDLEWARE_CANDIDATES = ["src/middleware.ts", "src/middleware.js"];
10285
11304
  async function findAstroMiddleware(serviceDir) {
10286
11305
  for (const rel of ASTRO_MIDDLEWARE_CANDIDATES) {
10287
- const candidate = import_node_path48.default.join(serviceDir, rel);
11306
+ const candidate = import_node_path51.default.join(serviceDir, rel);
10288
11307
  if (await exists3(candidate)) return candidate;
10289
11308
  }
10290
11309
  return null;
10291
11310
  }
10292
11311
  async function planAstro(serviceDir, pkg, manifestPath, project) {
10293
11312
  const useTs = await isTypeScriptProject(serviceDir);
10294
- const otelInitFile = import_node_path48.default.join(
11313
+ const otelInitFile = import_node_path51.default.join(
10295
11314
  serviceDir,
10296
11315
  useTs ? "src/otel-init.ts" : "src/otel-init.js"
10297
11316
  );
10298
11317
  const existingMiddleware = await findAstroMiddleware(serviceDir);
10299
- const middlewareFile = existingMiddleware ?? import_node_path48.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");
10300
11319
  const dependencyEdits = buildDependencyEdits(pkg, manifestPath);
10301
11320
  const generatedFiles = [];
10302
11321
  const entrypointEdits = [];
@@ -10321,7 +11340,7 @@ async function planAstro(serviceDir, pkg, manifestPath, project) {
10321
11340
  });
10322
11341
  } else {
10323
11342
  try {
10324
- const raw = await import_node_fs31.promises.readFile(existingMiddleware, "utf8");
11343
+ const raw = await import_node_fs32.promises.readFile(existingMiddleware, "utf8");
10325
11344
  if (!fileImportsOtelHook(raw, ["./otel-init"])) {
10326
11345
  const lines = raw.split(/\r?\n/);
10327
11346
  const firstReal = lines[0]?.startsWith("#!") ? lines[1] ?? "" : lines[0] ?? "";
@@ -10392,7 +11411,7 @@ async function findFrameworkDispatch(serviceDir, pkg, manifestPath, project) {
10392
11411
  }
10393
11412
  async function plan(serviceDir, opts) {
10394
11413
  const pkg = await readPackageJson2(serviceDir);
10395
- const manifestPath = import_node_path48.default.join(serviceDir, "package.json");
11414
+ const manifestPath = import_node_path51.default.join(serviceDir, "package.json");
10396
11415
  const project = opts?.project;
10397
11416
  const empty = {
10398
11417
  language: "javascript",
@@ -10427,8 +11446,8 @@ async function plan(serviceDir, opts) {
10427
11446
  return { ...empty, libOnly: true };
10428
11447
  }
10429
11448
  const flavor = dispatchEntry(entryFile, pkg);
10430
- const otelInitFile = import_node_path48.default.join(import_node_path48.default.dirname(entryFile), otelInitFilename(flavor));
10431
- const envNeatFile = import_node_path48.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");
10432
11451
  const existingDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
10433
11452
  const dependencyEdits = [];
10434
11453
  for (const sdk of SDK_PACKAGES) {
@@ -10452,7 +11471,7 @@ async function plan(serviceDir, opts) {
10452
11471
  }
10453
11472
  const entrypointEdits = [];
10454
11473
  try {
10455
- const raw = await import_node_fs31.promises.readFile(entryFile, "utf8");
11474
+ const raw = await import_node_fs32.promises.readFile(entryFile, "utf8");
10456
11475
  const lines = raw.split(/\r?\n/);
10457
11476
  const firstReal = lines[0]?.startsWith("#!") ? lines[1] ?? "" : lines[0] ?? "";
10458
11477
  if (!lineIsOtelInjection(firstReal)) {
@@ -10497,14 +11516,14 @@ async function plan(serviceDir, opts) {
10497
11516
  };
10498
11517
  }
10499
11518
  function isAllowedWritePath(serviceDir, target) {
10500
- const rel = import_node_path48.default.relative(serviceDir, target);
11519
+ const rel = import_node_path51.default.relative(serviceDir, target);
10501
11520
  if (rel.startsWith("..")) return false;
10502
- const base = import_node_path48.default.basename(target);
11521
+ const base = import_node_path51.default.basename(target);
10503
11522
  if (base === "package.json") return true;
10504
11523
  if (base === ".env.neat") return true;
10505
11524
  if (/^otel-init\.(?:js|cjs|mjs|ts)$/.test(base)) return true;
10506
- const relPosix = rel.split(import_node_path48.default.sep).join("/");
10507
- 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)) {
10508
11527
  if (relPosix === base) return true;
10509
11528
  if (relPosix === `src/${base}`) return true;
10510
11529
  return false;
@@ -10520,10 +11539,10 @@ function isAllowedWritePath(serviceDir, target) {
10520
11539
  return false;
10521
11540
  }
10522
11541
  async function writeAtomic(file, contents) {
10523
- await import_node_fs31.promises.mkdir(import_node_path48.default.dirname(file), { recursive: true });
11542
+ await import_node_fs32.promises.mkdir(import_node_path51.default.dirname(file), { recursive: true });
10524
11543
  const tmp = `${file}.${process.pid}.${Date.now()}.tmp`;
10525
- await import_node_fs31.promises.writeFile(tmp, contents, "utf8");
10526
- 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);
10527
11546
  }
10528
11547
  async function apply(installPlan) {
10529
11548
  const { serviceDir } = installPlan;
@@ -10599,7 +11618,7 @@ async function apply(installPlan) {
10599
11618
  for (const target of allTargets) {
10600
11619
  if (await exists3(target)) {
10601
11620
  try {
10602
- originals.set(target, await import_node_fs31.promises.readFile(target, "utf8"));
11621
+ originals.set(target, await import_node_fs32.promises.readFile(target, "utf8"));
10603
11622
  } catch {
10604
11623
  }
10605
11624
  }
@@ -10682,14 +11701,14 @@ async function rollback(installPlan, originals, createdFiles) {
10682
11701
  const removed = [];
10683
11702
  for (const [file, raw] of originals.entries()) {
10684
11703
  try {
10685
- await import_node_fs31.promises.writeFile(file, raw, "utf8");
11704
+ await import_node_fs32.promises.writeFile(file, raw, "utf8");
10686
11705
  restored.push(file);
10687
11706
  } catch {
10688
11707
  }
10689
11708
  }
10690
11709
  for (const file of createdFiles) {
10691
11710
  try {
10692
- await import_node_fs31.promises.unlink(file);
11711
+ await import_node_fs32.promises.unlink(file);
10693
11712
  removed.push(file);
10694
11713
  } catch {
10695
11714
  }
@@ -10704,8 +11723,8 @@ async function rollback(installPlan, originals, createdFiles) {
10704
11723
  ...removed.map((f) => `removed: ${f}`),
10705
11724
  ""
10706
11725
  ];
10707
- const rollbackPath = import_node_path48.default.join(installPlan.serviceDir, "neat-rollback.patch");
10708
- 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");
10709
11728
  }
10710
11729
  function injectInstrumentationHook(raw) {
10711
11730
  if (raw.includes("instrumentationHook")) return raw;
@@ -10734,8 +11753,8 @@ var javascriptInstaller = {
10734
11753
 
10735
11754
  // src/installers/python.ts
10736
11755
  init_cjs_shims();
10737
- var import_node_fs32 = require("fs");
10738
- var import_node_path49 = __toESM(require("path"), 1);
11756
+ var import_node_fs33 = require("fs");
11757
+ var import_node_path52 = __toESM(require("path"), 1);
10739
11758
  var SDK_PACKAGES2 = [
10740
11759
  { name: "opentelemetry-distro", version: ">=0.49b0" },
10741
11760
  { name: "opentelemetry-exporter-otlp", version: ">=1.28.0" }
@@ -10747,7 +11766,7 @@ var OTEL_ENV2 = {
10747
11766
  };
10748
11767
  async function exists4(p) {
10749
11768
  try {
10750
- await import_node_fs32.promises.stat(p);
11769
+ await import_node_fs33.promises.stat(p);
10751
11770
  return true;
10752
11771
  } catch {
10753
11772
  return false;
@@ -10756,7 +11775,7 @@ async function exists4(p) {
10756
11775
  async function detect2(serviceDir) {
10757
11776
  const markers = ["requirements.txt", "pyproject.toml", "setup.py"];
10758
11777
  for (const m of markers) {
10759
- if (await exists4(import_node_path49.default.join(serviceDir, m))) return true;
11778
+ if (await exists4(import_node_path52.default.join(serviceDir, m))) return true;
10760
11779
  }
10761
11780
  return false;
10762
11781
  }
@@ -10766,9 +11785,9 @@ function reqPackageName(line) {
10766
11785
  return head.replace(/[<>=!~].*$/, "").toLowerCase();
10767
11786
  }
10768
11787
  async function planRequirementsTxtEdits(serviceDir) {
10769
- const file = import_node_path49.default.join(serviceDir, "requirements.txt");
11788
+ const file = import_node_path52.default.join(serviceDir, "requirements.txt");
10770
11789
  if (!await exists4(file)) return null;
10771
- const raw = await import_node_fs32.promises.readFile(file, "utf8");
11790
+ const raw = await import_node_fs33.promises.readFile(file, "utf8");
10772
11791
  const presentNames = new Set(
10773
11792
  raw.split(/\r?\n/).map(reqPackageName).filter((n) => n.length > 0)
10774
11793
  );
@@ -10776,9 +11795,9 @@ async function planRequirementsTxtEdits(serviceDir) {
10776
11795
  return { manifest: file, missing: [...missing] };
10777
11796
  }
10778
11797
  async function planProcfileEdits(serviceDir) {
10779
- const procfile = import_node_path49.default.join(serviceDir, "Procfile");
11798
+ const procfile = import_node_path52.default.join(serviceDir, "Procfile");
10780
11799
  if (!await exists4(procfile)) return [];
10781
- const raw = await import_node_fs32.promises.readFile(procfile, "utf8");
11800
+ const raw = await import_node_fs33.promises.readFile(procfile, "utf8");
10782
11801
  const edits = [];
10783
11802
  for (const line of raw.split(/\r?\n/)) {
10784
11803
  if (line.length === 0) continue;
@@ -10830,8 +11849,8 @@ async function applyRequirementsTxt(manifest, edits, original) {
10830
11849
  const next = `${original}${trailing}${newlines.join("\n")}
10831
11850
  `;
10832
11851
  const tmp = `${manifest}.${process.pid}.${Date.now()}.tmp`;
10833
- await import_node_fs32.promises.writeFile(tmp, next, "utf8");
10834
- 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);
10835
11854
  }
10836
11855
  async function applyProcfile(procfile, edits, original) {
10837
11856
  let next = original;
@@ -10840,8 +11859,8 @@ async function applyProcfile(procfile, edits, original) {
10840
11859
  next = next.replace(e.before, e.after);
10841
11860
  }
10842
11861
  const tmp = `${procfile}.${process.pid}.${Date.now()}.tmp`;
10843
- await import_node_fs32.promises.writeFile(tmp, next, "utf8");
10844
- 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);
10845
11864
  }
10846
11865
  async function apply2(installPlan) {
10847
11866
  const { serviceDir } = installPlan;
@@ -10854,7 +11873,7 @@ async function apply2(installPlan) {
10854
11873
  const originals = /* @__PURE__ */ new Map();
10855
11874
  for (const file of touched) {
10856
11875
  try {
10857
- originals.set(file, await import_node_fs32.promises.readFile(file, "utf8"));
11876
+ originals.set(file, await import_node_fs33.promises.readFile(file, "utf8"));
10858
11877
  } catch {
10859
11878
  }
10860
11879
  }
@@ -10865,7 +11884,7 @@ async function apply2(installPlan) {
10865
11884
  if (raw === void 0) {
10866
11885
  throw new Error(`python installer: cannot read ${file} during apply`);
10867
11886
  }
10868
- const base = import_node_path49.default.basename(file);
11887
+ const base = import_node_path52.default.basename(file);
10869
11888
  if (base === "requirements.txt") {
10870
11889
  const edits = installPlan.dependencyEdits.filter((e) => e.file === file);
10871
11890
  if (edits.length > 0) {
@@ -10890,7 +11909,7 @@ async function rollback2(installPlan, originals) {
10890
11909
  const restored = [];
10891
11910
  for (const [file, raw] of originals.entries()) {
10892
11911
  try {
10893
- await import_node_fs32.promises.writeFile(file, raw, "utf8");
11912
+ await import_node_fs33.promises.writeFile(file, raw, "utf8");
10894
11913
  restored.push(file);
10895
11914
  } catch {
10896
11915
  }
@@ -10904,8 +11923,8 @@ async function rollback2(installPlan, originals) {
10904
11923
  ...restored.map((f) => `restored: ${f}`),
10905
11924
  ""
10906
11925
  ];
10907
- const rollbackPath = import_node_path49.default.join(installPlan.serviceDir, "neat-rollback.patch");
10908
- 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");
10909
11928
  }
10910
11929
  var pythonInstaller = {
10911
11930
  name: "python",
@@ -11028,10 +12047,10 @@ function renderPatch(sections) {
11028
12047
 
11029
12048
  // src/orchestrator.ts
11030
12049
  init_cjs_shims();
11031
- var import_node_fs33 = require("fs");
12050
+ var import_node_fs34 = require("fs");
11032
12051
  var import_node_http = __toESM(require("http"), 1);
11033
12052
  var import_node_net = __toESM(require("net"), 1);
11034
- var import_node_path50 = __toESM(require("path"), 1);
12053
+ var import_node_path53 = __toESM(require("path"), 1);
11035
12054
  var import_node_child_process3 = require("child_process");
11036
12055
  var import_node_readline = __toESM(require("readline"), 1);
11037
12056
  async function extractAndPersist(opts) {
@@ -11040,7 +12059,7 @@ async function extractAndPersist(opts) {
11040
12059
  const graphKey = opts.projectExplicit ? opts.project : DEFAULT_PROJECT;
11041
12060
  resetGraph(graphKey);
11042
12061
  const graph = getGraph(graphKey);
11043
- const projectPaths = pathsForProject(graphKey, import_node_path50.default.join(opts.scanPath, "neat-out"));
12062
+ const projectPaths = pathsForProject(graphKey, import_node_path53.default.join(opts.scanPath, "neat-out"));
11044
12063
  const extraction = await extractFromDirectory(graph, opts.scanPath, {
11045
12064
  errorsPath: projectPaths.errorsPath
11046
12065
  });
@@ -11092,7 +12111,7 @@ async function applyInstallersOver(services, project, options = {}) {
11092
12111
  libOnly++;
11093
12112
  const appDeps = svc.pkg ? appFrameworkDependencies(svc.pkg) : [];
11094
12113
  if (appDeps.length > 0) {
11095
- const svcName = import_node_path50.default.basename(svc.dir);
12114
+ const svcName = import_node_path53.default.basename(svc.dir);
11096
12115
  const list = appDeps.join(", ");
11097
12116
  console.warn(
11098
12117
  `neat: runtime layer won't engage for ${svcName}: no entry point found.
@@ -11105,7 +12124,7 @@ async function applyInstallersOver(services, project, options = {}) {
11105
12124
  console.log(`skipping ${svc.dir}: browser bundle; browser-OTel support lands in a future release.`);
11106
12125
  } else if (outcome.outcome === "react-native") {
11107
12126
  reactNative++;
11108
- const svcName = import_node_path50.default.basename(svc.dir);
12127
+ const svcName = import_node_path53.default.basename(svc.dir);
11109
12128
  console.log(
11110
12129
  `neat: ${svc.dir} detected as React Native / Expo
11111
12130
  The installer doesn't cover this runtime deterministically.
@@ -11116,7 +12135,7 @@ async function applyInstallersOver(services, project, options = {}) {
11116
12135
  );
11117
12136
  } else if (outcome.outcome === "bun") {
11118
12137
  bun++;
11119
- const svcName = import_node_path50.default.basename(svc.dir);
12138
+ const svcName = import_node_path53.default.basename(svc.dir);
11120
12139
  console.log(
11121
12140
  `neat: ${svc.dir} detected as Bun
11122
12141
  The installer doesn't cover this runtime deterministically.
@@ -11127,7 +12146,7 @@ async function applyInstallersOver(services, project, options = {}) {
11127
12146
  );
11128
12147
  } else if (outcome.outcome === "deno") {
11129
12148
  deno++;
11130
- const svcName = import_node_path50.default.basename(svc.dir);
12149
+ const svcName = import_node_path53.default.basename(svc.dir);
11131
12150
  console.log(
11132
12151
  `neat: ${svc.dir} detected as Deno
11133
12152
  The installer doesn't cover this runtime deterministically.
@@ -11138,7 +12157,7 @@ async function applyInstallersOver(services, project, options = {}) {
11138
12157
  );
11139
12158
  } else if (outcome.outcome === "cloudflare-workers") {
11140
12159
  cloudflareWorkers++;
11141
- const svcName = import_node_path50.default.basename(svc.dir);
12160
+ const svcName = import_node_path53.default.basename(svc.dir);
11142
12161
  console.log(
11143
12162
  `neat: ${svc.dir} detected as Cloudflare Workers
11144
12163
  The installer doesn't cover this runtime deterministically.
@@ -11149,7 +12168,7 @@ async function applyInstallersOver(services, project, options = {}) {
11149
12168
  );
11150
12169
  } else if (outcome.outcome === "electron") {
11151
12170
  electron++;
11152
- const svcName = import_node_path50.default.basename(svc.dir);
12171
+ const svcName = import_node_path53.default.basename(svc.dir);
11153
12172
  console.log(
11154
12173
  `neat: ${svc.dir} detected as Electron
11155
12174
  The installer doesn't cover this runtime deterministically.
@@ -11162,7 +12181,7 @@ async function applyInstallersOver(services, project, options = {}) {
11162
12181
  if (svc.pkg && (outcome.outcome === "instrumented" || outcome.outcome === "already-instrumented")) {
11163
12182
  const gaps = uninstrumentedLibraries(svc.pkg);
11164
12183
  if (gaps.length > 0) {
11165
- const svcName = import_node_path50.default.basename(svc.dir);
12184
+ const svcName = import_node_path53.default.basename(svc.dir);
11166
12185
  const list = gaps.join(", ");
11167
12186
  const subject = gaps.length === 1 ? "this library" : "these libraries";
11168
12187
  const aux = gaps.length === 1 ? "isn't" : "aren't";
@@ -11368,24 +12387,24 @@ async function persistedPortsFor(scanPath) {
11368
12387
  return { rest: record.ports.rest, otlp: record.ports.otlp, web: record.ports.web };
11369
12388
  }
11370
12389
  async function acquireSpawnLock(scanPath) {
11371
- const lockPath = import_node_path50.default.join(scanPath, "neat-out", "daemon.spawn.lock");
11372
- await import_node_fs33.promises.mkdir(import_node_path50.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 });
11373
12392
  const STALE_LOCK_MS = 6e4;
11374
12393
  try {
11375
- const fd = await import_node_fs33.promises.open(lockPath, "wx");
12394
+ const fd = await import_node_fs34.promises.open(lockPath, "wx");
11376
12395
  await fd.writeFile(`${process.pid}
11377
12396
  `, "utf8");
11378
12397
  await fd.close();
11379
12398
  return async () => {
11380
- await import_node_fs33.promises.unlink(lockPath).catch(() => {
12399
+ await import_node_fs34.promises.unlink(lockPath).catch(() => {
11381
12400
  });
11382
12401
  };
11383
12402
  } catch (err) {
11384
12403
  if (err.code !== "EEXIST") return null;
11385
12404
  try {
11386
- const stat = await import_node_fs33.promises.stat(lockPath);
12405
+ const stat = await import_node_fs34.promises.stat(lockPath);
11387
12406
  if (Date.now() - stat.mtimeMs > STALE_LOCK_MS) {
11388
- await import_node_fs33.promises.unlink(lockPath).catch(() => {
12407
+ await import_node_fs34.promises.unlink(lockPath).catch(() => {
11389
12408
  });
11390
12409
  return acquireSpawnLock(scanPath);
11391
12410
  }
@@ -11414,13 +12433,13 @@ async function healthIsForProject(restPort, project) {
11414
12433
  return false;
11415
12434
  }
11416
12435
  function daemonLogPath(projectPath2) {
11417
- return import_node_path50.default.join(projectPath2, "neat-out", "daemon.log");
12436
+ return import_node_path53.default.join(projectPath2, "neat-out", "daemon.log");
11418
12437
  }
11419
12438
  function spawnDaemonDetached(spec) {
11420
- const here = import_node_path50.default.dirname(new URL(importMetaUrl).pathname);
12439
+ const here = import_node_path53.default.dirname(new URL(importMetaUrl).pathname);
11421
12440
  const candidates = [
11422
- import_node_path50.default.join(here, "neatd.cjs"),
11423
- import_node_path50.default.join(here, "neatd.js")
12441
+ import_node_path53.default.join(here, "neatd.cjs"),
12442
+ import_node_path53.default.join(here, "neatd.js")
11424
12443
  ];
11425
12444
  let entry2 = null;
11426
12445
  const fsSync = require("fs");
@@ -11450,7 +12469,7 @@ function spawnDaemonDetached(spec) {
11450
12469
  let logFd = null;
11451
12470
  if (spec) {
11452
12471
  const logPath = daemonLogPath(spec.projectPath);
11453
- fsSync.mkdirSync(import_node_path50.default.dirname(logPath), { recursive: true });
12472
+ fsSync.mkdirSync(import_node_path53.default.dirname(logPath), { recursive: true });
11454
12473
  logFd = fsSync.openSync(logPath, "a");
11455
12474
  }
11456
12475
  const child = (0, import_node_child_process3.spawn)(process.execPath, [entry2, "start"], {
@@ -11489,7 +12508,7 @@ async function runOrchestrator(opts) {
11489
12508
  browser: "skipped"
11490
12509
  }
11491
12510
  };
11492
- 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);
11493
12512
  if (!stat || !stat.isDirectory()) {
11494
12513
  console.error(`neat: ${opts.scanPath} is not a directory`);
11495
12514
  result.exitCode = 2;
@@ -11649,7 +12668,7 @@ async function runOrchestrator(opts) {
11649
12668
  result.steps.browser = openBrowser(dashboardUrl);
11650
12669
  }
11651
12670
  const daemonRunning = result.steps.daemon === "spawned" || result.steps.daemon === "already-running";
11652
- const daemonLog = daemonRunning ? import_node_path50.default.relative(opts.scanPath, daemonLogPath(opts.scanPath)) : null;
12671
+ const daemonLog = daemonRunning ? import_node_path53.default.relative(opts.scanPath, daemonLogPath(opts.scanPath)) : null;
11653
12672
  printSummary(result, graph, dashboardUrl, daemonLog);
11654
12673
  return result;
11655
12674
  }
@@ -11687,11 +12706,11 @@ function printSummary(result, graph, dashboardUrl, daemonLog) {
11687
12706
 
11688
12707
  // src/cli-verbs.ts
11689
12708
  init_cjs_shims();
11690
- var import_node_path51 = __toESM(require("path"), 1);
12709
+ var import_node_path54 = __toESM(require("path"), 1);
11691
12710
 
11692
12711
  // src/cli-client.ts
11693
12712
  init_cjs_shims();
11694
- var import_types29 = require("@neat.is/types");
12713
+ var import_types32 = require("@neat.is/types");
11695
12714
  var HttpError = class extends Error {
11696
12715
  constructor(status2, message, responseBody = "") {
11697
12716
  super(message);
@@ -11716,10 +12735,10 @@ function createHttpClient(baseUrl, bearerToken) {
11716
12735
  const root = baseUrl.replace(/\/$/, "");
11717
12736
  const authHeader = bearerToken && bearerToken.length > 0 ? { authorization: `Bearer ${bearerToken}` } : {};
11718
12737
  return {
11719
- async get(path53) {
12738
+ async get(path56) {
11720
12739
  let res;
11721
12740
  try {
11722
- res = await fetch(`${root}${path53}`, {
12741
+ res = await fetch(`${root}${path56}`, {
11723
12742
  headers: { ...authHeader }
11724
12743
  });
11725
12744
  } catch (err) {
@@ -11731,16 +12750,16 @@ function createHttpClient(baseUrl, bearerToken) {
11731
12750
  const body = await res.text().catch(() => "");
11732
12751
  throw new HttpError(
11733
12752
  res.status,
11734
- `${res.status} ${res.statusText} on GET ${path53}: ${body}`,
12753
+ `${res.status} ${res.statusText} on GET ${path56}: ${body}`,
11735
12754
  body
11736
12755
  );
11737
12756
  }
11738
12757
  return await res.json();
11739
12758
  },
11740
- async post(path53, body) {
12759
+ async post(path56, body) {
11741
12760
  let res;
11742
12761
  try {
11743
- res = await fetch(`${root}${path53}`, {
12762
+ res = await fetch(`${root}${path56}`, {
11744
12763
  method: "POST",
11745
12764
  headers: { "content-type": "application/json", ...authHeader },
11746
12765
  body: JSON.stringify(body)
@@ -11754,7 +12773,7 @@ function createHttpClient(baseUrl, bearerToken) {
11754
12773
  const text = await res.text().catch(() => "");
11755
12774
  throw new HttpError(
11756
12775
  res.status,
11757
- `${res.status} ${res.statusText} on POST ${path53}: ${text}`,
12776
+ `${res.status} ${res.statusText} on POST ${path56}: ${text}`,
11758
12777
  text
11759
12778
  );
11760
12779
  }
@@ -11768,12 +12787,12 @@ function projectPath(project, suffix) {
11768
12787
  }
11769
12788
  async function runRootCause(client, input) {
11770
12789
  const qs = input.errorId ? `?errorId=${encodeURIComponent(input.errorId)}` : "";
11771
- const path53 = projectPath(
12790
+ const path56 = projectPath(
11772
12791
  input.project,
11773
12792
  `/graph/root-cause/${encodeURIComponent(input.errorNode)}${qs}`
11774
12793
  );
11775
12794
  try {
11776
- const result = await client.get(path53);
12795
+ const result = await client.get(path56);
11777
12796
  const arrowPath = result.traversalPath.join(" \u2190 ");
11778
12797
  const provenances = result.edgeProvenances.length ? result.edgeProvenances.join(", ") : "(direct, no edges traversed)";
11779
12798
  const summary = `Root cause for ${input.errorNode} is ${result.rootCauseNode}. ` + result.rootCauseReason + (result.fixRecommendation ? ` Recommended fix: ${result.fixRecommendation}.` : "");
@@ -11799,12 +12818,12 @@ async function runRootCause(client, input) {
11799
12818
  }
11800
12819
  async function runBlastRadius(client, input) {
11801
12820
  const qs = input.depth !== void 0 ? `?depth=${input.depth}` : "";
11802
- const path53 = projectPath(
12821
+ const path56 = projectPath(
11803
12822
  input.project,
11804
12823
  `/graph/blast-radius/${encodeURIComponent(input.nodeId)}${qs}`
11805
12824
  );
11806
12825
  try {
11807
- const result = await client.get(path53);
12826
+ const result = await client.get(path56);
11808
12827
  if (result.totalAffected === 0) {
11809
12828
  return {
11810
12829
  summary: `${result.origin} has no dependents. Nothing else would break if it failed.`
@@ -11833,17 +12852,17 @@ async function runBlastRadius(client, input) {
11833
12852
  }
11834
12853
  }
11835
12854
  function formatBlastEntry(n) {
11836
- const tag = n.edgeProvenance === import_types29.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]" : "";
11837
12856
  return ` \u2022 ${n.nodeId} (distance ${n.distance}, ${n.edgeProvenance})${tag}`;
11838
12857
  }
11839
12858
  async function runDependencies(client, input) {
11840
12859
  const depth = input.depth ?? 3;
11841
- const path53 = projectPath(
12860
+ const path56 = projectPath(
11842
12861
  input.project,
11843
12862
  `/graph/dependencies/${encodeURIComponent(input.nodeId)}?depth=${depth}`
11844
12863
  );
11845
12864
  try {
11846
- const result = await client.get(path53);
12865
+ const result = await client.get(path56);
11847
12866
  if (result.total === 0) {
11848
12867
  return {
11849
12868
  summary: depth === 1 ? `${input.nodeId} has no direct dependencies in the graph.` : `${input.nodeId} has no dependencies (BFS to depth ${depth}).`
@@ -11890,7 +12909,7 @@ async function runObservedDependencies(client, input) {
11890
12909
  if (result.observed) {
11891
12910
  return {
11892
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.`,
11893
- provenance: import_types29.Provenance.OBSERVED
12912
+ provenance: import_types32.Provenance.OBSERVED
11894
12913
  };
11895
12914
  }
11896
12915
  const note = result.hasExtractedOutbound ? " Static (EXTRACTED) dependencies exist but no runtime traffic has been seen \u2014 is OTel running?" : "";
@@ -11900,7 +12919,7 @@ async function runObservedDependencies(client, input) {
11900
12919
  return {
11901
12920
  summary: `${input.nodeId} has ${result.dependencies.length} runtime dependenc${result.dependencies.length === 1 ? "y" : "ies"} confirmed by OTel.`,
11902
12921
  block: blockLines.join("\n"),
11903
- provenance: import_types29.Provenance.OBSERVED
12922
+ provenance: import_types32.Provenance.OBSERVED
11904
12923
  };
11905
12924
  } catch (err) {
11906
12925
  if (err instanceof HttpError && err.status === 404) {
@@ -11935,9 +12954,9 @@ function formatDuration(ms) {
11935
12954
  return `${Math.round(h / 24)}d`;
11936
12955
  }
11937
12956
  async function runIncidents(client, input) {
11938
- const path53 = 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");
11939
12958
  try {
11940
- const body = await client.get(path53);
12959
+ const body = await client.get(path56);
11941
12960
  const events = body.events;
11942
12961
  if (events.length === 0) {
11943
12962
  return {
@@ -11954,7 +12973,7 @@ async function runIncidents(client, input) {
11954
12973
  return {
11955
12974
  summary: `${target} has ${body.total} recorded incident${body.total === 1 ? "" : "s"}; showing the ${ordered.length} most recent.`,
11956
12975
  block: blockLines.join("\n"),
11957
- provenance: import_types29.Provenance.OBSERVED
12976
+ provenance: import_types32.Provenance.OBSERVED
11958
12977
  };
11959
12978
  } catch (err) {
11960
12979
  if (err instanceof HttpError && err.status === 404) {
@@ -12063,7 +13082,7 @@ async function runStaleEdges(client, input) {
12063
13082
  return {
12064
13083
  summary: `${events.length} stale-edge transition${events.length === 1 ? "" : "s"} recorded${input.edgeType ? ` for ${input.edgeType}` : ""}.`,
12065
13084
  block: blockLines.join("\n"),
12066
- provenance: import_types29.Provenance.STALE
13085
+ provenance: import_types32.Provenance.STALE
12067
13086
  };
12068
13087
  }
12069
13088
  async function runPolicies(client, input) {
@@ -12227,7 +13246,7 @@ async function resolveProjectEntry(opts) {
12227
13246
  const cwd = opts.cwd ?? process.cwd();
12228
13247
  const resolvedCwd = await normalizeProjectPath(cwd);
12229
13248
  for (const entry2 of entries) {
12230
- if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${import_node_path51.default.sep}`)) {
13249
+ if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${import_node_path54.default.sep}`)) {
12231
13250
  return entry2;
12232
13251
  }
12233
13252
  }
@@ -12380,7 +13399,7 @@ async function runSync(opts) {
12380
13399
  }
12381
13400
 
12382
13401
  // src/cli.ts
12383
- var import_types30 = require("@neat.is/types");
13402
+ var import_types33 = require("@neat.is/types");
12384
13403
  function isNpxInvocation() {
12385
13404
  if (process.env.npm_command === "exec") return true;
12386
13405
  const execpath = process.env.npm_execpath ?? "";
@@ -12662,7 +13681,7 @@ async function buildPatchSections(services, project) {
12662
13681
  }
12663
13682
  async function runInit(opts) {
12664
13683
  const written = [];
12665
- 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);
12666
13685
  if (!stat || !stat.isDirectory()) {
12667
13686
  console.error(`neat init: ${opts.scanPath} is not a directory`);
12668
13687
  return { exitCode: 2, writtenFiles: written };
@@ -12671,13 +13690,13 @@ async function runInit(opts) {
12671
13690
  printDiscoveryReport(opts, services);
12672
13691
  const sections = opts.noInstall ? [] : await buildPatchSections(services, opts.project);
12673
13692
  const patch = renderPatch(sections);
12674
- const patchPath = import_node_path52.default.join(opts.scanPath, "neat.patch");
13693
+ const patchPath = import_node_path55.default.join(opts.scanPath, "neat.patch");
12675
13694
  if (opts.dryRun) {
12676
- await import_node_fs34.promises.writeFile(patchPath, patch, "utf8");
13695
+ await import_node_fs35.promises.writeFile(patchPath, patch, "utf8");
12677
13696
  written.push(patchPath);
12678
13697
  console.log(`dry-run: patch written to ${patchPath}`);
12679
- const gitignorePath = import_node_path52.default.join(opts.scanPath, ".gitignore");
12680
- 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);
12681
13700
  const verb = gitignoreExists ? "append" : "create";
12682
13701
  console.log(`dry-run: would ${verb} ${gitignorePath} (add neat-out/)`);
12683
13702
  console.log("rerun without --dry-run to register and snapshot.");
@@ -12688,9 +13707,9 @@ async function runInit(opts) {
12688
13707
  const graph = getGraph(graphKey);
12689
13708
  const projectPaths = pathsForProject(
12690
13709
  graphKey,
12691
- import_node_path52.default.join(opts.scanPath, "neat-out")
13710
+ import_node_path55.default.join(opts.scanPath, "neat-out")
12692
13711
  );
12693
- const errorsPath = import_node_path52.default.join(import_node_path52.default.dirname(opts.outPath), import_node_path52.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));
12694
13713
  const result = await extractFromDirectory(graph, opts.scanPath, { errorsPath });
12695
13714
  await saveGraphToDisk(graph, opts.outPath);
12696
13715
  written.push(opts.outPath);
@@ -12769,7 +13788,7 @@ async function runInit(opts) {
12769
13788
  console.log("Run `npm install` (or your language equivalent) to refresh lockfiles.");
12770
13789
  }
12771
13790
  } else {
12772
- await import_node_fs34.promises.writeFile(patchPath, patch, "utf8");
13791
+ await import_node_fs35.promises.writeFile(patchPath, patch, "utf8");
12773
13792
  written.push(patchPath);
12774
13793
  }
12775
13794
  }
@@ -12809,9 +13828,9 @@ var CLAUDE_SKILL_CONFIG = {
12809
13828
  };
12810
13829
  function claudeConfigPath() {
12811
13830
  const override = process.env.NEAT_CLAUDE_CONFIG;
12812
- if (override && override.length > 0) return import_node_path52.default.resolve(override);
13831
+ if (override && override.length > 0) return import_node_path55.default.resolve(override);
12813
13832
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
12814
- return import_node_path52.default.join(home, ".claude.json");
13833
+ return import_node_path55.default.join(home, ".claude.json");
12815
13834
  }
12816
13835
  async function runSkill(opts) {
12817
13836
  const snippet2 = JSON.stringify(CLAUDE_SKILL_CONFIG, null, 2) + "\n";
@@ -12823,7 +13842,7 @@ async function runSkill(opts) {
12823
13842
  const target = claudeConfigPath();
12824
13843
  let existing = {};
12825
13844
  try {
12826
- existing = JSON.parse(await import_node_fs34.promises.readFile(target, "utf8"));
13845
+ existing = JSON.parse(await import_node_fs35.promises.readFile(target, "utf8"));
12827
13846
  } catch (err) {
12828
13847
  if (err.code !== "ENOENT") {
12829
13848
  console.error(`neat skill: failed to read ${target} \u2014 ${err.message}`);
@@ -12835,8 +13854,8 @@ async function runSkill(opts) {
12835
13854
  ...existing,
12836
13855
  mcpServers: { ...mcp, neat: CLAUDE_SKILL_CONFIG.mcpServers.neat }
12837
13856
  };
12838
- await import_node_fs34.promises.mkdir(import_node_path52.default.dirname(target), { recursive: true });
12839
- 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");
12840
13859
  console.log(`neat skill: wrote mcpServers.neat to ${target}`);
12841
13860
  console.log("restart Claude Code to pick up the new MCP server.");
12842
13861
  return { exitCode: 0 };
@@ -12885,12 +13904,12 @@ async function main() {
12885
13904
  console.error("neat init: --apply and --dry-run are mutually exclusive");
12886
13905
  process.exit(2);
12887
13906
  }
12888
- const scanPath = import_node_path52.default.resolve(target);
13907
+ const scanPath = import_node_path55.default.resolve(target);
12889
13908
  const projectExplicit = parsed.project !== null;
12890
- const projectName = projectExplicit ? project : import_node_path52.default.basename(scanPath);
13909
+ const projectName = projectExplicit ? project : import_node_path55.default.basename(scanPath);
12891
13910
  const projectKey = projectExplicit ? project : DEFAULT_PROJECT;
12892
- const fallback = pathsForProject(projectKey, import_node_path52.default.join(scanPath, "neat-out")).snapshotPath;
12893
- const outPath = import_node_path52.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);
12894
13913
  const result = await runInit({
12895
13914
  scanPath,
12896
13915
  outPath,
@@ -12911,21 +13930,21 @@ async function main() {
12911
13930
  usage();
12912
13931
  process.exit(2);
12913
13932
  }
12914
- const scanPath = import_node_path52.default.resolve(target);
12915
- 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);
12916
13935
  if (!stat || !stat.isDirectory()) {
12917
13936
  console.error(`neat watch: ${scanPath} is not a directory`);
12918
13937
  process.exit(2);
12919
13938
  }
12920
- const projectPaths = pathsForProject(project, import_node_path52.default.join(scanPath, "neat-out"));
12921
- const outPath = import_node_path52.default.resolve(process.env.NEAT_OUT_PATH ?? projectPaths.snapshotPath);
12922
- const errorsPath = import_node_path52.default.resolve(
12923
- process.env.NEAT_ERRORS_PATH ?? import_node_path52.default.join(import_node_path52.default.dirname(outPath), import_node_path52.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))
12924
13943
  );
12925
- const staleEventsPath = import_node_path52.default.resolve(
12926
- process.env.NEAT_STALE_EVENTS_PATH ?? import_node_path52.default.join(import_node_path52.default.dirname(outPath), import_node_path52.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))
12927
13946
  );
12928
- const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ? import_node_path52.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;
12929
13948
  const handle = await startWatch(getGraph(project), {
12930
13949
  scanPath,
12931
13950
  outPath,
@@ -13108,11 +14127,11 @@ async function main() {
13108
14127
  process.exit(1);
13109
14128
  }
13110
14129
  async function tryOrchestrator(cmd, parsed) {
13111
- const scanPath = import_node_path52.default.resolve(cmd);
13112
- 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);
13113
14132
  if (!stat || !stat.isDirectory()) return null;
13114
14133
  const projectExplicit = parsed.project !== null;
13115
- const projectName = projectExplicit ? parsed.project : import_node_path52.default.basename(scanPath);
14134
+ const projectName = projectExplicit ? parsed.project : import_node_path55.default.basename(scanPath);
13116
14135
  const result = await runOrchestrator({
13117
14136
  scanPath,
13118
14137
  project: projectName,
@@ -13301,10 +14320,10 @@ async function runQueryVerb(cmd, parsed) {
13301
14320
  const parts = parsed.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
13302
14321
  const out = [];
13303
14322
  for (const p of parts) {
13304
- const r = import_types30.DivergenceTypeSchema.safeParse(p);
14323
+ const r = import_types33.DivergenceTypeSchema.safeParse(p);
13305
14324
  if (!r.success) {
13306
14325
  console.error(
13307
- `neat divergences: unknown --type "${p}". allowed: ${import_types30.DivergenceTypeSchema.options.join(", ")}`
14326
+ `neat divergences: unknown --type "${p}". allowed: ${import_types33.DivergenceTypeSchema.options.join(", ")}`
13308
14327
  );
13309
14328
  return 2;
13310
14329
  }