@neat.is/core 0.4.25 → 0.4.26-dev.20260703

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 path55 = (req.url.split("?")[0] ?? "").replace(/\/+$/, "");
62
62
  for (const suffix of suffixes) {
63
- if (path53 === suffix || path53.endsWith(suffix)) {
63
+ if (path55 === suffix || path55.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_path43.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
194
+ return import_node_path43.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_path43, 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_path43 = __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,13 @@ 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
+ }
339
348
  function parseOtlpRequest(body) {
340
349
  const out = [];
341
350
  for (const rs of body.resourceSpans ?? []) {
@@ -362,6 +371,10 @@ function parseOtlpRequest(body) {
362
371
  attributes: attrs,
363
372
  dbSystem: typeof attrs["db.system"] === "string" ? attrs["db.system"] : void 0,
364
373
  dbName: typeof attrs["db.name"] === "string" ? attrs["db.name"] : void 0,
374
+ messagingSystem: typeof attrs["messaging.system"] === "string" ? attrs["messaging.system"] : void 0,
375
+ messagingDestination: messagingDestinationOf(attrs),
376
+ graphqlOperationName: typeof attrs["graphql.operation.name"] === "string" && attrs["graphql.operation.name"].length > 0 ? attrs["graphql.operation.name"] : void 0,
377
+ graphqlOperationType: typeof attrs["graphql.operation.type"] === "string" && attrs["graphql.operation.type"].length > 0 ? attrs["graphql.operation.type"] : void 0,
365
378
  statusCode: span.status?.code,
366
379
  errorMessage: span.status?.message,
367
380
  exception: extractExceptionFromEvents(span.events)
@@ -373,10 +386,10 @@ function parseOtlpRequest(body) {
373
386
  return out;
374
387
  }
375
388
  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");
389
+ const here = import_node_path44.default.dirname((0, import_node_url3.fileURLToPath)(importMetaUrl));
390
+ const protoRoot = import_node_path44.default.resolve(here, "..", "proto");
378
391
  const root = new import_protobufjs.default.Root();
379
- root.resolvePath = (_origin, target) => import_node_path42.default.resolve(protoRoot, target);
392
+ root.resolvePath = (_origin, target) => import_node_path44.default.resolve(protoRoot, target);
380
393
  root.loadSync(
381
394
  "opentelemetry/proto/collector/trace/v1/trace_service.proto",
382
395
  { keepCase: true }
@@ -421,7 +434,21 @@ async function buildOtelReceiver(opts) {
421
434
  logger: false,
422
435
  bodyLimit: opts.bodyLimit ?? 16 * 1024 * 1024
423
436
  });
424
- mountBearerAuth(app, { token: opts.authToken, trustProxy: opts.trustProxy });
437
+ const REJECT_WARN_INTERVAL_MS = 6e4;
438
+ let lastRejectWarnAt = 0;
439
+ const warnRejectedOtlp = () => {
440
+ const now = Date.now();
441
+ if (now - lastRejectWarnAt < REJECT_WARN_INTERVAL_MS) return;
442
+ lastRejectWarnAt = now;
443
+ console.warn(
444
+ "[neatd] rejecting OTLP spans on /v1/traces \u2014 missing or invalid bearer token (set NEAT_OTEL_TOKEN on the instrumented app)"
445
+ );
446
+ };
447
+ mountBearerAuth(app, {
448
+ token: opts.authToken,
449
+ trustProxy: opts.trustProxy,
450
+ onReject: warnRejectedOtlp
451
+ });
425
452
  const queue = [];
426
453
  let draining = false;
427
454
  let drainPromise = Promise.resolve();
@@ -590,12 +617,12 @@ async function listenSteppingOtlp(app, requestedPort, host) {
590
617
  }
591
618
  }
592
619
  }
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;
620
+ var import_node_path44, import_node_url3, import_fastify2, import_protobufjs, ENV_ATTR_CANONICAL, ENV_ATTR_COMPAT, ENV_FALLBACK, exportTraceServiceRequestType, exportTraceServiceResponseType, cachedProtobufResponseBody, OTLP_STEP_ATTEMPTS, OTLP_STEP_STRIDE;
594
621
  var init_otel = __esm({
595
622
  "src/otel.ts"() {
596
623
  "use strict";
597
624
  init_cjs_shims();
598
- import_node_path42 = __toESM(require("path"), 1);
625
+ import_node_path44 = __toESM(require("path"), 1);
599
626
  import_node_url3 = require("url");
600
627
  import_fastify2 = __toESM(require("fastify"), 1);
601
628
  import_protobufjs = __toESM(require("protobufjs"), 1);
@@ -632,7 +659,7 @@ __export(cli_exports, {
632
659
  });
633
660
  module.exports = __toCommonJS(cli_exports);
634
661
  init_cjs_shims();
635
- var import_node_path52 = __toESM(require("path"), 1);
662
+ var import_node_path54 = __toESM(require("path"), 1);
636
663
  var import_node_fs34 = require("fs");
637
664
 
638
665
  // src/banner.ts
@@ -1198,19 +1225,19 @@ function confidenceFromMix(edges, now = Date.now()) {
1198
1225
  function longestIncomingWalk(graph, start, maxDepth) {
1199
1226
  let best = { path: [start], edges: [] };
1200
1227
  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] };
1228
+ function step(node, path55, edges) {
1229
+ if (path55.length > best.path.length) {
1230
+ best = { path: [...path55], edges: [...edges] };
1204
1231
  }
1205
- if (path53.length - 1 >= maxDepth) return;
1232
+ if (path55.length - 1 >= maxDepth) return;
1206
1233
  const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
1207
1234
  for (const [srcId, edge] of incoming) {
1208
1235
  if (visited.has(srcId)) continue;
1209
1236
  visited.add(srcId);
1210
- path53.push(srcId);
1237
+ path55.push(srcId);
1211
1238
  edges.push(edge);
1212
- step(srcId, path53, edges);
1213
- path53.pop();
1239
+ step(srcId, path55, edges);
1240
+ path55.pop();
1214
1241
  edges.pop();
1215
1242
  visited.delete(srcId);
1216
1243
  }
@@ -1218,14 +1245,14 @@ function longestIncomingWalk(graph, start, maxDepth) {
1218
1245
  step(start, [start], []);
1219
1246
  return best;
1220
1247
  }
1221
- function databaseRootCauseShape(graph, origin, walk) {
1248
+ function databaseRootCauseShape(graph, origin, walk3) {
1222
1249
  const targetDb = origin;
1223
1250
  const candidatePairs = compatPairs().filter((p) => p.engine === targetDb.engine);
1224
1251
  if (candidatePairs.length === 0) return null;
1225
- for (const id of walk.path) {
1252
+ for (const id of walk3.path) {
1226
1253
  const owner = resolveOwningService(graph, id);
1227
1254
  if (!owner) continue;
1228
- const { id: serviceId3, svc } = owner;
1255
+ const { id: serviceId4, svc } = owner;
1229
1256
  const deps = svc.dependencies ?? {};
1230
1257
  for (const pair of candidatePairs) {
1231
1258
  const declared = deps[pair.driver];
@@ -1238,7 +1265,7 @@ function databaseRootCauseShape(graph, origin, walk) {
1238
1265
  );
1239
1266
  if (!result.compatible) {
1240
1267
  return {
1241
- rootCauseNode: serviceId3,
1268
+ rootCauseNode: serviceId4,
1242
1269
  rootCauseReason: result.reason ?? "incompatible driver",
1243
1270
  ...result.minDriverVersion ? {
1244
1271
  fixRecommendation: `Upgrade ${svc.name} ${pair.driver} driver to >= ${result.minDriverVersion}`
@@ -1249,11 +1276,11 @@ function databaseRootCauseShape(graph, origin, walk) {
1249
1276
  }
1250
1277
  return null;
1251
1278
  }
1252
- function serviceRootCauseShape(graph, _origin, walk) {
1253
- for (const id of walk.path) {
1279
+ function serviceRootCauseShape(graph, _origin, walk3) {
1280
+ for (const id of walk3.path) {
1254
1281
  const owner = resolveOwningService(graph, id);
1255
1282
  if (!owner) continue;
1256
- const { id: serviceId3, svc } = owner;
1283
+ const { id: serviceId4, svc } = owner;
1257
1284
  const deps = svc.dependencies ?? {};
1258
1285
  const serviceNodeEngine = svc.nodeEngine;
1259
1286
  for (const constraint of nodeEngineConstraints()) {
@@ -1262,7 +1289,7 @@ function serviceRootCauseShape(graph, _origin, walk) {
1262
1289
  const result = checkNodeEngineConstraint(constraint, declared, serviceNodeEngine);
1263
1290
  if (!result.compatible && result.reason) {
1264
1291
  return {
1265
- rootCauseNode: serviceId3,
1292
+ rootCauseNode: serviceId4,
1266
1293
  rootCauseReason: result.reason,
1267
1294
  ...result.requiredNodeVersion ? {
1268
1295
  fixRecommendation: `Bump ${svc.name}'s engines.node to >= ${result.requiredNodeVersion}`
@@ -1277,7 +1304,7 @@ function serviceRootCauseShape(graph, _origin, walk) {
1277
1304
  const result = checkPackageConflict(conflict, declared, requiredDeclared);
1278
1305
  if (!result.compatible && result.reason) {
1279
1306
  return {
1280
- rootCauseNode: serviceId3,
1307
+ rootCauseNode: serviceId4,
1281
1308
  rootCauseReason: result.reason,
1282
1309
  fixRecommendation: `Upgrade ${svc.name}'s ${conflict.requires.name} to >= ${conflict.requires.minVersion}`
1283
1310
  };
@@ -1286,10 +1313,10 @@ function serviceRootCauseShape(graph, _origin, walk) {
1286
1313
  }
1287
1314
  return null;
1288
1315
  }
1289
- function fileRootCauseShape(graph, origin, walk) {
1316
+ function fileRootCauseShape(graph, origin, walk3) {
1290
1317
  const owner = resolveOwningService(graph, origin.id);
1291
1318
  if (!owner) return null;
1292
- return serviceRootCauseShape(graph, owner.svc, walk);
1319
+ return serviceRootCauseShape(graph, owner.svc, walk3);
1293
1320
  }
1294
1321
  var rootCauseShapes = {
1295
1322
  [import_types.NodeType.DatabaseNode]: databaseRootCauseShape,
@@ -1301,16 +1328,16 @@ function getRootCause(graph, errorNodeId, errorEvent, incidents) {
1301
1328
  const origin = graph.getNodeAttributes(errorNodeId);
1302
1329
  const shape = rootCauseShapes[origin.type];
1303
1330
  if (shape) {
1304
- const walk = longestIncomingWalk(graph, errorNodeId, ROOT_CAUSE_MAX_DEPTH);
1305
- const match = shape(graph, origin, walk);
1331
+ const walk3 = longestIncomingWalk(graph, errorNodeId, ROOT_CAUSE_MAX_DEPTH);
1332
+ const match = shape(graph, origin, walk3);
1306
1333
  if (match) {
1307
1334
  const reason = errorEvent ? `${match.rootCauseReason} (observed error: ${errorEvent.errorMessage})` : match.rootCauseReason;
1308
1335
  return import_types.RootCauseResultSchema.parse({
1309
1336
  rootCauseNode: match.rootCauseNode,
1310
1337
  rootCauseReason: reason,
1311
- traversalPath: walk.path,
1312
- edgeProvenances: walk.edges.map((e) => e.provenance),
1313
- confidence: confidenceFromMix(walk.edges),
1338
+ traversalPath: walk3.path,
1339
+ edgeProvenances: walk3.edges.map((e) => e.provenance),
1340
+ confidence: confidenceFromMix(walk3.edges),
1314
1341
  fixRecommendation: match.fixRecommendation
1315
1342
  });
1316
1343
  }
@@ -1368,9 +1395,9 @@ function rootCauseFromIncidents(nodeId, incidents, errorEvent) {
1368
1395
  function isFailingCallEdge(e) {
1369
1396
  return e.type === import_types.EdgeType.CALLS && (e.signal?.errorCount ?? 0) > 0;
1370
1397
  }
1371
- function callSourcesForService(graph, serviceId3) {
1372
- const ids = [serviceId3];
1373
- for (const edgeId of graph.outboundEdges(serviceId3)) {
1398
+ function callSourcesForService(graph, serviceId4) {
1399
+ const ids = [serviceId4];
1400
+ for (const edgeId of graph.outboundEdges(serviceId4)) {
1374
1401
  const e = graph.getEdgeAttributes(edgeId);
1375
1402
  if (e.type !== import_types.EdgeType.CONTAINS) continue;
1376
1403
  const tgt = graph.getNodeAttributes(e.target);
@@ -1387,9 +1414,9 @@ function failingCallDominates(e, id, curEdge, curId) {
1387
1414
  }
1388
1415
  return id < curId;
1389
1416
  }
1390
- function dominantFailingCall(graph, serviceId3, visited) {
1417
+ function dominantFailingCall(graph, serviceId4, visited) {
1391
1418
  let best = null;
1392
- for (const src of callSourcesForService(graph, serviceId3)) {
1419
+ for (const src of callSourcesForService(graph, serviceId4)) {
1393
1420
  for (const edgeId of graph.outboundEdges(src)) {
1394
1421
  const e = graph.getEdgeAttributes(edgeId);
1395
1422
  if (!isFailingCallEdge(e)) continue;
@@ -1404,26 +1431,26 @@ function dominantFailingCall(graph, serviceId3, visited) {
1404
1431
  return best;
1405
1432
  }
1406
1433
  function followFailingCallChain(graph, originServiceId, maxDepth) {
1407
- const path53 = [originServiceId];
1434
+ const path55 = [originServiceId];
1408
1435
  const edges = [];
1409
1436
  const visited = /* @__PURE__ */ new Set([originServiceId]);
1410
1437
  let current = originServiceId;
1411
1438
  for (let depth = 0; depth < maxDepth; depth++) {
1412
1439
  const hop = dominantFailingCall(graph, current, visited);
1413
1440
  if (!hop) break;
1414
- path53.push(hop.nextService);
1441
+ path55.push(hop.nextService);
1415
1442
  edges.push(hop.edge);
1416
1443
  visited.add(hop.nextService);
1417
1444
  current = hop.nextService;
1418
1445
  }
1419
1446
  if (edges.length === 0) return null;
1420
- return { path: path53, edges, culprit: current };
1447
+ return { path: path55, edges, culprit: current };
1421
1448
  }
1422
1449
  function crossServiceRootCause(graph, originId, incidents, errorEvent) {
1423
1450
  const chain = followFailingCallChain(graph, originId, ROOT_CAUSE_MAX_DEPTH);
1424
1451
  if (!chain) return null;
1425
1452
  const culprit = chain.culprit;
1426
- const path53 = [...chain.path];
1453
+ const path55 = [...chain.path];
1427
1454
  const edgeProvenances = chain.edges.map((e) => e.provenance);
1428
1455
  const baseConfidence = confidenceFromMix(chain.edges);
1429
1456
  const confidence = Math.max(0, Math.min(1, baseConfidence * INCIDENT_ROOT_CAUSE_CONFIDENCE));
@@ -1431,14 +1458,14 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
1431
1458
  if (loc) {
1432
1459
  let rootCauseNode = culprit;
1433
1460
  if (loc.fileNode) {
1434
- path53.push(loc.fileNode);
1461
+ path55.push(loc.fileNode);
1435
1462
  edgeProvenances.push(import_types.Provenance.OBSERVED);
1436
1463
  rootCauseNode = loc.fileNode;
1437
1464
  }
1438
1465
  return import_types.RootCauseResultSchema.parse({
1439
1466
  rootCauseNode,
1440
1467
  rootCauseReason: loc.rootCauseReason,
1441
- traversalPath: path53,
1468
+ traversalPath: path55,
1442
1469
  edgeProvenances,
1443
1470
  confidence,
1444
1471
  ...loc.fixRecommendation ? { fixRecommendation: loc.fixRecommendation } : {}
@@ -1450,7 +1477,7 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
1450
1477
  return import_types.RootCauseResultSchema.parse({
1451
1478
  rootCauseNode: culprit,
1452
1479
  rootCauseReason: `${culpritName} is failing downstream calls (${errs} observed error${errs === 1 ? "" : "s"})`,
1453
- traversalPath: path53,
1480
+ traversalPath: path55,
1454
1481
  edgeProvenances,
1455
1482
  confidence,
1456
1483
  fixRecommendation: `Inspect ${culpritName}'s failing handler`
@@ -2374,10 +2401,48 @@ var STITCH_EDGE_TYPES = /* @__PURE__ */ new Set([
2374
2401
  ]);
2375
2402
  var WIRE_SPAN_KIND_CLIENT = 3;
2376
2403
  var WIRE_SPAN_KIND_PRODUCER = 4;
2404
+ var WIRE_SPAN_KIND_CONSUMER = 5;
2377
2405
  function spanMintsObservedEdge(kind) {
2378
2406
  if (kind === void 0 || kind === 0) return true;
2379
2407
  return kind === WIRE_SPAN_KIND_CLIENT || kind === WIRE_SPAN_KIND_PRODUCER;
2380
2408
  }
2409
+ function spanMintsMessagingEdge(kind) {
2410
+ return kind === WIRE_SPAN_KIND_PRODUCER || kind === WIRE_SPAN_KIND_CONSUMER;
2411
+ }
2412
+ function spanServesGraphqlOperation(kind) {
2413
+ return kind !== WIRE_SPAN_KIND_CLIENT && kind !== WIRE_SPAN_KIND_PRODUCER && kind !== WIRE_SPAN_KIND_CONSUMER;
2414
+ }
2415
+ function ensureGraphqlOperationNode(graph, serviceName, operationType, operationName) {
2416
+ const id = (0, import_types3.graphqlOperationId)(serviceName, operationType, operationName);
2417
+ if (graph.hasNode(id)) return id;
2418
+ const node = {
2419
+ id,
2420
+ type: import_types3.NodeType.GraphQLOperationNode,
2421
+ name: operationName,
2422
+ service: serviceName,
2423
+ operationType: operationType.toLowerCase(),
2424
+ operationName,
2425
+ discoveredVia: "otel"
2426
+ };
2427
+ graph.addNode(id, node);
2428
+ return id;
2429
+ }
2430
+ function messagingDestinationKind(system) {
2431
+ return `${system}-topic`;
2432
+ }
2433
+ function ensureMessagingDestinationNode(graph, system, destination) {
2434
+ const id = (0, import_types3.infraId)(messagingDestinationKind(system), destination);
2435
+ if (graph.hasNode(id)) return id;
2436
+ const node = {
2437
+ id,
2438
+ type: import_types3.NodeType.InfraNode,
2439
+ name: destination,
2440
+ provider: "self",
2441
+ kind: messagingDestinationKind(system)
2442
+ };
2443
+ graph.addNode(id, node);
2444
+ return id;
2445
+ }
2381
2446
  var PARENT_SPAN_CACHE_SIZE = 1e4;
2382
2447
  var PARENT_SPAN_CACHE_TTL_MS = 5 * 60 * 1e3;
2383
2448
  var parentSpanCache = /* @__PURE__ */ new Map();
@@ -2471,6 +2536,21 @@ function ensureDatabaseNode(graph, host, engine) {
2471
2536
  graph.addNode(id, node);
2472
2537
  return id;
2473
2538
  }
2539
+ function ensureLocalDatabaseNode(graph, serviceName, name, engine) {
2540
+ const id = (0, import_types3.localDatabaseId)(serviceName, name);
2541
+ if (graph.hasNode(id)) return id;
2542
+ const node = {
2543
+ id,
2544
+ type: import_types3.NodeType.DatabaseNode,
2545
+ name,
2546
+ engine,
2547
+ engineVersion: "unknown",
2548
+ compatibleDrivers: [],
2549
+ discoveredVia: "otel"
2550
+ };
2551
+ graph.addNode(id, node);
2552
+ return id;
2553
+ }
2474
2554
  function ensureFrontierNode(graph, host, ts) {
2475
2555
  const id = frontierIdFor(host);
2476
2556
  if (graph.hasNode(id)) {
@@ -2722,10 +2802,21 @@ async function handleSpan(ctx, span) {
2722
2802
  let affectedNode = sourceId;
2723
2803
  const mintsFromCallerSide = spanMintsObservedEdge(span.kind);
2724
2804
  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);
2805
+ if (mintsFromCallerSide) {
2806
+ const host = pickAddress(span);
2807
+ let targetId;
2808
+ if (host) {
2809
+ ensureDatabaseNode(ctx.graph, host, span.dbSystem);
2810
+ targetId = (0, import_types3.databaseId)(host);
2811
+ } else {
2812
+ const localName = span.dbName ?? span.dbSystem;
2813
+ targetId = ensureLocalDatabaseNode(
2814
+ ctx.graph,
2815
+ span.service,
2816
+ localName,
2817
+ span.dbSystem
2818
+ );
2819
+ }
2729
2820
  const result = upsertObservedEdge(
2730
2821
  ctx.graph,
2731
2822
  import_types3.EdgeType.CONNECTS_TO,
@@ -2737,6 +2828,40 @@ async function handleSpan(ctx, span) {
2737
2828
  );
2738
2829
  if (result) affectedNode = targetId;
2739
2830
  }
2831
+ } else if (span.messagingSystem && span.messagingDestination && spanMintsMessagingEdge(span.kind)) {
2832
+ const targetId = ensureMessagingDestinationNode(
2833
+ ctx.graph,
2834
+ span.messagingSystem,
2835
+ span.messagingDestination
2836
+ );
2837
+ const edgeType = span.kind === WIRE_SPAN_KIND_CONSUMER ? import_types3.EdgeType.CONSUMES_FROM : import_types3.EdgeType.PUBLISHES_TO;
2838
+ const result = upsertObservedEdge(
2839
+ ctx.graph,
2840
+ edgeType,
2841
+ observedSource(),
2842
+ targetId,
2843
+ ts,
2844
+ isError,
2845
+ callSiteEvidence
2846
+ );
2847
+ if (result) affectedNode = targetId;
2848
+ } else if (span.graphqlOperationName && span.graphqlOperationType && spanServesGraphqlOperation(span.kind)) {
2849
+ const targetId = ensureGraphqlOperationNode(
2850
+ ctx.graph,
2851
+ span.service,
2852
+ span.graphqlOperationType,
2853
+ span.graphqlOperationName
2854
+ );
2855
+ const result = upsertObservedEdge(
2856
+ ctx.graph,
2857
+ import_types3.EdgeType.CONTAINS,
2858
+ observedSource(),
2859
+ targetId,
2860
+ ts,
2861
+ isError,
2862
+ callSiteEvidence
2863
+ );
2864
+ if (result) affectedNode = targetId;
2740
2865
  } else {
2741
2866
  const host = pickAddress(span);
2742
2867
  let resolvedViaAddress = false;
@@ -2846,29 +2971,29 @@ function promoteFrontierNodes(graph, opts = {}) {
2846
2971
  toPromote.push({ frontierId: id, serviceId: target });
2847
2972
  });
2848
2973
  let promoted = 0;
2849
- for (const { frontierId: frontierId2, serviceId: serviceId3 } of toPromote) {
2974
+ for (const { frontierId: frontierId2, serviceId: serviceId4 } of toPromote) {
2850
2975
  if (opts.policies && opts.policies.length > 0 && opts.policyCtx) {
2851
2976
  const gate = canPromoteFrontier(graph, frontierId2, opts.policies, opts.policyCtx);
2852
2977
  if (!gate.allowed) {
2853
2978
  continue;
2854
2979
  }
2855
2980
  }
2856
- rewireFrontierEdges(graph, frontierId2, serviceId3);
2981
+ rewireFrontierEdges(graph, frontierId2, serviceId4);
2857
2982
  graph.dropNode(frontierId2);
2858
2983
  promoted++;
2859
2984
  }
2860
2985
  return promoted;
2861
2986
  }
2862
- function rewireFrontierEdges(graph, frontierId2, serviceId3) {
2987
+ function rewireFrontierEdges(graph, frontierId2, serviceId4) {
2863
2988
  const inbound = [...graph.inboundEdges(frontierId2)];
2864
2989
  const outbound = [...graph.outboundEdges(frontierId2)];
2865
2990
  for (const edgeId of inbound) {
2866
2991
  const edge = graph.getEdgeAttributes(edgeId);
2867
- rebuildEdge(graph, edge, edge.source, serviceId3, edgeId);
2992
+ rebuildEdge(graph, edge, edge.source, serviceId4, edgeId);
2868
2993
  }
2869
2994
  for (const edgeId of outbound) {
2870
2995
  const edge = graph.getEdgeAttributes(edgeId);
2871
- rebuildEdge(graph, edge, serviceId3, edge.target, edgeId);
2996
+ rebuildEdge(graph, edge, serviceId4, edge.target, edgeId);
2872
2997
  }
2873
2998
  }
2874
2999
  function rebuildEdge(graph, edge, newSource, newTarget, oldEdgeId) {
@@ -3642,9 +3767,9 @@ var K8S_KINDS_WITH_HOSTNAMES = /* @__PURE__ */ new Set([
3642
3767
  "StatefulSet",
3643
3768
  "DaemonSet"
3644
3769
  ]);
3645
- function addAliases(graph, serviceId3, candidates) {
3646
- if (!graph.hasNode(serviceId3)) return;
3647
- const node = graph.getNodeAttributes(serviceId3);
3770
+ function addAliases(graph, serviceId4, candidates) {
3771
+ if (!graph.hasNode(serviceId4)) return;
3772
+ const node = graph.getNodeAttributes(serviceId4);
3648
3773
  if (node.type !== import_types6.NodeType.ServiceNode) return;
3649
3774
  const set = new Set(node.aliases ?? []);
3650
3775
  for (const c of candidates) {
@@ -3654,7 +3779,7 @@ function addAliases(graph, serviceId3, candidates) {
3654
3779
  }
3655
3780
  if (set.size === 0) return;
3656
3781
  const updated = { ...node, aliases: [...set].sort() };
3657
- graph.replaceNodeAttributes(serviceId3, updated);
3782
+ graph.replaceNodeAttributes(serviceId4, updated);
3658
3783
  }
3659
3784
  function indexServicesByName(services) {
3660
3785
  const map = /* @__PURE__ */ new Map();
@@ -3687,12 +3812,12 @@ async function collectComposeAliases(graph, scanPath, serviceIndex) {
3687
3812
  }
3688
3813
  if (!compose?.services) return;
3689
3814
  for (const [composeName, svc] of Object.entries(compose.services)) {
3690
- const serviceId3 = serviceIndex.get(composeName);
3691
- if (!serviceId3) continue;
3815
+ const serviceId4 = serviceIndex.get(composeName);
3816
+ if (!serviceId4) continue;
3692
3817
  const aliases = /* @__PURE__ */ new Set([composeName]);
3693
3818
  if (svc.container_name) aliases.add(svc.container_name);
3694
3819
  if (svc.hostname) aliases.add(svc.hostname);
3695
- addAliases(graph, serviceId3, aliases);
3820
+ addAliases(graph, serviceId4, aliases);
3696
3821
  }
3697
3822
  }
3698
3823
  var LABEL_KEYS = /* @__PURE__ */ new Set([
@@ -3805,16 +3930,28 @@ init_cjs_shims();
3805
3930
  var import_node_fs11 = require("fs");
3806
3931
  var import_node_path11 = __toESM(require("path"), 1);
3807
3932
  var import_types7 = require("@neat.is/types");
3933
+ function buildServiceHostIndex(services) {
3934
+ const knownHosts = /* @__PURE__ */ new Set();
3935
+ const hostToNodeId = /* @__PURE__ */ new Map();
3936
+ for (const service of services) {
3937
+ const base = import_node_path11.default.basename(service.dir);
3938
+ knownHosts.add(base);
3939
+ knownHosts.add(service.pkg.name);
3940
+ hostToNodeId.set(base, service.node.id);
3941
+ hostToNodeId.set(service.pkg.name, service.node.id);
3942
+ }
3943
+ return { knownHosts, hostToNodeId };
3944
+ }
3808
3945
  async function walkSourceFiles(dir) {
3809
3946
  const out = [];
3810
- async function walk(current) {
3947
+ async function walk3(current) {
3811
3948
  const entries = await import_node_fs11.promises.readdir(current, { withFileTypes: true }).catch(() => []);
3812
3949
  for (const entry2 of entries) {
3813
3950
  const full = import_node_path11.default.join(current, entry2.name);
3814
3951
  if (entry2.isDirectory()) {
3815
3952
  if (IGNORED_DIRS.has(entry2.name)) continue;
3816
3953
  if (await isPythonVenvDir(full)) continue;
3817
- await walk(full);
3954
+ await walk3(full);
3818
3955
  } 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
3956
  // would attribute our instrumentation imports to the user's service.
3820
3957
  !isNeatAuthoredSourceFile(entry2.name)) {
@@ -3822,7 +3959,7 @@ async function walkSourceFiles(dir) {
3822
3959
  }
3823
3960
  }
3824
3961
  }
3825
- await walk(dir);
3962
+ await walk3(dir);
3826
3963
  return out;
3827
3964
  }
3828
3965
  async function loadSourceFiles(dir) {
@@ -4916,20 +5053,20 @@ var import_node_path22 = __toESM(require("path"), 1);
4916
5053
  var import_types10 = require("@neat.is/types");
4917
5054
  async function walkConfigFiles(dir) {
4918
5055
  const out = [];
4919
- async function walk(current) {
5056
+ async function walk3(current) {
4920
5057
  const entries = await import_node_fs15.promises.readdir(current, { withFileTypes: true });
4921
5058
  for (const entry2 of entries) {
4922
5059
  const full = import_node_path22.default.join(current, entry2.name);
4923
5060
  if (entry2.isDirectory()) {
4924
5061
  if (IGNORED_DIRS.has(entry2.name)) continue;
4925
5062
  if (await isPythonVenvDir(full)) continue;
4926
- await walk(full);
5063
+ await walk3(full);
4927
5064
  } else if (entry2.isFile() && isConfigFile(entry2.name).match) {
4928
5065
  out.push(full);
4929
5066
  }
4930
5067
  }
4931
5068
  }
4932
- await walk(dir);
5069
+ await walk3(dir);
4933
5070
  return out;
4934
5071
  }
4935
5072
  async function addConfigNodes(graph, services, scanPath) {
@@ -4977,17 +5114,347 @@ async function addConfigNodes(graph, services, scanPath) {
4977
5114
  return { nodesAdded, edgesAdded };
4978
5115
  }
4979
5116
 
5117
+ // src/extract/routes.ts
5118
+ init_cjs_shims();
5119
+ var import_node_path23 = __toESM(require("path"), 1);
5120
+ var import_tree_sitter2 = __toESM(require("tree-sitter"), 1);
5121
+ var import_tree_sitter_javascript2 = __toESM(require("tree-sitter-javascript"), 1);
5122
+ var import_types11 = require("@neat.is/types");
5123
+ var PARSE_CHUNK2 = 16384;
5124
+ function parseSource2(parser, source) {
5125
+ return parser.parse(
5126
+ (index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK2)
5127
+ );
5128
+ }
5129
+ function makeJsParser2() {
5130
+ const p = new import_tree_sitter2.default();
5131
+ p.setLanguage(import_tree_sitter_javascript2.default);
5132
+ return p;
5133
+ }
5134
+ var ROUTER_METHODS = /* @__PURE__ */ new Set([
5135
+ "get",
5136
+ "post",
5137
+ "put",
5138
+ "patch",
5139
+ "delete",
5140
+ "options",
5141
+ "head",
5142
+ "all"
5143
+ ]);
5144
+ var NEXT_APP_METHODS = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]);
5145
+ var JS_ROUTE_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx"]);
5146
+ function canonicalizeTemplate(raw) {
5147
+ let p = raw.split("?")[0].split("#")[0];
5148
+ if (!p.startsWith("/")) p = "/" + p;
5149
+ if (p.length > 1 && p.endsWith("/")) p = p.slice(0, -1);
5150
+ return p;
5151
+ }
5152
+ function isDynamicSegment(seg) {
5153
+ if (seg.length === 0) return false;
5154
+ if (seg.includes(":")) return true;
5155
+ if (seg.startsWith("{") || seg.startsWith("[")) return true;
5156
+ if (/^\d+$/.test(seg)) return true;
5157
+ 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;
5158
+ if (/^[0-9a-f]{24,}$/i.test(seg)) return true;
5159
+ return false;
5160
+ }
5161
+ function normalizePathTemplate(raw) {
5162
+ const canonical = canonicalizeTemplate(raw);
5163
+ const segments = canonical.split("/").filter((s) => s.length > 0);
5164
+ const normalised = segments.map((seg) => isDynamicSegment(seg) ? ":param" : seg.toLowerCase());
5165
+ return "/" + normalised.join("/");
5166
+ }
5167
+ function walk(node, visit) {
5168
+ visit(node);
5169
+ for (let i = 0; i < node.namedChildCount; i++) {
5170
+ const child = node.namedChild(i);
5171
+ if (child) walk(child, visit);
5172
+ }
5173
+ }
5174
+ function staticStringText(node) {
5175
+ if (node.type === "string") {
5176
+ for (let i = 0; i < node.namedChildCount; i++) {
5177
+ const child = node.namedChild(i);
5178
+ if (child?.type === "string_fragment") return child.text;
5179
+ }
5180
+ return "";
5181
+ }
5182
+ if (node.type === "template_string") {
5183
+ for (let i = 0; i < node.namedChildCount; i++) {
5184
+ if (node.namedChild(i)?.type === "template_substitution") return null;
5185
+ }
5186
+ const raw = node.text;
5187
+ return raw.length >= 2 ? raw.slice(1, -1) : "";
5188
+ }
5189
+ return null;
5190
+ }
5191
+ function objectStringProp(objNode, key) {
5192
+ for (let i = 0; i < objNode.namedChildCount; i++) {
5193
+ const pair = objNode.namedChild(i);
5194
+ if (!pair || pair.type !== "pair") continue;
5195
+ const k = pair.childForFieldName("key");
5196
+ if (!k) continue;
5197
+ const kText = k.type === "string" ? staticStringText(k) : k.text;
5198
+ if (kText !== key) continue;
5199
+ const v = pair.childForFieldName("value");
5200
+ if (v) return staticStringText(v);
5201
+ }
5202
+ return null;
5203
+ }
5204
+ function fastifyRouteMethods(objNode) {
5205
+ for (let i = 0; i < objNode.namedChildCount; i++) {
5206
+ const pair = objNode.namedChild(i);
5207
+ if (!pair || pair.type !== "pair") continue;
5208
+ const k = pair.childForFieldName("key");
5209
+ const kText = k ? k.type === "string" ? staticStringText(k) : k.text : null;
5210
+ if (kText !== "method") continue;
5211
+ const v = pair.childForFieldName("value");
5212
+ if (!v) return [];
5213
+ if (v.type === "string" || v.type === "template_string") {
5214
+ const s = staticStringText(v);
5215
+ return s ? [s.toUpperCase()] : [];
5216
+ }
5217
+ if (v.type === "array") {
5218
+ const out = [];
5219
+ for (let j = 0; j < v.namedChildCount; j++) {
5220
+ const el = v.namedChild(j);
5221
+ if (el && (el.type === "string" || el.type === "template_string")) {
5222
+ const s = staticStringText(el);
5223
+ if (s) out.push(s.toUpperCase());
5224
+ }
5225
+ }
5226
+ return out;
5227
+ }
5228
+ }
5229
+ return [];
5230
+ }
5231
+ function serverRoutesFromSource(source, parser, hasExpress, hasFastify) {
5232
+ const tree = parseSource2(parser, source);
5233
+ const out = [];
5234
+ const framework = hasExpress ? "express" : "fastify";
5235
+ walk(tree.rootNode, (node) => {
5236
+ if (node.type !== "call_expression") return;
5237
+ const fn = node.childForFieldName("function");
5238
+ if (!fn || fn.type !== "member_expression") return;
5239
+ const prop = fn.childForFieldName("property");
5240
+ if (!prop) return;
5241
+ const method = prop.text.toLowerCase();
5242
+ const args = node.childForFieldName("arguments");
5243
+ const first = args?.namedChild(0);
5244
+ if (!first) return;
5245
+ const line = node.startPosition.row + 1;
5246
+ if (ROUTER_METHODS.has(method)) {
5247
+ const p = staticStringText(first);
5248
+ if (p && p.startsWith("/")) {
5249
+ out.push({
5250
+ method: method === "all" ? "ALL" : method.toUpperCase(),
5251
+ pathTemplate: canonicalizeTemplate(p),
5252
+ line,
5253
+ framework
5254
+ });
5255
+ }
5256
+ return;
5257
+ }
5258
+ if (method === "route" && hasFastify && first.type === "object") {
5259
+ const url = objectStringProp(first, "url");
5260
+ if (!url || !url.startsWith("/")) return;
5261
+ const methods = fastifyRouteMethods(first);
5262
+ const list = methods.length > 0 ? methods : ["ALL"];
5263
+ for (const m of list) {
5264
+ out.push({
5265
+ method: m === "ALL" ? "ALL" : m.toUpperCase(),
5266
+ pathTemplate: canonicalizeTemplate(url),
5267
+ line,
5268
+ framework: "fastify"
5269
+ });
5270
+ }
5271
+ }
5272
+ });
5273
+ return out;
5274
+ }
5275
+ function segmentsOf(relFile) {
5276
+ return toPosix2(relFile).split("/").filter((s) => s.length > 0);
5277
+ }
5278
+ function isNextAppRouteFile(relFile) {
5279
+ const segs = segmentsOf(relFile);
5280
+ if (!segs.includes("app")) return false;
5281
+ const base = segs[segs.length - 1] ?? "";
5282
+ return /^route\.(?:js|jsx|mjs|cjs|ts|tsx)$/.test(base);
5283
+ }
5284
+ function isNextPagesApiFile(relFile) {
5285
+ const segs = segmentsOf(relFile);
5286
+ const pagesIdx = segs.indexOf("pages");
5287
+ if (pagesIdx === -1 || segs[pagesIdx + 1] !== "api") return false;
5288
+ const base = segs[segs.length - 1] ?? "";
5289
+ if (/^_(app|document|middleware)\./.test(base)) return false;
5290
+ return JS_ROUTE_EXTENSIONS.has(import_node_path23.default.extname(base));
5291
+ }
5292
+ function nextSegment(seg) {
5293
+ if (seg.startsWith("(") && seg.endsWith(")")) return null;
5294
+ const catchAll = seg.match(/^\[\[?\.\.\.(.+?)\]?\]$/);
5295
+ if (catchAll) return ":" + catchAll[1];
5296
+ const dynamic = seg.match(/^\[(.+?)\]$/);
5297
+ if (dynamic) return ":" + dynamic[1];
5298
+ return seg;
5299
+ }
5300
+ function nextAppPathTemplate(relFile) {
5301
+ const segs = segmentsOf(relFile);
5302
+ const appIdx = segs.lastIndexOf("app");
5303
+ const between = segs.slice(appIdx + 1, segs.length - 1);
5304
+ const parts = [];
5305
+ for (const seg of between) {
5306
+ const mapped = nextSegment(seg);
5307
+ if (mapped !== null) parts.push(mapped);
5308
+ }
5309
+ return "/" + parts.join("/");
5310
+ }
5311
+ function nextPagesApiPathTemplate(relFile) {
5312
+ const segs = segmentsOf(relFile);
5313
+ const pagesIdx = segs.indexOf("pages");
5314
+ const rest = segs.slice(pagesIdx + 1);
5315
+ const parts = [];
5316
+ for (let i = 0; i < rest.length; i++) {
5317
+ let seg = rest[i];
5318
+ if (i === rest.length - 1) {
5319
+ seg = seg.replace(/\.(?:js|jsx|mjs|cjs|ts|tsx)$/, "");
5320
+ if (seg === "index") continue;
5321
+ }
5322
+ const mapped = nextSegment(seg);
5323
+ if (mapped !== null) parts.push(mapped);
5324
+ }
5325
+ return "/" + parts.join("/");
5326
+ }
5327
+ function nextAppMethods(root) {
5328
+ const out = [];
5329
+ walk(root, (node) => {
5330
+ if (node.type !== "export_statement") return;
5331
+ const decl = node.childForFieldName("declaration");
5332
+ if (!decl) return;
5333
+ const line = node.startPosition.row + 1;
5334
+ if (decl.type === "function_declaration") {
5335
+ const name = decl.childForFieldName("name")?.text;
5336
+ if (name && NEXT_APP_METHODS.has(name)) out.push({ method: name, line });
5337
+ return;
5338
+ }
5339
+ if (decl.type === "lexical_declaration" || decl.type === "variable_declaration") {
5340
+ for (let i = 0; i < decl.namedChildCount; i++) {
5341
+ const d = decl.namedChild(i);
5342
+ if (d?.type !== "variable_declarator") continue;
5343
+ const name = d.childForFieldName("name")?.text;
5344
+ if (name && NEXT_APP_METHODS.has(name)) out.push({ method: name, line });
5345
+ }
5346
+ }
5347
+ });
5348
+ return out;
5349
+ }
5350
+ function nextRoutesFromFile(source, relFile, parser) {
5351
+ if (isNextAppRouteFile(relFile)) {
5352
+ const tree = parseSource2(parser, source);
5353
+ const template = nextAppPathTemplate(relFile);
5354
+ return nextAppMethods(tree.rootNode).map(({ method, line }) => ({
5355
+ method,
5356
+ pathTemplate: canonicalizeTemplate(template),
5357
+ line,
5358
+ framework: "next"
5359
+ }));
5360
+ }
5361
+ if (isNextPagesApiFile(relFile)) {
5362
+ return [
5363
+ {
5364
+ method: "ALL",
5365
+ pathTemplate: canonicalizeTemplate(nextPagesApiPathTemplate(relFile)),
5366
+ line: 1,
5367
+ framework: "next"
5368
+ }
5369
+ ];
5370
+ }
5371
+ return [];
5372
+ }
5373
+ async function addRoutes(graph, services) {
5374
+ const jsParser = makeJsParser2();
5375
+ let nodesAdded = 0;
5376
+ let edgesAdded = 0;
5377
+ for (const service of services) {
5378
+ const deps = {
5379
+ ...service.pkg.dependencies ?? {},
5380
+ ...service.pkg.devDependencies ?? {}
5381
+ };
5382
+ const hasExpress = deps["express"] !== void 0;
5383
+ const hasFastify = deps["fastify"] !== void 0;
5384
+ const hasNext = deps["next"] !== void 0;
5385
+ if (!hasExpress && !hasFastify && !hasNext) continue;
5386
+ const files = await loadSourceFiles(service.dir);
5387
+ for (const file of files) {
5388
+ if (isTestPath(file.path)) continue;
5389
+ if (!JS_ROUTE_EXTENSIONS.has(import_node_path23.default.extname(file.path))) continue;
5390
+ const relFile = toPosix2(import_node_path23.default.relative(service.dir, file.path));
5391
+ let routes;
5392
+ try {
5393
+ if (hasNext && (isNextAppRouteFile(relFile) || isNextPagesApiFile(relFile))) {
5394
+ routes = nextRoutesFromFile(file.content, relFile, jsParser);
5395
+ } else if (hasExpress || hasFastify) {
5396
+ routes = serverRoutesFromSource(file.content, jsParser, hasExpress, hasFastify);
5397
+ } else {
5398
+ routes = [];
5399
+ }
5400
+ } catch (err) {
5401
+ recordExtractionError("route extraction", file.path, err);
5402
+ continue;
5403
+ }
5404
+ if (routes.length === 0) continue;
5405
+ for (const route of routes) {
5406
+ const rid = (0, import_types11.routeId)(service.pkg.name, route.method, route.pathTemplate);
5407
+ if (!graph.hasNode(rid)) {
5408
+ const node = {
5409
+ id: rid,
5410
+ type: import_types11.NodeType.RouteNode,
5411
+ name: `${route.method} ${route.pathTemplate}`,
5412
+ service: service.pkg.name,
5413
+ method: route.method,
5414
+ pathTemplate: route.pathTemplate,
5415
+ path: relFile,
5416
+ line: route.line,
5417
+ framework: route.framework,
5418
+ discoveredVia: "static"
5419
+ };
5420
+ graph.addNode(rid, node);
5421
+ nodesAdded++;
5422
+ }
5423
+ const containsId = (0, import_types11.extractedEdgeId)(service.node.id, rid, import_types11.EdgeType.CONTAINS);
5424
+ if (!graph.hasEdge(containsId)) {
5425
+ const edge = {
5426
+ id: containsId,
5427
+ source: service.node.id,
5428
+ target: rid,
5429
+ type: import_types11.EdgeType.CONTAINS,
5430
+ provenance: import_types11.Provenance.EXTRACTED,
5431
+ confidence: (0, import_types11.confidenceForExtracted)("structural"),
5432
+ evidence: {
5433
+ file: relFile,
5434
+ line: route.line,
5435
+ snippet: snippet(file.content, route.line)
5436
+ }
5437
+ };
5438
+ graph.addEdgeWithKey(containsId, service.node.id, rid, edge);
5439
+ edgesAdded++;
5440
+ }
5441
+ }
5442
+ }
5443
+ }
5444
+ return { nodesAdded, edgesAdded };
5445
+ }
5446
+
4980
5447
  // src/extract/calls/index.ts
4981
5448
  init_cjs_shims();
4982
- var import_types17 = require("@neat.is/types");
5449
+ var import_types19 = require("@neat.is/types");
4983
5450
 
4984
5451
  // src/extract/calls/http.ts
4985
5452
  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);
5453
+ var import_node_path24 = __toESM(require("path"), 1);
5454
+ var import_tree_sitter3 = __toESM(require("tree-sitter"), 1);
5455
+ var import_tree_sitter_javascript3 = __toESM(require("tree-sitter-javascript"), 1);
4989
5456
  var import_tree_sitter_python2 = __toESM(require("tree-sitter-python"), 1);
4990
- var import_types11 = require("@neat.is/types");
5457
+ var import_types12 = require("@neat.is/types");
4991
5458
  var STRING_LITERAL_NODE_TYPES = /* @__PURE__ */ new Set(["string_fragment", "string_content"]);
4992
5459
  var JSX_EXTERNAL_LINK_TAGS = /* @__PURE__ */ new Set(["a", "Link", "NavLink", "ExternalLink", "Anchor"]);
4993
5460
  function isInsideJsxExternalLink(node) {
@@ -5015,14 +5482,14 @@ function collectStringLiterals(node, out) {
5015
5482
  if (child) collectStringLiterals(child, out);
5016
5483
  }
5017
5484
  }
5018
- var PARSE_CHUNK2 = 16384;
5019
- function parseSource2(parser, source) {
5485
+ var PARSE_CHUNK3 = 16384;
5486
+ function parseSource3(parser, source) {
5020
5487
  return parser.parse(
5021
- (index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK2)
5488
+ (index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK3)
5022
5489
  );
5023
5490
  }
5024
5491
  function callsFromSource(source, parser, knownHosts) {
5025
- const tree = parseSource2(parser, source);
5492
+ const tree = parseSource3(parser, source);
5026
5493
  const literals = [];
5027
5494
  collectStringLiterals(tree.rootNode, literals);
5028
5495
  const out = [];
@@ -5036,27 +5503,20 @@ function callsFromSource(source, parser, knownHosts) {
5036
5503
  }
5037
5504
  return out;
5038
5505
  }
5039
- function makeJsParser2() {
5040
- const p = new import_tree_sitter2.default();
5041
- p.setLanguage(import_tree_sitter_javascript2.default);
5506
+ function makeJsParser3() {
5507
+ const p = new import_tree_sitter3.default();
5508
+ p.setLanguage(import_tree_sitter_javascript3.default);
5042
5509
  return p;
5043
5510
  }
5044
5511
  function makePyParser2() {
5045
- const p = new import_tree_sitter2.default();
5512
+ const p = new import_tree_sitter3.default();
5046
5513
  p.setLanguage(import_tree_sitter_python2.default);
5047
5514
  return p;
5048
5515
  }
5049
5516
  async function addHttpCallEdges(graph, services) {
5050
- const jsParser = makeJsParser2();
5517
+ const jsParser = makeJsParser3();
5051
5518
  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
- }
5519
+ const { knownHosts, hostToNodeId } = buildServiceHostIndex(services);
5060
5520
  let nodesAdded = 0;
5061
5521
  let edgesAdded = 0;
5062
5522
  for (const service of services) {
@@ -5064,7 +5524,7 @@ async function addHttpCallEdges(graph, services) {
5064
5524
  const seen = /* @__PURE__ */ new Set();
5065
5525
  for (const file of files) {
5066
5526
  if (isTestPath(file.path)) continue;
5067
- const parser = import_node_path23.default.extname(file.path) === ".py" ? pyParser : jsParser;
5527
+ const parser = import_node_path24.default.extname(file.path) === ".py" ? pyParser : jsParser;
5068
5528
  let sites;
5069
5529
  try {
5070
5530
  sites = callsFromSource(file.content, parser, knownHosts);
@@ -5073,14 +5533,14 @@ async function addHttpCallEdges(graph, services) {
5073
5533
  continue;
5074
5534
  }
5075
5535
  if (sites.length === 0) continue;
5076
- const relFile = toPosix2(import_node_path23.default.relative(service.dir, file.path));
5536
+ const relFile = toPosix2(import_node_path24.default.relative(service.dir, file.path));
5077
5537
  for (const site of sites) {
5078
5538
  const targetId = hostToNodeId.get(site.host);
5079
5539
  if (!targetId || targetId === service.node.id) continue;
5080
5540
  const dedupKey = `${relFile}|${targetId}`;
5081
5541
  if (seen.has(dedupKey)) continue;
5082
5542
  seen.add(dedupKey);
5083
- const confidence = (0, import_types11.confidenceForExtracted)("url-literal-service-target");
5543
+ const confidence = (0, import_types12.confidenceForExtracted)("url-literal-service-target");
5084
5544
  const ev = {
5085
5545
  file: relFile,
5086
5546
  line: site.line,
@@ -5094,25 +5554,25 @@ async function addHttpCallEdges(graph, services) {
5094
5554
  );
5095
5555
  nodesAdded += n;
5096
5556
  edgesAdded += e;
5097
- if (!(0, import_types11.passesExtractedFloor)(confidence)) {
5557
+ if (!(0, import_types12.passesExtractedFloor)(confidence)) {
5098
5558
  noteExtractedDropped({
5099
5559
  source: fileNodeId,
5100
5560
  target: targetId,
5101
- type: import_types11.EdgeType.CALLS,
5561
+ type: import_types12.EdgeType.CALLS,
5102
5562
  confidence,
5103
5563
  confidenceKind: "url-literal-service-target",
5104
5564
  evidence: ev
5105
5565
  });
5106
5566
  continue;
5107
5567
  }
5108
- const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, targetId, import_types11.EdgeType.CALLS);
5568
+ const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, targetId, import_types12.EdgeType.CALLS);
5109
5569
  if (!graph.hasEdge(edgeId)) {
5110
5570
  const edge = {
5111
5571
  id: edgeId,
5112
5572
  source: fileNodeId,
5113
5573
  target: targetId,
5114
- type: import_types11.EdgeType.CALLS,
5115
- provenance: import_types11.Provenance.EXTRACTED,
5574
+ type: import_types12.EdgeType.CALLS,
5575
+ provenance: import_types12.Provenance.EXTRACTED,
5116
5576
  confidence,
5117
5577
  evidence: ev
5118
5578
  };
@@ -5125,10 +5585,279 @@ async function addHttpCallEdges(graph, services) {
5125
5585
  return { nodesAdded, edgesAdded };
5126
5586
  }
5127
5587
 
5588
+ // src/extract/calls/route-match.ts
5589
+ init_cjs_shims();
5590
+ var import_node_path25 = __toESM(require("path"), 1);
5591
+ var import_tree_sitter4 = __toESM(require("tree-sitter"), 1);
5592
+ var import_tree_sitter_javascript4 = __toESM(require("tree-sitter-javascript"), 1);
5593
+ var import_types13 = require("@neat.is/types");
5594
+ var PARSE_CHUNK4 = 16384;
5595
+ function parseSource4(parser, source) {
5596
+ return parser.parse(
5597
+ (index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK4)
5598
+ );
5599
+ }
5600
+ function makeJsParser4() {
5601
+ const p = new import_tree_sitter4.default();
5602
+ p.setLanguage(import_tree_sitter_javascript4.default);
5603
+ return p;
5604
+ }
5605
+ var JS_CLIENT_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx"]);
5606
+ var AXIOS_METHODS = /* @__PURE__ */ new Set(["get", "post", "put", "patch", "delete", "head", "options", "request"]);
5607
+ function walk2(node, visit) {
5608
+ visit(node);
5609
+ for (let i = 0; i < node.namedChildCount; i++) {
5610
+ const child = node.namedChild(i);
5611
+ if (child) walk2(child, visit);
5612
+ }
5613
+ }
5614
+ function reconstructUrl(node) {
5615
+ if (node.type === "string") {
5616
+ for (let i = 0; i < node.namedChildCount; i++) {
5617
+ const child = node.namedChild(i);
5618
+ if (child?.type === "string_fragment") return child.text;
5619
+ }
5620
+ return "";
5621
+ }
5622
+ if (node.type === "template_string") {
5623
+ let out = "";
5624
+ for (let i = 0; i < node.namedChildCount; i++) {
5625
+ const child = node.namedChild(i);
5626
+ if (!child) continue;
5627
+ if (child.type === "string_fragment") out += child.text;
5628
+ else if (child.type === "template_substitution") out += ":param";
5629
+ }
5630
+ if (out.length === 0) {
5631
+ const raw = node.text;
5632
+ return raw.length >= 2 ? raw.slice(1, -1) : "";
5633
+ }
5634
+ return out;
5635
+ }
5636
+ return null;
5637
+ }
5638
+ function methodFromOptions(objNode) {
5639
+ for (let i = 0; i < objNode.namedChildCount; i++) {
5640
+ const pair = objNode.namedChild(i);
5641
+ if (!pair || pair.type !== "pair") continue;
5642
+ const k = pair.childForFieldName("key");
5643
+ const kText = k ? k.type === "string" ? stringText(k) : k.text : null;
5644
+ if (kText !== "method") continue;
5645
+ const v = pair.childForFieldName("value");
5646
+ if (v && (v.type === "string" || v.type === "template_string")) {
5647
+ const s = stringText(v);
5648
+ return s ? s.toUpperCase() : void 0;
5649
+ }
5650
+ }
5651
+ return void 0;
5652
+ }
5653
+ function stringText(node) {
5654
+ if (node.type === "string") {
5655
+ for (let i = 0; i < node.namedChildCount; i++) {
5656
+ const child = node.namedChild(i);
5657
+ if (child?.type === "string_fragment") return child.text;
5658
+ }
5659
+ return "";
5660
+ }
5661
+ return null;
5662
+ }
5663
+ function urlNodeFromConfig(objNode) {
5664
+ for (let i = 0; i < objNode.namedChildCount; i++) {
5665
+ const pair = objNode.namedChild(i);
5666
+ if (!pair || pair.type !== "pair") continue;
5667
+ const k = pair.childForFieldName("key");
5668
+ const kText = k ? k.type === "string" ? stringText(k) : k.text : null;
5669
+ if (kText === "url") return pair.childForFieldName("value");
5670
+ }
5671
+ return null;
5672
+ }
5673
+ function pathOf(urlStr) {
5674
+ try {
5675
+ const candidate = urlStr.startsWith("//") ? `http:${urlStr}` : urlStr;
5676
+ const parsed = new URL(candidate);
5677
+ return parsed.pathname || "/";
5678
+ } catch {
5679
+ return null;
5680
+ }
5681
+ }
5682
+ function matchHost(urlStr, knownHosts) {
5683
+ for (const host of knownHosts) {
5684
+ if (urlMatchesHost(urlStr, host)) return host;
5685
+ }
5686
+ return null;
5687
+ }
5688
+ function clientCallSitesFromSource(source, parser, knownHosts) {
5689
+ const tree = parseSource4(parser, source);
5690
+ const out = [];
5691
+ const push = (urlNode, method, callNode) => {
5692
+ const urlStr = reconstructUrl(urlNode);
5693
+ if (!urlStr) return;
5694
+ const host = matchHost(urlStr, knownHosts);
5695
+ if (!host) return;
5696
+ const p = pathOf(urlStr);
5697
+ if (p === null) return;
5698
+ const line = callNode.startPosition.row + 1;
5699
+ out.push({
5700
+ host,
5701
+ method,
5702
+ pathTemplate: p,
5703
+ line,
5704
+ snippet: snippet(source, line)
5705
+ });
5706
+ };
5707
+ walk2(tree.rootNode, (node) => {
5708
+ if (node.type !== "call_expression") return;
5709
+ const fn = node.childForFieldName("function");
5710
+ if (!fn) return;
5711
+ const args = node.childForFieldName("arguments");
5712
+ const first = args?.namedChild(0);
5713
+ if (!first) return;
5714
+ const fnName = fn.type === "identifier" ? fn.text : fn.type === "member_expression" ? fn.childForFieldName("property")?.text ?? "" : "";
5715
+ if (fn.type === "identifier" && fnName === "fetch") {
5716
+ const opts = args?.namedChild(1);
5717
+ const method = opts && opts.type === "object" ? methodFromOptions(opts) ?? "GET" : "GET";
5718
+ push(first, method, node);
5719
+ return;
5720
+ }
5721
+ if (fn.type === "identifier" && fnName === "axios") {
5722
+ if (first.type === "object") {
5723
+ const urlNode = urlNodeFromConfig(first);
5724
+ if (urlNode) push(urlNode, methodFromOptions(first) ?? "GET", node);
5725
+ } else {
5726
+ const opts = args?.namedChild(1);
5727
+ const method = opts && opts.type === "object" ? methodFromOptions(opts) ?? "GET" : "GET";
5728
+ push(first, method, node);
5729
+ }
5730
+ return;
5731
+ }
5732
+ if (fn.type === "member_expression") {
5733
+ const obj = fn.childForFieldName("object");
5734
+ const objName = obj?.text ?? "";
5735
+ if (objName === "axios" && AXIOS_METHODS.has(fnName)) {
5736
+ if (fnName === "request" && first.type === "object") {
5737
+ const urlNode = urlNodeFromConfig(first);
5738
+ if (urlNode) push(urlNode, methodFromOptions(first) ?? "GET", node);
5739
+ } else {
5740
+ push(first, fnName.toUpperCase(), node);
5741
+ }
5742
+ return;
5743
+ }
5744
+ if ((objName === "http" || objName === "https") && (fnName === "request" || fnName === "get")) {
5745
+ const opts = args?.namedChild(1);
5746
+ const method = opts && opts.type === "object" ? methodFromOptions(opts) ?? (fnName === "get" ? "GET" : "GET") : "GET";
5747
+ push(first, method, node);
5748
+ return;
5749
+ }
5750
+ }
5751
+ });
5752
+ return out;
5753
+ }
5754
+ function buildRouteIndex(graph) {
5755
+ const index = /* @__PURE__ */ new Map();
5756
+ graph.forEachNode((_id, attrs) => {
5757
+ const node = attrs;
5758
+ if (node.type !== import_types13.NodeType.RouteNode) return;
5759
+ const route = attrs;
5760
+ const owner = (0, import_types13.serviceId)(route.service);
5761
+ const entry2 = {
5762
+ method: route.method.toUpperCase(),
5763
+ normalizedPath: normalizePathTemplate(route.pathTemplate),
5764
+ routeNodeId: route.id
5765
+ };
5766
+ const list = index.get(owner);
5767
+ if (list) list.push(entry2);
5768
+ else index.set(owner, [entry2]);
5769
+ });
5770
+ return index;
5771
+ }
5772
+ function findRoute(entries, method, normalizedPath) {
5773
+ return entries.find(
5774
+ (e) => e.normalizedPath === normalizedPath && (e.method === "ALL" || method === void 0 || e.method === method)
5775
+ );
5776
+ }
5777
+ async function addRouteCallEdges(graph, services) {
5778
+ const jsParser = makeJsParser4();
5779
+ const { knownHosts, hostToNodeId } = buildServiceHostIndex(services);
5780
+ const routeIndex = buildRouteIndex(graph);
5781
+ if (routeIndex.size === 0) return { nodesAdded: 0, edgesAdded: 0 };
5782
+ let nodesAdded = 0;
5783
+ let edgesAdded = 0;
5784
+ for (const service of services) {
5785
+ const files = await loadSourceFiles(service.dir);
5786
+ const seen = /* @__PURE__ */ new Set();
5787
+ for (const file of files) {
5788
+ if (isTestPath(file.path)) continue;
5789
+ if (!JS_CLIENT_EXTENSIONS.has(import_node_path25.default.extname(file.path))) continue;
5790
+ let sites;
5791
+ try {
5792
+ sites = clientCallSitesFromSource(file.content, jsParser, knownHosts);
5793
+ } catch (err) {
5794
+ recordExtractionError("route-match call extraction", file.path, err);
5795
+ continue;
5796
+ }
5797
+ if (sites.length === 0) continue;
5798
+ const relFile = toPosix2(import_node_path25.default.relative(service.dir, file.path));
5799
+ for (const site of sites) {
5800
+ const serverServiceId = hostToNodeId.get(site.host);
5801
+ if (!serverServiceId || serverServiceId === service.node.id) continue;
5802
+ const entries = routeIndex.get(serverServiceId);
5803
+ if (!entries) continue;
5804
+ const normalizedPath = normalizePathTemplate(site.pathTemplate);
5805
+ const match = findRoute(entries, site.method, normalizedPath);
5806
+ if (!match) continue;
5807
+ const dedupKey = `${relFile}|${match.routeNodeId}`;
5808
+ if (seen.has(dedupKey)) continue;
5809
+ seen.add(dedupKey);
5810
+ const { fileNodeId, nodesAdded: n, edgesAdded: e } = ensureFileNode(
5811
+ graph,
5812
+ service.pkg.name,
5813
+ service.node.id,
5814
+ relFile
5815
+ );
5816
+ nodesAdded += n;
5817
+ edgesAdded += e;
5818
+ const confidence = (0, import_types13.confidenceForExtracted)("verified-call-site");
5819
+ const ev = {
5820
+ file: relFile,
5821
+ line: site.line,
5822
+ snippet: site.snippet,
5823
+ method: site.method ?? match.method,
5824
+ pathTemplate: site.pathTemplate
5825
+ };
5826
+ if (!(0, import_types13.passesExtractedFloor)(confidence)) {
5827
+ noteExtractedDropped({
5828
+ source: fileNodeId,
5829
+ target: match.routeNodeId,
5830
+ type: import_types13.EdgeType.CALLS,
5831
+ confidence,
5832
+ confidenceKind: "verified-call-site",
5833
+ evidence: ev
5834
+ });
5835
+ continue;
5836
+ }
5837
+ const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, match.routeNodeId, import_types13.EdgeType.CALLS);
5838
+ if (!graph.hasEdge(edgeId)) {
5839
+ const edge = {
5840
+ id: edgeId,
5841
+ source: fileNodeId,
5842
+ target: match.routeNodeId,
5843
+ type: import_types13.EdgeType.CALLS,
5844
+ provenance: import_types13.Provenance.EXTRACTED,
5845
+ confidence,
5846
+ evidence: ev
5847
+ };
5848
+ graph.addEdgeWithKey(edgeId, fileNodeId, match.routeNodeId, edge);
5849
+ edgesAdded++;
5850
+ }
5851
+ }
5852
+ }
5853
+ }
5854
+ return { nodesAdded, edgesAdded };
5855
+ }
5856
+
5128
5857
  // src/extract/calls/kafka.ts
5129
5858
  init_cjs_shims();
5130
- var import_node_path24 = __toESM(require("path"), 1);
5131
- var import_types12 = require("@neat.is/types");
5859
+ var import_node_path26 = __toESM(require("path"), 1);
5860
+ var import_types14 = require("@neat.is/types");
5132
5861
  var PRODUCER_TOPIC_RE = /(?:producer|kafkaProducer)[\s\S]{0,40}?\.send\s*\(\s*\{[\s\S]{0,200}?topic\s*:\s*['"`]([^'"`]+)['"`]/g;
5133
5862
  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
5863
  function findAll(re, text) {
@@ -5149,7 +5878,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
5149
5878
  seen.add(key);
5150
5879
  const line = lineOf(file.content, topic);
5151
5880
  out.push({
5152
- infraId: (0, import_types12.infraId)("kafka-topic", topic),
5881
+ infraId: (0, import_types14.infraId)("kafka-topic", topic),
5153
5882
  name: topic,
5154
5883
  kind: "kafka-topic",
5155
5884
  edgeType,
@@ -5158,7 +5887,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
5158
5887
  // tier (ADR-066).
5159
5888
  confidenceKind: "verified-call-site",
5160
5889
  evidence: {
5161
- file: import_node_path24.default.relative(serviceDir, file.path),
5890
+ file: import_node_path26.default.relative(serviceDir, file.path),
5162
5891
  line,
5163
5892
  snippet: snippet(file.content, line)
5164
5893
  }
@@ -5171,8 +5900,8 @@ function kafkaEndpointsFromFile(file, serviceDir) {
5171
5900
 
5172
5901
  // src/extract/calls/redis.ts
5173
5902
  init_cjs_shims();
5174
- var import_node_path25 = __toESM(require("path"), 1);
5175
- var import_types13 = require("@neat.is/types");
5903
+ var import_node_path27 = __toESM(require("path"), 1);
5904
+ var import_types15 = require("@neat.is/types");
5176
5905
  var REDIS_URL_RE = /redis(?:s)?:\/\/(?:[^@'"`\s]+@)?([^:/'"`\s]+)(?::(\d+))?/g;
5177
5906
  function redisEndpointsFromFile(file, serviceDir) {
5178
5907
  const out = [];
@@ -5185,7 +5914,7 @@ function redisEndpointsFromFile(file, serviceDir) {
5185
5914
  seen.add(host);
5186
5915
  const line = lineOf(file.content, host);
5187
5916
  out.push({
5188
- infraId: (0, import_types13.infraId)("redis", host),
5917
+ infraId: (0, import_types15.infraId)("redis", host),
5189
5918
  name: host,
5190
5919
  kind: "redis",
5191
5920
  edgeType: "CALLS",
@@ -5194,7 +5923,7 @@ function redisEndpointsFromFile(file, serviceDir) {
5194
5923
  // support tier (ADR-066).
5195
5924
  confidenceKind: "url-with-structural-support",
5196
5925
  evidence: {
5197
- file: import_node_path25.default.relative(serviceDir, file.path),
5926
+ file: import_node_path27.default.relative(serviceDir, file.path),
5198
5927
  line,
5199
5928
  snippet: snippet(file.content, line)
5200
5929
  }
@@ -5205,8 +5934,8 @@ function redisEndpointsFromFile(file, serviceDir) {
5205
5934
 
5206
5935
  // src/extract/calls/aws.ts
5207
5936
  init_cjs_shims();
5208
- var import_node_path26 = __toESM(require("path"), 1);
5209
- var import_types14 = require("@neat.is/types");
5937
+ var import_node_path28 = __toESM(require("path"), 1);
5938
+ var import_types16 = require("@neat.is/types");
5210
5939
  var S3_BUCKET_RE = /Bucket\s*:\s*['"`]([^'"`]+)['"`]/g;
5211
5940
  var DYNAMO_TABLE_RE = /TableName\s*:\s*['"`]([^'"`]+)['"`]/g;
5212
5941
  function hasMarker(text, markers) {
@@ -5230,7 +5959,7 @@ function awsEndpointsFromFile(file, serviceDir) {
5230
5959
  seen.add(key);
5231
5960
  const line = lineOf(file.content, name);
5232
5961
  out.push({
5233
- infraId: (0, import_types14.infraId)(kind, name),
5962
+ infraId: (0, import_types16.infraId)(kind, name),
5234
5963
  name,
5235
5964
  kind,
5236
5965
  edgeType: "CALLS",
@@ -5239,7 +5968,7 @@ function awsEndpointsFromFile(file, serviceDir) {
5239
5968
  // (ADR-066).
5240
5969
  confidenceKind: "verified-call-site",
5241
5970
  evidence: {
5242
- file: import_node_path26.default.relative(serviceDir, file.path),
5971
+ file: import_node_path28.default.relative(serviceDir, file.path),
5243
5972
  line,
5244
5973
  snippet: snippet(file.content, line)
5245
5974
  }
@@ -5264,8 +5993,8 @@ function awsEndpointsFromFile(file, serviceDir) {
5264
5993
 
5265
5994
  // src/extract/calls/grpc.ts
5266
5995
  init_cjs_shims();
5267
- var import_node_path27 = __toESM(require("path"), 1);
5268
- var import_types15 = require("@neat.is/types");
5996
+ var import_node_path29 = __toESM(require("path"), 1);
5997
+ var import_types17 = require("@neat.is/types");
5269
5998
  var GRPC_CLIENT_RE = /new\s+([A-Z][A-Za-z0-9_]*)Client\s*\(\s*['"`]?([^,'"`)]+)?/g;
5270
5999
  var AWS_SDK_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@aws-sdk\/client-([a-z0-9-]+)['"`]/g;
5271
6000
  var GRPC_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@grpc\/grpc-js['"`]|_grpc_pb['"`]/;
@@ -5314,7 +6043,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
5314
6043
  const { kind } = classified;
5315
6044
  const line = lineOf(file.content, m[0]);
5316
6045
  out.push({
5317
- infraId: (0, import_types15.infraId)(kind, name),
6046
+ infraId: (0, import_types17.infraId)(kind, name),
5318
6047
  name,
5319
6048
  kind,
5320
6049
  edgeType: "CALLS",
@@ -5323,7 +6052,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
5323
6052
  // tier (ADR-066).
5324
6053
  confidenceKind: "verified-call-site",
5325
6054
  evidence: {
5326
- file: import_node_path27.default.relative(serviceDir, file.path),
6055
+ file: import_node_path29.default.relative(serviceDir, file.path),
5327
6056
  line,
5328
6057
  snippet: snippet(file.content, line)
5329
6058
  }
@@ -5334,8 +6063,8 @@ function grpcEndpointsFromFile(file, serviceDir) {
5334
6063
 
5335
6064
  // src/extract/calls/supabase.ts
5336
6065
  init_cjs_shims();
5337
- var import_node_path28 = __toESM(require("path"), 1);
5338
- var import_types16 = require("@neat.is/types");
6066
+ var import_node_path30 = __toESM(require("path"), 1);
6067
+ var import_types18 = require("@neat.is/types");
5339
6068
  var SUPABASE_JS_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@supabase\/supabase-js['"`]/;
5340
6069
  var SUPABASE_SSR_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@supabase\/ssr['"`]/;
5341
6070
  var SUPABASE_CLIENT_RE = /\b(createClient|createServerClient|createBrowserClient)\s*\(\s*(?:['"`]([^'"`]*)['"`])?/g;
@@ -5373,7 +6102,7 @@ function supabaseEndpointsFromFile(file, serviceDir) {
5373
6102
  seen.add(name);
5374
6103
  const line = lineOf(file.content, m[0]);
5375
6104
  out.push({
5376
- infraId: (0, import_types16.infraId)("supabase", name),
6105
+ infraId: (0, import_types18.infraId)("supabase", name),
5377
6106
  name,
5378
6107
  kind: "supabase",
5379
6108
  edgeType: "CALLS",
@@ -5383,7 +6112,7 @@ function supabaseEndpointsFromFile(file, serviceDir) {
5383
6112
  // tier (ADR-066), the same grade aws.ts / grpc.ts emit at.
5384
6113
  confidenceKind: "verified-call-site",
5385
6114
  evidence: {
5386
- file: import_node_path28.default.relative(serviceDir, file.path),
6115
+ file: import_node_path30.default.relative(serviceDir, file.path),
5387
6116
  line,
5388
6117
  snippet: snippet(file.content, line)
5389
6118
  }
@@ -5396,11 +6125,11 @@ function supabaseEndpointsFromFile(file, serviceDir) {
5396
6125
  function edgeTypeFromEndpoint(ep) {
5397
6126
  switch (ep.edgeType) {
5398
6127
  case "PUBLISHES_TO":
5399
- return import_types17.EdgeType.PUBLISHES_TO;
6128
+ return import_types19.EdgeType.PUBLISHES_TO;
5400
6129
  case "CONSUMES_FROM":
5401
- return import_types17.EdgeType.CONSUMES_FROM;
6130
+ return import_types19.EdgeType.CONSUMES_FROM;
5402
6131
  default:
5403
- return import_types17.EdgeType.CALLS;
6132
+ return import_types19.EdgeType.CALLS;
5404
6133
  }
5405
6134
  }
5406
6135
  function isAwsKind(kind) {
@@ -5428,7 +6157,7 @@ async function addExternalEndpointEdges(graph, services) {
5428
6157
  if (!graph.hasNode(ep.infraId)) {
5429
6158
  const node = {
5430
6159
  id: ep.infraId,
5431
- type: import_types17.NodeType.InfraNode,
6160
+ type: import_types19.NodeType.InfraNode,
5432
6161
  name: ep.name,
5433
6162
  // #238 — `aws-*` covers AWS-SDK client kinds (aws-s3, aws-dynamodb,
5434
6163
  // aws-cognito-identity-provider, …); `s3-` / `dynamodb-` cover the
@@ -5440,7 +6169,7 @@ async function addExternalEndpointEdges(graph, services) {
5440
6169
  nodesAdded++;
5441
6170
  }
5442
6171
  const edgeType = edgeTypeFromEndpoint(ep);
5443
- const confidence = (0, import_types17.confidenceForExtracted)(ep.confidenceKind);
6172
+ const confidence = (0, import_types19.confidenceForExtracted)(ep.confidenceKind);
5444
6173
  const relFile = toPosix2(ep.evidence.file);
5445
6174
  const { fileNodeId, nodesAdded: n, edgesAdded: e } = ensureFileNode(
5446
6175
  graph,
@@ -5450,7 +6179,7 @@ async function addExternalEndpointEdges(graph, services) {
5450
6179
  );
5451
6180
  nodesAdded += n;
5452
6181
  edgesAdded += e;
5453
- if (!(0, import_types17.passesExtractedFloor)(confidence)) {
6182
+ if (!(0, import_types19.passesExtractedFloor)(confidence)) {
5454
6183
  noteExtractedDropped({
5455
6184
  source: fileNodeId,
5456
6185
  target: ep.infraId,
@@ -5470,7 +6199,7 @@ async function addExternalEndpointEdges(graph, services) {
5470
6199
  source: fileNodeId,
5471
6200
  target: ep.infraId,
5472
6201
  type: edgeType,
5473
- provenance: import_types17.Provenance.EXTRACTED,
6202
+ provenance: import_types19.Provenance.EXTRACTED,
5474
6203
  confidence,
5475
6204
  evidence: ep.evidence
5476
6205
  };
@@ -5484,9 +6213,10 @@ async function addExternalEndpointEdges(graph, services) {
5484
6213
  async function addCallEdges(graph, services) {
5485
6214
  const http2 = await addHttpCallEdges(graph, services);
5486
6215
  const ext = await addExternalEndpointEdges(graph, services);
6216
+ const routes = await addRouteCallEdges(graph, services);
5487
6217
  return {
5488
- nodesAdded: http2.nodesAdded + ext.nodesAdded,
5489
- edgesAdded: http2.edgesAdded + ext.edgesAdded
6218
+ nodesAdded: http2.nodesAdded + ext.nodesAdded + routes.nodesAdded,
6219
+ edgesAdded: http2.edgesAdded + ext.edgesAdded + routes.edgesAdded
5490
6220
  };
5491
6221
  }
5492
6222
 
@@ -5495,16 +6225,16 @@ init_cjs_shims();
5495
6225
 
5496
6226
  // src/extract/infra/docker-compose.ts
5497
6227
  init_cjs_shims();
5498
- var import_node_path29 = __toESM(require("path"), 1);
5499
- var import_types19 = require("@neat.is/types");
6228
+ var import_node_path31 = __toESM(require("path"), 1);
6229
+ var import_types21 = require("@neat.is/types");
5500
6230
 
5501
6231
  // src/extract/infra/shared.ts
5502
6232
  init_cjs_shims();
5503
- var import_types18 = require("@neat.is/types");
6233
+ var import_types20 = require("@neat.is/types");
5504
6234
  function makeInfraNode(kind, name, provider = "self", extras) {
5505
6235
  return {
5506
- id: (0, import_types18.infraId)(kind, name),
5507
- type: import_types18.NodeType.InfraNode,
6236
+ id: (0, import_types20.infraId)(kind, name),
6237
+ type: import_types20.NodeType.InfraNode,
5508
6238
  name,
5509
6239
  provider,
5510
6240
  kind,
@@ -5533,7 +6263,7 @@ function dependsOnList(value) {
5533
6263
  }
5534
6264
  function serviceNameToServiceNode(name, services) {
5535
6265
  for (const s of services) {
5536
- if (s.node.name === name || import_node_path29.default.basename(s.dir) === name) return s.node.id;
6266
+ if (s.node.name === name || import_node_path31.default.basename(s.dir) === name) return s.node.id;
5537
6267
  }
5538
6268
  return null;
5539
6269
  }
@@ -5542,7 +6272,7 @@ async function addComposeInfra(graph, scanPath, services) {
5542
6272
  let edgesAdded = 0;
5543
6273
  let composePath = null;
5544
6274
  for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
5545
- const abs = import_node_path29.default.join(scanPath, name);
6275
+ const abs = import_node_path31.default.join(scanPath, name);
5546
6276
  if (await exists(abs)) {
5547
6277
  composePath = abs;
5548
6278
  break;
@@ -5555,13 +6285,13 @@ async function addComposeInfra(graph, scanPath, services) {
5555
6285
  } catch (err) {
5556
6286
  recordExtractionError(
5557
6287
  "infra docker-compose",
5558
- import_node_path29.default.relative(scanPath, composePath),
6288
+ import_node_path31.default.relative(scanPath, composePath),
5559
6289
  err
5560
6290
  );
5561
6291
  return { nodesAdded, edgesAdded };
5562
6292
  }
5563
6293
  if (!compose?.services) return { nodesAdded, edgesAdded };
5564
- const evidenceFile = import_node_path29.default.relative(scanPath, composePath).split(import_node_path29.default.sep).join("/");
6294
+ const evidenceFile = import_node_path31.default.relative(scanPath, composePath).split(import_node_path31.default.sep).join("/");
5565
6295
  const composeNameToNodeId = /* @__PURE__ */ new Map();
5566
6296
  for (const [composeName, svc] of Object.entries(compose.services)) {
5567
6297
  const matchedServiceId = serviceNameToServiceNode(composeName, services);
@@ -5583,15 +6313,15 @@ async function addComposeInfra(graph, scanPath, services) {
5583
6313
  for (const dep of dependsOnList(svc.depends_on)) {
5584
6314
  const targetId = composeNameToNodeId.get(dep);
5585
6315
  if (!targetId) continue;
5586
- const edgeId = (0, import_types4.extractedEdgeId)(sourceId, targetId, import_types19.EdgeType.DEPENDS_ON);
6316
+ const edgeId = (0, import_types4.extractedEdgeId)(sourceId, targetId, import_types21.EdgeType.DEPENDS_ON);
5587
6317
  if (graph.hasEdge(edgeId)) continue;
5588
6318
  const edge = {
5589
6319
  id: edgeId,
5590
6320
  source: sourceId,
5591
6321
  target: targetId,
5592
- type: import_types19.EdgeType.DEPENDS_ON,
5593
- provenance: import_types19.Provenance.EXTRACTED,
5594
- confidence: (0, import_types19.confidenceForExtracted)("structural"),
6322
+ type: import_types21.EdgeType.DEPENDS_ON,
6323
+ provenance: import_types21.Provenance.EXTRACTED,
6324
+ confidence: (0, import_types21.confidenceForExtracted)("structural"),
5595
6325
  evidence: { file: evidenceFile }
5596
6326
  };
5597
6327
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -5603,9 +6333,9 @@ async function addComposeInfra(graph, scanPath, services) {
5603
6333
 
5604
6334
  // src/extract/infra/dockerfile.ts
5605
6335
  init_cjs_shims();
5606
- var import_node_path30 = __toESM(require("path"), 1);
6336
+ var import_node_path32 = __toESM(require("path"), 1);
5607
6337
  var import_node_fs16 = require("fs");
5608
- var import_types20 = require("@neat.is/types");
6338
+ var import_types22 = require("@neat.is/types");
5609
6339
  function readDockerfile(content) {
5610
6340
  let image = null;
5611
6341
  const ports = [];
@@ -5634,7 +6364,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
5634
6364
  let nodesAdded = 0;
5635
6365
  let edgesAdded = 0;
5636
6366
  for (const service of services) {
5637
- const dockerfilePath = import_node_path30.default.join(service.dir, "Dockerfile");
6367
+ const dockerfilePath = import_node_path32.default.join(service.dir, "Dockerfile");
5638
6368
  if (!await exists(dockerfilePath)) continue;
5639
6369
  let content;
5640
6370
  try {
@@ -5642,7 +6372,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
5642
6372
  } catch (err) {
5643
6373
  recordExtractionError(
5644
6374
  "infra dockerfile",
5645
- import_node_path30.default.relative(scanPath, dockerfilePath),
6375
+ import_node_path32.default.relative(scanPath, dockerfilePath),
5646
6376
  err
5647
6377
  );
5648
6378
  continue;
@@ -5654,8 +6384,8 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
5654
6384
  graph.addNode(node.id, node);
5655
6385
  nodesAdded++;
5656
6386
  }
5657
- const relDockerfile = toPosix2(import_node_path30.default.relative(service.dir, dockerfilePath));
5658
- const evidenceFile = toPosix2(import_node_path30.default.relative(scanPath, dockerfilePath));
6387
+ const relDockerfile = toPosix2(import_node_path32.default.relative(service.dir, dockerfilePath));
6388
+ const evidenceFile = toPosix2(import_node_path32.default.relative(scanPath, dockerfilePath));
5659
6389
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
5660
6390
  graph,
5661
6391
  service.pkg.name,
@@ -5664,15 +6394,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
5664
6394
  );
5665
6395
  nodesAdded += fn;
5666
6396
  edgesAdded += fe;
5667
- const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, node.id, import_types20.EdgeType.RUNS_ON);
6397
+ const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, node.id, import_types22.EdgeType.RUNS_ON);
5668
6398
  if (!graph.hasEdge(edgeId)) {
5669
6399
  const edge = {
5670
6400
  id: edgeId,
5671
6401
  source: fileNodeId,
5672
6402
  target: node.id,
5673
- type: import_types20.EdgeType.RUNS_ON,
5674
- provenance: import_types20.Provenance.EXTRACTED,
5675
- confidence: (0, import_types20.confidenceForExtracted)("structural"),
6403
+ type: import_types22.EdgeType.RUNS_ON,
6404
+ provenance: import_types22.Provenance.EXTRACTED,
6405
+ confidence: (0, import_types22.confidenceForExtracted)("structural"),
5676
6406
  evidence: {
5677
6407
  file: evidenceFile,
5678
6408
  ...facts.entrypoint ? { snippet: facts.entrypoint.slice(0, 120) } : {}
@@ -5687,15 +6417,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
5687
6417
  graph.addNode(portNode.id, portNode);
5688
6418
  nodesAdded++;
5689
6419
  }
5690
- const portEdgeId = (0, import_types4.extractedEdgeId)(fileNodeId, portNode.id, import_types20.EdgeType.CONNECTS_TO);
6420
+ const portEdgeId = (0, import_types4.extractedEdgeId)(fileNodeId, portNode.id, import_types22.EdgeType.CONNECTS_TO);
5691
6421
  if (graph.hasEdge(portEdgeId)) continue;
5692
6422
  const portEdge = {
5693
6423
  id: portEdgeId,
5694
6424
  source: fileNodeId,
5695
6425
  target: portNode.id,
5696
- type: import_types20.EdgeType.CONNECTS_TO,
5697
- provenance: import_types20.Provenance.EXTRACTED,
5698
- confidence: (0, import_types20.confidenceForExtracted)("structural"),
6426
+ type: import_types22.EdgeType.CONNECTS_TO,
6427
+ provenance: import_types22.Provenance.EXTRACTED,
6428
+ confidence: (0, import_types22.confidenceForExtracted)("structural"),
5699
6429
  evidence: { file: evidenceFile, snippet: `EXPOSE ${port}` }
5700
6430
  };
5701
6431
  graph.addEdgeWithKey(portEdgeId, portEdge.source, portEdge.target, portEdge);
@@ -5708,8 +6438,8 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
5708
6438
  // src/extract/infra/terraform.ts
5709
6439
  init_cjs_shims();
5710
6440
  var import_node_fs17 = require("fs");
5711
- var import_node_path31 = __toESM(require("path"), 1);
5712
- var import_types21 = require("@neat.is/types");
6441
+ var import_node_path33 = __toESM(require("path"), 1);
6442
+ var import_types23 = require("@neat.is/types");
5713
6443
  var RESOURCE_RE = /resource\s+"(aws_[A-Za-z0-9_]+)"\s+"([A-Za-z0-9_-]+)"/g;
5714
6444
  var REFERENCE_RE = /(?<![\w.])(aws_[A-Za-z0-9_]+)\.([A-Za-z0-9_-]+)/g;
5715
6445
  async function walkTfFiles(start, depth = 0, max = 5) {
@@ -5719,11 +6449,11 @@ async function walkTfFiles(start, depth = 0, max = 5) {
5719
6449
  for (const entry2 of entries) {
5720
6450
  if (entry2.isDirectory()) {
5721
6451
  if (IGNORED_DIRS.has(entry2.name) || entry2.name === ".terraform") continue;
5722
- const child = import_node_path31.default.join(start, entry2.name);
6452
+ const child = import_node_path33.default.join(start, entry2.name);
5723
6453
  if (await isPythonVenvDir(child)) continue;
5724
6454
  out.push(...await walkTfFiles(child, depth + 1, max));
5725
6455
  } else if (entry2.isFile() && entry2.name.endsWith(".tf")) {
5726
- out.push(import_node_path31.default.join(start, entry2.name));
6456
+ out.push(import_node_path33.default.join(start, entry2.name));
5727
6457
  }
5728
6458
  }
5729
6459
  return out;
@@ -5755,7 +6485,7 @@ async function addTerraformResources(graph, scanPath) {
5755
6485
  const files = await walkTfFiles(scanPath);
5756
6486
  for (const file of files) {
5757
6487
  const content = await import_node_fs17.promises.readFile(file, "utf8");
5758
- const evidenceFile = toPosix2(import_node_path31.default.relative(scanPath, file));
6488
+ const evidenceFile = toPosix2(import_node_path33.default.relative(scanPath, file));
5759
6489
  const resources = [];
5760
6490
  const byKey = /* @__PURE__ */ new Map();
5761
6491
  RESOURCE_RE.lastIndex = 0;
@@ -5790,16 +6520,16 @@ async function addTerraformResources(graph, scanPath) {
5790
6520
  if (!target) continue;
5791
6521
  if (seen.has(target.nodeId)) continue;
5792
6522
  seen.add(target.nodeId);
5793
- const edgeId = (0, import_types4.extractedEdgeId)(resource.nodeId, target.nodeId, import_types21.EdgeType.DEPENDS_ON);
6523
+ const edgeId = (0, import_types4.extractedEdgeId)(resource.nodeId, target.nodeId, import_types23.EdgeType.DEPENDS_ON);
5794
6524
  if (graph.hasEdge(edgeId)) continue;
5795
6525
  const line = lineAt(content, resource.bodyOffset + ref.index);
5796
6526
  const edge = {
5797
6527
  id: edgeId,
5798
6528
  source: resource.nodeId,
5799
6529
  target: target.nodeId,
5800
- type: import_types21.EdgeType.DEPENDS_ON,
5801
- provenance: import_types21.Provenance.EXTRACTED,
5802
- confidence: (0, import_types21.confidenceForExtracted)("structural"),
6530
+ type: import_types23.EdgeType.DEPENDS_ON,
6531
+ provenance: import_types23.Provenance.EXTRACTED,
6532
+ confidence: (0, import_types23.confidenceForExtracted)("structural"),
5803
6533
  evidence: { file: evidenceFile, line, snippet: key }
5804
6534
  };
5805
6535
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -5813,7 +6543,7 @@ async function addTerraformResources(graph, scanPath) {
5813
6543
  // src/extract/infra/k8s.ts
5814
6544
  init_cjs_shims();
5815
6545
  var import_node_fs18 = require("fs");
5816
- var import_node_path32 = __toESM(require("path"), 1);
6546
+ var import_node_path34 = __toESM(require("path"), 1);
5817
6547
  var import_yaml3 = require("yaml");
5818
6548
  var K8S_KIND_TO_INFRA_KIND = {
5819
6549
  Service: "k8s-service",
@@ -5831,11 +6561,11 @@ async function walkYamlFiles2(start, depth = 0, max = 5) {
5831
6561
  for (const entry2 of entries) {
5832
6562
  if (entry2.isDirectory()) {
5833
6563
  if (IGNORED_DIRS.has(entry2.name)) continue;
5834
- const child = import_node_path32.default.join(start, entry2.name);
6564
+ const child = import_node_path34.default.join(start, entry2.name);
5835
6565
  if (await isPythonVenvDir(child)) continue;
5836
6566
  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));
6567
+ } else if (entry2.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path34.default.extname(entry2.name))) {
6568
+ out.push(import_node_path34.default.join(start, entry2.name));
5839
6569
  }
5840
6570
  }
5841
6571
  return out;
@@ -5879,17 +6609,17 @@ async function addInfra(graph, scanPath, services) {
5879
6609
  }
5880
6610
 
5881
6611
  // src/extract/index.ts
5882
- var import_node_path34 = __toESM(require("path"), 1);
6612
+ var import_node_path36 = __toESM(require("path"), 1);
5883
6613
 
5884
6614
  // src/extract/retire.ts
5885
6615
  init_cjs_shims();
5886
6616
  var import_node_fs19 = require("fs");
5887
- var import_node_path33 = __toESM(require("path"), 1);
5888
- var import_types22 = require("@neat.is/types");
6617
+ var import_node_path35 = __toESM(require("path"), 1);
6618
+ var import_types24 = require("@neat.is/types");
5889
6619
  function dropOrphanedFileNodes(graph) {
5890
6620
  const orphans = [];
5891
6621
  graph.forEachNode((id, attrs) => {
5892
- if (attrs.type !== import_types22.NodeType.FileNode) return;
6622
+ if (attrs.type !== import_types24.NodeType.FileNode) return;
5893
6623
  if (graph.inboundEdges(id).length === 0 && graph.outboundEdges(id).length === 0) {
5894
6624
  orphans.push(id);
5895
6625
  }
@@ -5902,7 +6632,7 @@ function retireEdgesByFile(graph, file) {
5902
6632
  const toDrop = [];
5903
6633
  graph.forEachEdge((id, attrs) => {
5904
6634
  const edge = attrs;
5905
- if (edge.provenance !== import_types22.Provenance.EXTRACTED) return;
6635
+ if (edge.provenance !== import_types24.Provenance.EXTRACTED) return;
5906
6636
  if (!edge.evidence?.file) return;
5907
6637
  if (edge.evidence.file === normalized) toDrop.push(id);
5908
6638
  });
@@ -5915,14 +6645,14 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
5915
6645
  const bases = [scanPath, ...serviceDirs];
5916
6646
  graph.forEachEdge((id, attrs) => {
5917
6647
  const edge = attrs;
5918
- if (edge.provenance !== import_types22.Provenance.EXTRACTED) return;
6648
+ if (edge.provenance !== import_types24.Provenance.EXTRACTED) return;
5919
6649
  const evidenceFile = edge.evidence?.file;
5920
6650
  if (!evidenceFile) return;
5921
- if (import_node_path33.default.isAbsolute(evidenceFile)) {
6651
+ if (import_node_path35.default.isAbsolute(evidenceFile)) {
5922
6652
  if (!(0, import_node_fs19.existsSync)(evidenceFile)) toDrop.push(id);
5923
6653
  return;
5924
6654
  }
5925
- const found = bases.some((base) => (0, import_node_fs19.existsSync)(import_node_path33.default.join(base, evidenceFile)));
6655
+ const found = bases.some((base) => (0, import_node_fs19.existsSync)(import_node_path35.default.join(base, evidenceFile)));
5926
6656
  if (!found) toDrop.push(id);
5927
6657
  });
5928
6658
  for (const id of toDrop) graph.dropEdge(id);
@@ -5941,6 +6671,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
5941
6671
  const importGraph = await addImports(graph, services);
5942
6672
  const phase2 = await addDatabasesAndCompat(graph, services, scanPath);
5943
6673
  const phase3 = await addConfigNodes(graph, services, scanPath);
6674
+ const routePhase = await addRoutes(graph, services);
5944
6675
  const phase4 = await addCallEdges(graph, services);
5945
6676
  const phase5 = await addInfra(graph, scanPath, services);
5946
6677
  const ghostsRetired = retireExtractedEdgesByMissingFile(
@@ -5962,7 +6693,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
5962
6693
  }
5963
6694
  const droppedEntries = drainDroppedExtracted();
5964
6695
  if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
5965
- const rejectedPath = import_node_path34.default.join(import_node_path34.default.dirname(opts.errorsPath), "rejected.ndjson");
6696
+ const rejectedPath = import_node_path36.default.join(import_node_path36.default.dirname(opts.errorsPath), "rejected.ndjson");
5966
6697
  try {
5967
6698
  await writeRejectedExtracted(droppedEntries, rejectedPath);
5968
6699
  } catch (err) {
@@ -5972,8 +6703,8 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
5972
6703
  }
5973
6704
  }
5974
6705
  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,
6706
+ nodesAdded: phase1Nodes + fileEnum.nodesAdded + importGraph.nodesAdded + phase2.nodesAdded + phase3.nodesAdded + routePhase.nodesAdded + phase4.nodesAdded + phase5.nodesAdded,
6707
+ edgesAdded: fileEnum.edgesAdded + importGraph.edgesAdded + phase2.edgesAdded + phase3.edgesAdded + routePhase.edgesAdded + phase4.edgesAdded + phase5.edgesAdded,
5977
6708
  frontiersPromoted,
5978
6709
  extractionErrors: errorEntries.length,
5979
6710
  errorEntries,
@@ -5996,7 +6727,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
5996
6727
 
5997
6728
  // src/divergences.ts
5998
6729
  init_cjs_shims();
5999
- var import_types23 = require("@neat.is/types");
6730
+ var import_types25 = require("@neat.is/types");
6000
6731
  function bucketKey(source, target, type) {
6001
6732
  return `${type}|${source}|${target}`;
6002
6733
  }
@@ -6004,22 +6735,22 @@ function bucketEdges(graph) {
6004
6735
  const buckets = /* @__PURE__ */ new Map();
6005
6736
  graph.forEachEdge((id, attrs) => {
6006
6737
  const e = attrs;
6007
- const parsed = (0, import_types23.parseEdgeId)(id);
6738
+ const parsed = (0, import_types25.parseEdgeId)(id);
6008
6739
  const provenance = parsed?.provenance ?? e.provenance;
6009
6740
  const key = bucketKey(e.source, e.target, e.type);
6010
6741
  const cur = buckets.get(key) ?? { source: e.source, target: e.target, type: e.type };
6011
6742
  switch (provenance) {
6012
- case import_types23.Provenance.EXTRACTED:
6743
+ case import_types25.Provenance.EXTRACTED:
6013
6744
  cur.extracted = e;
6014
6745
  break;
6015
- case import_types23.Provenance.OBSERVED:
6746
+ case import_types25.Provenance.OBSERVED:
6016
6747
  cur.observed = e;
6017
6748
  break;
6018
- case import_types23.Provenance.INFERRED:
6749
+ case import_types25.Provenance.INFERRED:
6019
6750
  cur.inferred = e;
6020
6751
  break;
6021
6752
  default:
6022
- if (e.provenance === import_types23.Provenance.STALE) cur.stale = e;
6753
+ if (e.provenance === import_types25.Provenance.STALE) cur.stale = e;
6023
6754
  }
6024
6755
  buckets.set(key, cur);
6025
6756
  });
@@ -6028,7 +6759,7 @@ function bucketEdges(graph) {
6028
6759
  function nodeIsFrontier(graph, nodeId) {
6029
6760
  if (!graph.hasNode(nodeId)) return false;
6030
6761
  const attrs = graph.getNodeAttributes(nodeId);
6031
- return attrs.type === import_types23.NodeType.FrontierNode;
6762
+ return attrs.type === import_types25.NodeType.FrontierNode;
6032
6763
  }
6033
6764
  function clampConfidence(n) {
6034
6765
  if (!Number.isFinite(n)) return 0;
@@ -6048,14 +6779,14 @@ function gradedConfidence(edge) {
6048
6779
  return clampConfidence(confidenceForEdge(edge));
6049
6780
  }
6050
6781
  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
6782
+ import_types25.EdgeType.CALLS,
6783
+ import_types25.EdgeType.CONNECTS_TO,
6784
+ import_types25.EdgeType.PUBLISHES_TO,
6785
+ import_types25.EdgeType.CONSUMES_FROM
6055
6786
  ]);
6056
6787
  function detectMissingDivergences(graph, bucket) {
6057
6788
  const out = [];
6058
- if (bucket.type === import_types23.EdgeType.CONTAINS) return out;
6789
+ if (bucket.type === import_types25.EdgeType.CONTAINS) return out;
6059
6790
  if (bucket.extracted && !bucket.observed && OBSERVABLE_EDGE_TYPES.has(bucket.type)) {
6060
6791
  if (!nodeIsFrontier(graph, bucket.target)) {
6061
6792
  out.push({
@@ -6096,7 +6827,7 @@ function declaredHostFor(svc) {
6096
6827
  function hasExtractedConfiguredBy(graph, svcId) {
6097
6828
  for (const edgeId of graph.outboundEdges(svcId)) {
6098
6829
  const e = graph.getEdgeAttributes(edgeId);
6099
- if (e.type === import_types23.EdgeType.CONFIGURED_BY && e.provenance === import_types23.Provenance.EXTRACTED) {
6830
+ if (e.type === import_types25.EdgeType.CONFIGURED_BY && e.provenance === import_types25.Provenance.EXTRACTED) {
6100
6831
  return true;
6101
6832
  }
6102
6833
  }
@@ -6109,10 +6840,10 @@ function detectHostMismatch(graph, svcId, svc) {
6109
6840
  const out = [];
6110
6841
  for (const edgeId of graph.outboundEdges(svcId)) {
6111
6842
  const edge = graph.getEdgeAttributes(edgeId);
6112
- if (edge.type !== import_types23.EdgeType.CONNECTS_TO) continue;
6113
- if (edge.provenance !== import_types23.Provenance.OBSERVED) continue;
6843
+ if (edge.type !== import_types25.EdgeType.CONNECTS_TO) continue;
6844
+ if (edge.provenance !== import_types25.Provenance.OBSERVED) continue;
6114
6845
  const target = graph.getNodeAttributes(edge.target);
6115
- if (target.type !== import_types23.NodeType.DatabaseNode) continue;
6846
+ if (target.type !== import_types25.NodeType.DatabaseNode) continue;
6116
6847
  const observedHost = target.host?.trim();
6117
6848
  if (!observedHost) continue;
6118
6849
  if (observedHost === declaredHost) continue;
@@ -6134,10 +6865,10 @@ function detectCompatDivergences(graph, svcId, svc) {
6134
6865
  const deps = svc.dependencies ?? {};
6135
6866
  for (const edgeId of graph.outboundEdges(svcId)) {
6136
6867
  const edge = graph.getEdgeAttributes(edgeId);
6137
- if (edge.type !== import_types23.EdgeType.CONNECTS_TO) continue;
6138
- if (edge.provenance !== import_types23.Provenance.OBSERVED) continue;
6868
+ if (edge.type !== import_types25.EdgeType.CONNECTS_TO) continue;
6869
+ if (edge.provenance !== import_types25.Provenance.OBSERVED) continue;
6139
6870
  const target = graph.getNodeAttributes(edge.target);
6140
- if (target.type !== import_types23.NodeType.DatabaseNode) continue;
6871
+ if (target.type !== import_types25.NodeType.DatabaseNode) continue;
6141
6872
  for (const pair of compatPairs()) {
6142
6873
  if (pair.engine !== target.engine) continue;
6143
6874
  const declared = deps[pair.driver];
@@ -6196,7 +6927,7 @@ function suppressHostMismatchHalves(all) {
6196
6927
  for (const d of all) {
6197
6928
  if (d.type !== "host-mismatch") continue;
6198
6929
  observedHalf.add(`${d.source}->${d.target}`);
6199
- declaredHalf.add((0, import_types23.databaseId)(d.extractedHost));
6930
+ declaredHalf.add((0, import_types25.databaseId)(d.extractedHost));
6200
6931
  }
6201
6932
  if (observedHalf.size === 0) return all;
6202
6933
  return all.filter((d) => {
@@ -6215,7 +6946,7 @@ function computeDivergences(graph, opts = {}) {
6215
6946
  }
6216
6947
  graph.forEachNode((nodeId, attrs) => {
6217
6948
  const n = attrs;
6218
- if (n.type !== import_types23.NodeType.ServiceNode) return;
6949
+ if (n.type !== import_types25.NodeType.ServiceNode) return;
6219
6950
  const svc = n;
6220
6951
  for (const d of detectHostMismatch(graph, nodeId, svc)) all.push(d);
6221
6952
  for (const d of detectCompatDivergences(graph, nodeId, svc)) all.push(d);
@@ -6249,7 +6980,7 @@ function computeDivergences(graph, opts = {}) {
6249
6980
  if (a.source !== b.source) return a.source.localeCompare(b.source);
6250
6981
  return a.target.localeCompare(b.target);
6251
6982
  });
6252
- return import_types23.DivergenceResultSchema.parse({
6983
+ return import_types25.DivergenceResultSchema.parse({
6253
6984
  divergences: filtered,
6254
6985
  totalAffected: filtered.length,
6255
6986
  computedAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -6259,8 +6990,8 @@ function computeDivergences(graph, opts = {}) {
6259
6990
  // src/persist.ts
6260
6991
  init_cjs_shims();
6261
6992
  var import_node_fs20 = require("fs");
6262
- var import_node_path35 = __toESM(require("path"), 1);
6263
- var import_types24 = require("@neat.is/types");
6993
+ var import_node_path37 = __toESM(require("path"), 1);
6994
+ var import_types26 = require("@neat.is/types");
6264
6995
  var SCHEMA_VERSION = 4;
6265
6996
  function migrateV1ToV2(payload) {
6266
6997
  const nodes = payload.graph.nodes;
@@ -6282,12 +7013,12 @@ function migrateV2ToV3(payload) {
6282
7013
  for (const edge of edges) {
6283
7014
  const attrs = edge.attributes;
6284
7015
  if (!attrs || attrs.provenance !== "FRONTIER") continue;
6285
- attrs.provenance = import_types24.Provenance.OBSERVED;
7016
+ attrs.provenance = import_types26.Provenance.OBSERVED;
6286
7017
  const type = typeof attrs.type === "string" ? attrs.type : void 0;
6287
7018
  const source = typeof attrs.source === "string" ? attrs.source : void 0;
6288
7019
  const target = typeof attrs.target === "string" ? attrs.target : void 0;
6289
7020
  if (type && source && target) {
6290
- const newId = (0, import_types24.observedEdgeId)(source, target, type);
7021
+ const newId = (0, import_types26.observedEdgeId)(source, target, type);
6291
7022
  attrs.id = newId;
6292
7023
  if (edge.key) edge.key = newId;
6293
7024
  }
@@ -6296,7 +7027,7 @@ function migrateV2ToV3(payload) {
6296
7027
  return { ...payload, schemaVersion: 3 };
6297
7028
  }
6298
7029
  async function ensureDir(filePath) {
6299
- await import_node_fs20.promises.mkdir(import_node_path35.default.dirname(filePath), { recursive: true });
7030
+ await import_node_fs20.promises.mkdir(import_node_path37.default.dirname(filePath), { recursive: true });
6300
7031
  }
6301
7032
  async function saveGraphToDisk(graph, outPath) {
6302
7033
  await ensureDir(outPath);
@@ -6378,7 +7109,7 @@ function startPersistLoop(graph, outPath, opts = {}) {
6378
7109
  // src/gitignore.ts
6379
7110
  init_cjs_shims();
6380
7111
  var import_node_fs21 = require("fs");
6381
- var import_node_path36 = __toESM(require("path"), 1);
7112
+ var import_node_path38 = __toESM(require("path"), 1);
6382
7113
  var NEAT_OUT_LINE = "neat-out/";
6383
7114
  var NEAT_HEADER = "# NEAT \u2014 machine-local snapshots and events";
6384
7115
  function isNeatOutLine(line) {
@@ -6386,7 +7117,7 @@ function isNeatOutLine(line) {
6386
7117
  return trimmed === "neat-out/" || trimmed === "neat-out";
6387
7118
  }
6388
7119
  async function ensureNeatOutIgnored(projectDir) {
6389
- const file = import_node_path36.default.join(projectDir, ".gitignore");
7120
+ const file = import_node_path38.default.join(projectDir, ".gitignore");
6390
7121
  let existing = null;
6391
7122
  try {
6392
7123
  existing = await import_node_fs21.promises.readFile(file, "utf8");
@@ -6413,7 +7144,7 @@ ${NEAT_OUT_LINE}
6413
7144
 
6414
7145
  // src/summary.ts
6415
7146
  init_cjs_shims();
6416
- var import_types25 = require("@neat.is/types");
7147
+ var import_types27 = require("@neat.is/types");
6417
7148
  function renderOtelEnvBlock() {
6418
7149
  return [
6419
7150
  "for prod OTel routing, set these in your deploy platform's env:",
@@ -6423,19 +7154,19 @@ function renderOtelEnvBlock() {
6423
7154
  }
6424
7155
  function findIncompatServices(nodes) {
6425
7156
  return nodes.filter(
6426
- (n) => n.type === import_types25.NodeType.ServiceNode && Array.isArray(n.incompatibilities) && (n.incompatibilities ?? []).length > 0
7157
+ (n) => n.type === import_types27.NodeType.ServiceNode && Array.isArray(n.incompatibilities) && (n.incompatibilities ?? []).length > 0
6427
7158
  );
6428
7159
  }
6429
7160
  function servicesWithoutObserved(nodes, edges) {
6430
7161
  const seen = /* @__PURE__ */ new Set();
6431
7162
  for (const e of edges) {
6432
- if (e.provenance === import_types25.Provenance.OBSERVED) {
7163
+ if (e.provenance === import_types27.Provenance.OBSERVED) {
6433
7164
  seen.add(e.source);
6434
7165
  seen.add(e.target);
6435
7166
  }
6436
7167
  }
6437
7168
  return nodes.filter(
6438
- (n) => n.type === import_types25.NodeType.ServiceNode && !seen.has(n.id)
7169
+ (n) => n.type === import_types27.NodeType.ServiceNode && !seen.has(n.id)
6439
7170
  );
6440
7171
  }
6441
7172
  function formatDivergence(d) {
@@ -6510,26 +7241,26 @@ function formatIncompat(inc) {
6510
7241
  // src/watch.ts
6511
7242
  init_cjs_shims();
6512
7243
  var import_node_fs29 = __toESM(require("fs"), 1);
6513
- var import_node_path46 = __toESM(require("path"), 1);
7244
+ var import_node_path48 = __toESM(require("path"), 1);
6514
7245
  var import_chokidar = __toESM(require("chokidar"), 1);
6515
7246
 
6516
7247
  // src/api.ts
6517
7248
  init_cjs_shims();
6518
7249
  var import_fastify = __toESM(require("fastify"), 1);
6519
7250
  var import_cors = __toESM(require("@fastify/cors"), 1);
6520
- var import_types27 = require("@neat.is/types");
7251
+ var import_types29 = require("@neat.is/types");
6521
7252
 
6522
7253
  // src/extend/index.ts
6523
7254
  init_cjs_shims();
6524
7255
  var import_node_fs23 = require("fs");
6525
- var import_node_path38 = __toESM(require("path"), 1);
7256
+ var import_node_path40 = __toESM(require("path"), 1);
6526
7257
  var import_node_os2 = __toESM(require("os"), 1);
6527
7258
  var import_instrumentation_registry = require("@neat.is/instrumentation-registry");
6528
7259
 
6529
7260
  // src/installers/package-manager.ts
6530
7261
  init_cjs_shims();
6531
7262
  var import_node_fs22 = require("fs");
6532
- var import_node_path37 = __toESM(require("path"), 1);
7263
+ var import_node_path39 = __toESM(require("path"), 1);
6533
7264
  var import_node_child_process = require("child_process");
6534
7265
  var LOCKFILE_PRIORITY = [
6535
7266
  { lockfile: "bun.lockb", pm: "bun", args: ["install", "--no-summary"] },
@@ -6551,22 +7282,22 @@ async function exists2(p) {
6551
7282
  }
6552
7283
  }
6553
7284
  async function detectPackageManager(serviceDir) {
6554
- let dir = import_node_path37.default.resolve(serviceDir);
7285
+ let dir = import_node_path39.default.resolve(serviceDir);
6555
7286
  const stops = /* @__PURE__ */ new Set();
6556
7287
  for (let i = 0; i < 64; i++) {
6557
7288
  if (stops.has(dir)) break;
6558
7289
  stops.add(dir);
6559
7290
  for (const candidate of LOCKFILE_PRIORITY) {
6560
- const lockPath = import_node_path37.default.join(dir, candidate.lockfile);
7291
+ const lockPath = import_node_path39.default.join(dir, candidate.lockfile);
6561
7292
  if (await exists2(lockPath)) {
6562
7293
  return { pm: candidate.pm, cwd: dir, args: [...candidate.args] };
6563
7294
  }
6564
7295
  }
6565
- const parent = import_node_path37.default.dirname(dir);
7296
+ const parent = import_node_path39.default.dirname(dir);
6566
7297
  if (parent === dir) break;
6567
7298
  dir = parent;
6568
7299
  }
6569
- return { pm: "npm", cwd: import_node_path37.default.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
7300
+ return { pm: "npm", cwd: import_node_path39.default.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
6570
7301
  }
6571
7302
  async function runPackageManagerInstall(cmd) {
6572
7303
  return new Promise((resolve) => {
@@ -6615,7 +7346,7 @@ async function fileExists2(p) {
6615
7346
  }
6616
7347
  }
6617
7348
  async function readPackageJson(scanPath) {
6618
- const pkgPath = import_node_path38.default.join(scanPath, "package.json");
7349
+ const pkgPath = import_node_path40.default.join(scanPath, "package.json");
6619
7350
  const raw = await import_node_fs23.promises.readFile(pkgPath, "utf8");
6620
7351
  return JSON.parse(raw);
6621
7352
  }
@@ -6626,11 +7357,11 @@ async function findHookFiles(scanPath) {
6626
7357
  ).sort();
6627
7358
  }
6628
7359
  function extendLogPath() {
6629
- return process.env.NEAT_EXTEND_LOG ?? import_node_path38.default.join(import_node_os2.default.homedir(), ".neat", "extend-log.ndjson");
7360
+ return process.env.NEAT_EXTEND_LOG ?? import_node_path40.default.join(import_node_os2.default.homedir(), ".neat", "extend-log.ndjson");
6630
7361
  }
6631
7362
  async function appendExtendLog(entry2) {
6632
7363
  const logPath = extendLogPath();
6633
- await import_node_fs23.promises.mkdir(import_node_path38.default.dirname(logPath), { recursive: true });
7364
+ await import_node_fs23.promises.mkdir(import_node_path40.default.dirname(logPath), { recursive: true });
6634
7365
  await import_node_fs23.promises.appendFile(logPath, JSON.stringify(entry2) + "\n", "utf8");
6635
7366
  }
6636
7367
  function splicedContent(fileContent, snippet2) {
@@ -6689,7 +7420,7 @@ function lookupInstrumentation(library, installedVersion) {
6689
7420
  }
6690
7421
  async function describeProjectInstrumentation(ctx) {
6691
7422
  const hookFiles = await findHookFiles(ctx.scanPath);
6692
- const envNeat = await fileExists2(import_node_path38.default.join(ctx.scanPath, ".env.neat"));
7423
+ const envNeat = await fileExists2(import_node_path40.default.join(ctx.scanPath, ".env.neat"));
6693
7424
  const registryInstrPackages = new Set(
6694
7425
  (0, import_instrumentation_registry.list)().map((e) => e.instrumentation_package).filter((p) => !!p)
6695
7426
  );
@@ -6711,16 +7442,16 @@ async function applyExtension(ctx, args, options) {
6711
7442
  );
6712
7443
  }
6713
7444
  for (const file of hookFiles) {
6714
- const content = await import_node_fs23.promises.readFile(import_node_path38.default.join(ctx.scanPath, file), "utf8");
7445
+ const content = await import_node_fs23.promises.readFile(import_node_path40.default.join(ctx.scanPath, file), "utf8");
6715
7446
  if (content.includes(args.registration_snippet)) {
6716
7447
  return { library: args.library, filesTouched: [], depsAdded: [], installOutput: "", alreadyApplied: true };
6717
7448
  }
6718
7449
  }
6719
7450
  const primaryFile = hookFiles[0];
6720
- const primaryPath = import_node_path38.default.join(ctx.scanPath, primaryFile);
7451
+ const primaryPath = import_node_path40.default.join(ctx.scanPath, primaryFile);
6721
7452
  const filesTouched = [];
6722
7453
  const depsAdded = [];
6723
- const pkgPath = import_node_path38.default.join(ctx.scanPath, "package.json");
7454
+ const pkgPath = import_node_path40.default.join(ctx.scanPath, "package.json");
6724
7455
  const pkg = await readPackageJson(ctx.scanPath);
6725
7456
  if (!(pkg.dependencies ?? {})[args.instrumentation_package]) {
6726
7457
  pkg.dependencies = { ...pkg.dependencies ?? {}, [args.instrumentation_package]: args.version };
@@ -6766,7 +7497,7 @@ async function dryRunExtension(ctx, args) {
6766
7497
  };
6767
7498
  }
6768
7499
  for (const file of hookFiles) {
6769
- const content = await import_node_fs23.promises.readFile(import_node_path38.default.join(ctx.scanPath, file), "utf8");
7500
+ const content = await import_node_fs23.promises.readFile(import_node_path40.default.join(ctx.scanPath, file), "utf8");
6770
7501
  if (content.includes(args.registration_snippet)) {
6771
7502
  return {
6772
7503
  library: args.library,
@@ -6788,7 +7519,7 @@ async function dryRunExtension(ctx, args) {
6788
7519
  depsToAdd.push(`${args.instrumentation_package}@${args.version}`);
6789
7520
  filesTouched.push("package.json");
6790
7521
  }
6791
- const hookContent = await import_node_fs23.promises.readFile(import_node_path38.default.join(ctx.scanPath, primaryFile), "utf8");
7522
+ const hookContent = await import_node_fs23.promises.readFile(import_node_path40.default.join(ctx.scanPath, primaryFile), "utf8");
6792
7523
  const patched = splicedContent(hookContent, args.registration_snippet);
6793
7524
  if (patched) {
6794
7525
  filesTouched.push(primaryFile);
@@ -6809,7 +7540,7 @@ async function rollbackExtension(ctx, args) {
6809
7540
  if (!match) {
6810
7541
  return { undone: false, message: "no apply found for library" };
6811
7542
  }
6812
- const pkgPath = import_node_path38.default.join(ctx.scanPath, "package.json");
7543
+ const pkgPath = import_node_path40.default.join(ctx.scanPath, "package.json");
6813
7544
  if (await fileExists2(pkgPath)) {
6814
7545
  const pkg = await readPackageJson(ctx.scanPath);
6815
7546
  if (pkg.dependencies?.[match.instrumentation_package]) {
@@ -6820,7 +7551,7 @@ async function rollbackExtension(ctx, args) {
6820
7551
  }
6821
7552
  const hookFiles = await findHookFiles(ctx.scanPath);
6822
7553
  for (const file of hookFiles) {
6823
- const filePath = import_node_path38.default.join(ctx.scanPath, file);
7554
+ const filePath = import_node_path40.default.join(ctx.scanPath, file);
6824
7555
  const content = await import_node_fs23.promises.readFile(filePath, "utf8");
6825
7556
  if (content.includes(match.registration_snippet)) {
6826
7557
  const filtered = content.split("\n").filter((line) => !line.includes(match.registration_snippet)).join("\n");
@@ -6913,23 +7644,23 @@ function canonicalJson(value) {
6913
7644
 
6914
7645
  // src/projects.ts
6915
7646
  init_cjs_shims();
6916
- var import_node_path39 = __toESM(require("path"), 1);
7647
+ var import_node_path41 = __toESM(require("path"), 1);
6917
7648
  function pathsForProject(project, baseDir) {
6918
7649
  if (project === DEFAULT_PROJECT) {
6919
7650
  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")
7651
+ snapshotPath: import_node_path41.default.join(baseDir, "graph.json"),
7652
+ errorsPath: import_node_path41.default.join(baseDir, "errors.ndjson"),
7653
+ staleEventsPath: import_node_path41.default.join(baseDir, "stale-events.ndjson"),
7654
+ embeddingsCachePath: import_node_path41.default.join(baseDir, "embeddings.json"),
7655
+ policyViolationsPath: import_node_path41.default.join(baseDir, "policy-violations.ndjson")
6925
7656
  };
6926
7657
  }
6927
7658
  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`)
7659
+ snapshotPath: import_node_path41.default.join(baseDir, `${project}.json`),
7660
+ errorsPath: import_node_path41.default.join(baseDir, `errors.${project}.ndjson`),
7661
+ staleEventsPath: import_node_path41.default.join(baseDir, `stale-events.${project}.ndjson`),
7662
+ embeddingsCachePath: import_node_path41.default.join(baseDir, `embeddings.${project}.json`),
7663
+ policyViolationsPath: import_node_path41.default.join(baseDir, `policy-violations.${project}.ndjson`)
6933
7664
  };
6934
7665
  }
6935
7666
  var Projects = class {
@@ -6967,26 +7698,26 @@ var Projects = class {
6967
7698
  init_cjs_shims();
6968
7699
  var import_node_fs25 = require("fs");
6969
7700
  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");
7701
+ var import_node_path42 = __toESM(require("path"), 1);
7702
+ var import_types28 = require("@neat.is/types");
6972
7703
  var LOCK_TIMEOUT_MS = 5e3;
6973
7704
  var LOCK_RETRY_MS = 50;
6974
7705
  function neatHome() {
6975
7706
  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");
7707
+ if (override && override.length > 0) return import_node_path42.default.resolve(override);
7708
+ return import_node_path42.default.join(import_node_os3.default.homedir(), ".neat");
6978
7709
  }
6979
7710
  function registryPath() {
6980
- return import_node_path40.default.join(neatHome(), "projects.json");
7711
+ return import_node_path42.default.join(neatHome(), "projects.json");
6981
7712
  }
6982
7713
  function registryLockPath() {
6983
- return import_node_path40.default.join(neatHome(), "projects.json.lock");
7714
+ return import_node_path42.default.join(neatHome(), "projects.json.lock");
6984
7715
  }
6985
7716
  function daemonPidPath() {
6986
- return import_node_path40.default.join(neatHome(), "neatd.pid");
7717
+ return import_node_path42.default.join(neatHome(), "neatd.pid");
6987
7718
  }
6988
7719
  function daemonsDir() {
6989
- return import_node_path40.default.join(neatHome(), "daemons");
7720
+ return import_node_path42.default.join(neatHome(), "daemons");
6990
7721
  }
6991
7722
  function isFiniteInt(v) {
6992
7723
  return typeof v === "number" && Number.isFinite(v);
@@ -7027,7 +7758,7 @@ async function discoverDaemons(probe = defaultDiscoveryProbe) {
7027
7758
  const out = [];
7028
7759
  for (const name of names) {
7029
7760
  if (!name.endsWith(".json")) continue;
7030
- const file = import_node_path40.default.join(dir, name);
7761
+ const file = import_node_path42.default.join(dir, name);
7031
7762
  let raw;
7032
7763
  try {
7033
7764
  raw = await import_node_fs25.promises.readFile(file, "utf8");
@@ -7148,7 +7879,7 @@ function lockHolderMessage(holder, lockPath, timeoutMs) {
7148
7879
  }
7149
7880
  }
7150
7881
  async function normalizeProjectPath(input) {
7151
- const resolved = import_node_path40.default.resolve(input);
7882
+ const resolved = import_node_path42.default.resolve(input);
7152
7883
  try {
7153
7884
  return await import_node_fs25.promises.realpath(resolved);
7154
7885
  } catch {
@@ -7156,7 +7887,7 @@ async function normalizeProjectPath(input) {
7156
7887
  }
7157
7888
  }
7158
7889
  async function writeAtomically(target, contents) {
7159
- await import_node_fs25.promises.mkdir(import_node_path40.default.dirname(target), { recursive: true });
7890
+ await import_node_fs25.promises.mkdir(import_node_path42.default.dirname(target), { recursive: true });
7160
7891
  const tmp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
7161
7892
  const fd = await import_node_fs25.promises.open(tmp, "w");
7162
7893
  try {
@@ -7169,7 +7900,7 @@ async function writeAtomically(target, contents) {
7169
7900
  }
7170
7901
  async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaultLockHolderProbe) {
7171
7902
  const deadline = Date.now() + timeoutMs;
7172
- await import_node_fs25.promises.mkdir(import_node_path40.default.dirname(lockPath), { recursive: true });
7903
+ await import_node_fs25.promises.mkdir(import_node_path42.default.dirname(lockPath), { recursive: true });
7173
7904
  let probedHolder = false;
7174
7905
  while (true) {
7175
7906
  try {
@@ -7222,10 +7953,10 @@ async function readRegistry() {
7222
7953
  throw err;
7223
7954
  }
7224
7955
  const parsed = JSON.parse(raw);
7225
- return import_types26.RegistryFileSchema.parse(parsed);
7956
+ return import_types28.RegistryFileSchema.parse(parsed);
7226
7957
  }
7227
7958
  async function writeRegistry(reg) {
7228
- const validated = import_types26.RegistryFileSchema.parse(reg);
7959
+ const validated = import_types28.RegistryFileSchema.parse(reg);
7229
7960
  await writeAtomically(registryPath(), JSON.stringify(validated, null, 2) + "\n");
7230
7961
  }
7231
7962
  var ProjectNameCollisionError = class extends Error {
@@ -7557,11 +8288,11 @@ function registerRoutes(scope, ctx) {
7557
8288
  const candidates = req.query.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
7558
8289
  const parsed = [];
7559
8290
  for (const c of candidates) {
7560
- const r = import_types27.DivergenceTypeSchema.safeParse(c);
8291
+ const r = import_types29.DivergenceTypeSchema.safeParse(c);
7561
8292
  if (!r.success) {
7562
8293
  return reply.code(400).send({
7563
8294
  error: `unknown divergence type "${c}"`,
7564
- allowed: import_types27.DivergenceTypeSchema.options
8295
+ allowed: import_types29.DivergenceTypeSchema.options
7565
8296
  });
7566
8297
  }
7567
8298
  parsed.push(r.data);
@@ -7780,7 +8511,7 @@ function registerRoutes(scope, ctx) {
7780
8511
  const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
7781
8512
  let violations = await log.readAll();
7782
8513
  if (req.query.severity) {
7783
- const sev = import_types27.PolicySeveritySchema.safeParse(req.query.severity);
8514
+ const sev = import_types29.PolicySeveritySchema.safeParse(req.query.severity);
7784
8515
  if (!sev.success) {
7785
8516
  return reply.code(400).send({
7786
8517
  error: "invalid severity",
@@ -7819,7 +8550,7 @@ function registerRoutes(scope, ctx) {
7819
8550
  scope.post("/policies/check", async (req, reply) => {
7820
8551
  const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
7821
8552
  if (!proj) return;
7822
- const parsed = import_types27.PoliciesCheckBodySchema.safeParse(req.body ?? {});
8553
+ const parsed = import_types29.PoliciesCheckBodySchema.safeParse(req.body ?? {});
7823
8554
  if (!parsed.success) {
7824
8555
  return reply.code(400).send({
7825
8556
  error: "invalid /policies/check body",
@@ -8082,7 +8813,7 @@ init_otel();
8082
8813
  // src/daemon.ts
8083
8814
  init_cjs_shims();
8084
8815
  var import_node_fs27 = require("fs");
8085
- var import_node_path44 = __toESM(require("path"), 1);
8816
+ var import_node_path46 = __toESM(require("path"), 1);
8086
8817
  var import_node_module = require("module");
8087
8818
  init_otel();
8088
8819
  init_auth();
@@ -8090,28 +8821,28 @@ init_auth();
8090
8821
  // src/unrouted.ts
8091
8822
  init_cjs_shims();
8092
8823
  var import_node_fs26 = require("fs");
8093
- var import_node_path43 = __toESM(require("path"), 1);
8824
+ var import_node_path45 = __toESM(require("path"), 1);
8094
8825
 
8095
8826
  // src/daemon.ts
8096
- var import_types28 = require("@neat.is/types");
8827
+ var import_types30 = require("@neat.is/types");
8097
8828
  function daemonJsonPath(scanPath) {
8098
- return import_node_path44.default.join(scanPath, "neat-out", "daemon.json");
8829
+ return import_node_path46.default.join(scanPath, "neat-out", "daemon.json");
8099
8830
  }
8100
8831
  function daemonsDiscoveryDir(home) {
8101
8832
  const base = home && home.length > 0 ? home : neatHomeFromEnv();
8102
- return import_node_path44.default.join(base, "daemons");
8833
+ return import_node_path46.default.join(base, "daemons");
8103
8834
  }
8104
8835
  function daemonDiscoveryPath(project, home) {
8105
- return import_node_path44.default.join(daemonsDiscoveryDir(home), `${sanitizeDiscoveryName(project)}.json`);
8836
+ return import_node_path46.default.join(daemonsDiscoveryDir(home), `${sanitizeDiscoveryName(project)}.json`);
8106
8837
  }
8107
8838
  function sanitizeDiscoveryName(project) {
8108
8839
  return project.replace(/[^A-Za-z0-9._-]/g, "_");
8109
8840
  }
8110
8841
  function neatHomeFromEnv() {
8111
8842
  const env = process.env.NEAT_HOME;
8112
- if (env && env.length > 0) return import_node_path44.default.resolve(env);
8843
+ if (env && env.length > 0) return import_node_path46.default.resolve(env);
8113
8844
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
8114
- return import_node_path44.default.join(home, ".neat");
8845
+ return import_node_path46.default.join(home, ".neat");
8115
8846
  }
8116
8847
  async function readDaemonRecord(scanPath) {
8117
8848
  try {
@@ -8182,7 +8913,7 @@ init_otel_grpc();
8182
8913
  // src/search.ts
8183
8914
  init_cjs_shims();
8184
8915
  var import_node_fs28 = require("fs");
8185
- var import_node_path45 = __toESM(require("path"), 1);
8916
+ var import_node_path47 = __toESM(require("path"), 1);
8186
8917
  var import_node_crypto3 = require("crypto");
8187
8918
  var DEFAULT_LIMIT = 10;
8188
8919
  var NOMIC_DIM = 768;
@@ -8217,6 +8948,18 @@ function embedText(node) {
8217
8948
  if (filePath) parts.push(`path=${filePath}`);
8218
8949
  break;
8219
8950
  }
8951
+ case "RouteNode": {
8952
+ const method = node.method;
8953
+ const tmpl = node.pathTemplate;
8954
+ if (method) parts.push(`method=${method}`);
8955
+ if (tmpl) parts.push(`path=${tmpl}`);
8956
+ break;
8957
+ }
8958
+ case "GraphQLOperationNode": {
8959
+ const opType = node.operationType;
8960
+ if (opType) parts.push(`operationType=${opType}`);
8961
+ break;
8962
+ }
8220
8963
  default:
8221
8964
  break;
8222
8965
  }
@@ -8321,7 +9064,7 @@ async function readCache(cachePath) {
8321
9064
  }
8322
9065
  }
8323
9066
  async function writeCache(cachePath, cache) {
8324
- await import_node_fs28.promises.mkdir(import_node_path45.default.dirname(cachePath), { recursive: true });
9067
+ await import_node_fs28.promises.mkdir(import_node_path47.default.dirname(cachePath), { recursive: true });
8325
9068
  await import_node_fs28.promises.writeFile(cachePath, JSON.stringify(cache));
8326
9069
  }
8327
9070
  var VectorIndex = class {
@@ -8479,8 +9222,8 @@ var ALL_PHASES = [
8479
9222
  ];
8480
9223
  function classifyChange(relPath) {
8481
9224
  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());
9225
+ const base = import_node_path48.default.basename(relPath).toLowerCase();
9226
+ const segments = relPath.split(import_node_path48.default.sep).map((s) => s.toLowerCase());
8484
9227
  if (base === "package.json" || base === "requirements.txt" || base === "pyproject.toml" || base === "setup.py") {
8485
9228
  phases.add("services");
8486
9229
  phases.add("aliases");
@@ -8608,9 +9351,9 @@ function countWatchableDirs(scanPath, limit) {
8608
9351
  for (const e of entries) {
8609
9352
  if (count >= limit) return;
8610
9353
  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;
9354
+ if (IGNORED_WATCH_PATHS.some((re) => re.test(import_node_path48.default.join(dir, e.name) + import_node_path48.default.sep))) continue;
8612
9355
  count++;
8613
- if (depth < 2) visit(import_node_path46.default.join(dir, e.name), depth + 1);
9356
+ if (depth < 2) visit(import_node_path48.default.join(dir, e.name), depth + 1);
8614
9357
  }
8615
9358
  };
8616
9359
  visit(scanPath, 0);
@@ -8628,8 +9371,8 @@ async function startWatch(graph, opts) {
8628
9371
  const projectName = opts.project ?? DEFAULT_PROJECT;
8629
9372
  await loadGraphFromDisk(graph, opts.outPath);
8630
9373
  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");
9374
+ const policyFilePath = import_node_path48.default.join(opts.scanPath, "policy.json");
9375
+ const policyViolationsPath = import_node_path48.default.join(import_node_path48.default.dirname(opts.outPath), "policy-violations.ndjson");
8633
9376
  let policies = [];
8634
9377
  try {
8635
9378
  policies = await loadPolicyFile(policyFilePath);
@@ -8680,7 +9423,7 @@ async function startWatch(graph, opts) {
8680
9423
  assertBindAuthority(host, auth.authToken);
8681
9424
  const port = opts.port ?? 8080;
8682
9425
  const otelPort = opts.otelPort ?? 4318;
8683
- const cachePath = opts.embeddingsCachePath ?? import_node_path46.default.join(import_node_path46.default.dirname(opts.outPath), "embeddings.json");
9426
+ const cachePath = opts.embeddingsCachePath ?? import_node_path48.default.join(import_node_path48.default.dirname(opts.outPath), "embeddings.json");
8684
9427
  let searchIndex;
8685
9428
  try {
8686
9429
  searchIndex = await buildSearchIndex(graph, { cachePath });
@@ -8698,7 +9441,7 @@ async function startWatch(graph, opts) {
8698
9441
  // Paths are derived from the explicit options the watch caller passes
8699
9442
  // — pathsForProject is only used to fill in the embeddings/snapshot
8700
9443
  // fields so the registry shape is complete.
8701
- ...pathsForProject(projectName, import_node_path46.default.dirname(opts.outPath)),
9444
+ ...pathsForProject(projectName, import_node_path48.default.dirname(opts.outPath)),
8702
9445
  snapshotPath: opts.outPath,
8703
9446
  errorsPath: opts.errorsPath,
8704
9447
  staleEventsPath: opts.staleEventsPath
@@ -8816,9 +9559,9 @@ async function startWatch(graph, opts) {
8816
9559
  };
8817
9560
  const onPath = (absPath) => {
8818
9561
  if (shouldIgnore(absPath)) return;
8819
- const rel = import_node_path46.default.relative(opts.scanPath, absPath);
9562
+ const rel = import_node_path48.default.relative(opts.scanPath, absPath);
8820
9563
  if (!rel || rel.startsWith("..")) return;
8821
- pendingPaths.add(rel.split(import_node_path46.default.sep).join("/"));
9564
+ pendingPaths.add(rel.split(import_node_path48.default.sep).join("/"));
8822
9565
  const phases = classifyChange(rel);
8823
9566
  if (phases.size === 0) {
8824
9567
  for (const p of ALL_PHASES) pending.add(p);
@@ -8875,7 +9618,7 @@ async function startWatch(graph, opts) {
8875
9618
  // src/deploy/detect.ts
8876
9619
  init_cjs_shims();
8877
9620
  var import_node_fs30 = require("fs");
8878
- var import_node_path47 = __toESM(require("path"), 1);
9621
+ var import_node_path49 = __toESM(require("path"), 1);
8879
9622
  var import_node_child_process2 = require("child_process");
8880
9623
  var import_node_crypto4 = require("crypto");
8881
9624
  function generateToken() {
@@ -8975,7 +9718,7 @@ async function runDeploy(opts = {}) {
8975
9718
  const token = generateToken();
8976
9719
  switch (substrate) {
8977
9720
  case "docker-compose": {
8978
- const artifactPath = import_node_path47.default.join(cwd, "docker-compose.neat.yml");
9721
+ const artifactPath = import_node_path49.default.join(cwd, "docker-compose.neat.yml");
8979
9722
  const contents = emitDockerCompose(cwd);
8980
9723
  await import_node_fs30.promises.writeFile(artifactPath, contents, "utf8");
8981
9724
  return {
@@ -8983,11 +9726,11 @@ async function runDeploy(opts = {}) {
8983
9726
  artifactPath,
8984
9727
  token,
8985
9728
  contents,
8986
- startCommand: `NEAT_AUTH_TOKEN=${token} docker compose -f ${import_node_path47.default.basename(artifactPath)} up -d`
9729
+ startCommand: `NEAT_AUTH_TOKEN=${token} docker compose -f ${import_node_path49.default.basename(artifactPath)} up -d`
8987
9730
  };
8988
9731
  }
8989
9732
  case "systemd": {
8990
- const artifactPath = import_node_path47.default.join(cwd, "neat.service");
9733
+ const artifactPath = import_node_path49.default.join(cwd, "neat.service");
8991
9734
  const contents = emitSystemdUnit(cwd);
8992
9735
  await import_node_fs30.promises.writeFile(artifactPath, contents, "utf8");
8993
9736
  return {
@@ -9023,7 +9766,7 @@ init_cjs_shims();
9023
9766
  // src/installers/javascript.ts
9024
9767
  init_cjs_shims();
9025
9768
  var import_node_fs31 = require("fs");
9026
- var import_node_path48 = __toESM(require("path"), 1);
9769
+ var import_node_path50 = __toESM(require("path"), 1);
9027
9770
  var import_semver2 = __toESM(require("semver"), 1);
9028
9771
 
9029
9772
  // src/installers/templates.ts
@@ -9619,11 +10362,11 @@ var OTEL_ENV = {
9619
10362
  value: "http://localhost:4318/projects/<project>/v1/traces"
9620
10363
  };
9621
10364
  function serviceNodeName(pkg, serviceDir) {
9622
- return pkg.name ?? import_node_path48.default.basename(serviceDir);
10365
+ return pkg.name ?? import_node_path50.default.basename(serviceDir);
9623
10366
  }
9624
10367
  function projectToken(pkg, serviceDir, project) {
9625
10368
  if (project && project.length > 0) return project;
9626
- return pkg.name ?? import_node_path48.default.basename(serviceDir);
10369
+ return pkg.name ?? import_node_path50.default.basename(serviceDir);
9627
10370
  }
9628
10371
  async function readJsonFile(p) {
9629
10372
  try {
@@ -9636,16 +10379,16 @@ async function readJsonFile(p) {
9636
10379
  async function detectRuntimeKind(pkgRoot, pkg) {
9637
10380
  const deps = allDeps(pkg);
9638
10381
  if ("react-native" in deps || "expo" in deps) return "react-native";
9639
- const appJson = await readJsonFile(import_node_path48.default.join(pkgRoot, "app.json"));
10382
+ const appJson = await readJsonFile(import_node_path50.default.join(pkgRoot, "app.json"));
9640
10383
  if (appJson && typeof appJson === "object" && "expo" in appJson) {
9641
10384
  return "react-native";
9642
10385
  }
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) {
10386
+ if (await exists3(import_node_path50.default.join(pkgRoot, "vite.config.js")) || await exists3(import_node_path50.default.join(pkgRoot, "vite.config.ts")) || await exists3(import_node_path50.default.join(pkgRoot, "vite.config.mjs")) || "vite" in deps) {
9644
10387
  return "browser-bundle";
9645
10388
  }
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"))) {
10389
+ if (await exists3(import_node_path50.default.join(pkgRoot, "wrangler.toml"))) return "cloudflare-workers";
10390
+ if (await exists3(import_node_path50.default.join(pkgRoot, "bun.lockb"))) return "bun";
10391
+ if (await exists3(import_node_path50.default.join(pkgRoot, "deno.json")) || await exists3(import_node_path50.default.join(pkgRoot, "deno.lock"))) {
9649
10392
  return "deno";
9650
10393
  }
9651
10394
  const engines = pkg.engines ?? {};
@@ -9654,7 +10397,7 @@ async function detectRuntimeKind(pkgRoot, pkg) {
9654
10397
  }
9655
10398
  async function readPackageJson2(serviceDir) {
9656
10399
  try {
9657
- const raw = await import_node_fs31.promises.readFile(import_node_path48.default.join(serviceDir, "package.json"), "utf8");
10400
+ const raw = await import_node_fs31.promises.readFile(import_node_path50.default.join(serviceDir, "package.json"), "utf8");
9658
10401
  return JSON.parse(raw);
9659
10402
  } catch {
9660
10403
  return null;
@@ -9698,7 +10441,7 @@ function needsVersionUpgrade(installed, expected) {
9698
10441
  var NEXT_CONFIG_CANDIDATES = ["next.config.js", "next.config.ts", "next.config.mjs"];
9699
10442
  async function findNextConfig(serviceDir) {
9700
10443
  for (const name of NEXT_CONFIG_CANDIDATES) {
9701
- const candidate = import_node_path48.default.join(serviceDir, name);
10444
+ const candidate = import_node_path50.default.join(serviceDir, name);
9702
10445
  if (await exists3(candidate)) return candidate;
9703
10446
  }
9704
10447
  return null;
@@ -9767,7 +10510,7 @@ function hasRemixDependency(pkg) {
9767
10510
  }
9768
10511
  async function findRemixEntry(serviceDir) {
9769
10512
  for (const rel of REMIX_ENTRY_CANDIDATES) {
9770
- const candidate = import_node_path48.default.join(serviceDir, rel);
10513
+ const candidate = import_node_path50.default.join(serviceDir, rel);
9771
10514
  if (await exists3(candidate)) return candidate;
9772
10515
  }
9773
10516
  return null;
@@ -9779,14 +10522,14 @@ function hasSvelteKitDependency(pkg) {
9779
10522
  }
9780
10523
  async function findSvelteKitHooks(serviceDir) {
9781
10524
  for (const rel of SVELTEKIT_HOOKS_CANDIDATES) {
9782
- const candidate = import_node_path48.default.join(serviceDir, rel);
10525
+ const candidate = import_node_path50.default.join(serviceDir, rel);
9783
10526
  if (await exists3(candidate)) return candidate;
9784
10527
  }
9785
10528
  return null;
9786
10529
  }
9787
10530
  async function findSvelteKitConfig(serviceDir) {
9788
10531
  for (const rel of SVELTEKIT_CONFIG_CANDIDATES) {
9789
- const candidate = import_node_path48.default.join(serviceDir, rel);
10532
+ const candidate = import_node_path50.default.join(serviceDir, rel);
9790
10533
  if (await exists3(candidate)) return candidate;
9791
10534
  }
9792
10535
  return null;
@@ -9797,7 +10540,7 @@ function hasNuxtDependency(pkg) {
9797
10540
  }
9798
10541
  async function findNuxtConfig(serviceDir) {
9799
10542
  for (const name of NUXT_CONFIG_CANDIDATES) {
9800
- const candidate = import_node_path48.default.join(serviceDir, name);
10543
+ const candidate = import_node_path50.default.join(serviceDir, name);
9801
10544
  if (await exists3(candidate)) return candidate;
9802
10545
  }
9803
10546
  return null;
@@ -9808,7 +10551,7 @@ function hasAstroDependency(pkg) {
9808
10551
  }
9809
10552
  async function findAstroConfig(serviceDir) {
9810
10553
  for (const name of ASTRO_CONFIG_CANDIDATES) {
9811
- const candidate = import_node_path48.default.join(serviceDir, name);
10554
+ const candidate = import_node_path50.default.join(serviceDir, name);
9812
10555
  if (await exists3(candidate)) return candidate;
9813
10556
  }
9814
10557
  return null;
@@ -9822,7 +10565,7 @@ function parseNextMajor(range) {
9822
10565
  return Number.isFinite(n) ? n : null;
9823
10566
  }
9824
10567
  async function isTypeScriptProject(serviceDir) {
9825
- return exists3(import_node_path48.default.join(serviceDir, "tsconfig.json"));
10568
+ return exists3(import_node_path50.default.join(serviceDir, "tsconfig.json"));
9826
10569
  }
9827
10570
  var INDEX_EXTENSIONS = [".ts", ".tsx", ".js", ".mjs", ".cjs"];
9828
10571
  var INDEX_CANDIDATES = INDEX_EXTENSIONS.map((ext) => `index${ext}`);
@@ -9871,7 +10614,7 @@ function entryFromScript(script) {
9871
10614
  }
9872
10615
  async function resolveEntry(serviceDir, pkg) {
9873
10616
  if (typeof pkg.main === "string" && pkg.main.length > 0) {
9874
- const candidate = import_node_path48.default.resolve(serviceDir, pkg.main);
10617
+ const candidate = import_node_path50.default.resolve(serviceDir, pkg.main);
9875
10618
  if (await exists3(candidate)) return candidate;
9876
10619
  }
9877
10620
  if (pkg.bin) {
@@ -9885,40 +10628,40 @@ async function resolveEntry(serviceDir, pkg) {
9885
10628
  if (typeof first === "string") binEntry = first;
9886
10629
  }
9887
10630
  if (binEntry) {
9888
- const candidate = import_node_path48.default.resolve(serviceDir, binEntry);
10631
+ const candidate = import_node_path50.default.resolve(serviceDir, binEntry);
9889
10632
  if (await exists3(candidate)) return candidate;
9890
10633
  }
9891
10634
  }
9892
10635
  const startEntry = entryFromScript(pkg.scripts?.start);
9893
10636
  if (startEntry) {
9894
- const candidate = import_node_path48.default.resolve(serviceDir, startEntry);
10637
+ const candidate = import_node_path50.default.resolve(serviceDir, startEntry);
9895
10638
  if (await exists3(candidate)) return candidate;
9896
10639
  }
9897
10640
  const devEntry = entryFromScript(pkg.scripts?.dev);
9898
10641
  if (devEntry) {
9899
- const candidate = import_node_path48.default.resolve(serviceDir, devEntry);
10642
+ const candidate = import_node_path50.default.resolve(serviceDir, devEntry);
9900
10643
  if (await exists3(candidate)) return candidate;
9901
10644
  }
9902
10645
  for (const rel of SRC_INDEX_CANDIDATES) {
9903
- const candidate = import_node_path48.default.join(serviceDir, rel);
10646
+ const candidate = import_node_path50.default.join(serviceDir, rel);
9904
10647
  if (await exists3(candidate)) return candidate;
9905
10648
  }
9906
10649
  for (const rel of SRC_NAMED_CANDIDATES) {
9907
- const candidate = import_node_path48.default.join(serviceDir, rel);
10650
+ const candidate = import_node_path50.default.join(serviceDir, rel);
9908
10651
  if (await exists3(candidate)) return candidate;
9909
10652
  }
9910
10653
  for (const rel of ROOT_NAMED_CANDIDATES) {
9911
- const candidate = import_node_path48.default.join(serviceDir, rel);
10654
+ const candidate = import_node_path50.default.join(serviceDir, rel);
9912
10655
  if (await exists3(candidate)) return candidate;
9913
10656
  }
9914
10657
  for (const name of INDEX_CANDIDATES) {
9915
- const candidate = import_node_path48.default.join(serviceDir, name);
10658
+ const candidate = import_node_path50.default.join(serviceDir, name);
9916
10659
  if (await exists3(candidate)) return candidate;
9917
10660
  }
9918
10661
  return null;
9919
10662
  }
9920
10663
  function dispatchEntry(entryFile, pkg) {
9921
- const ext = import_node_path48.default.extname(entryFile).toLowerCase();
10664
+ const ext = import_node_path50.default.extname(entryFile).toLowerCase();
9922
10665
  if (ext === ".ts" || ext === ".tsx") return "ts";
9923
10666
  if (ext === ".mjs") return "esm";
9924
10667
  if (ext === ".cjs") return "cjs";
@@ -9935,9 +10678,9 @@ function otelInitContents(flavor) {
9935
10678
  return OTEL_INIT_CJS;
9936
10679
  }
9937
10680
  function injectionLine(flavor, entryFile, otelInitFile) {
9938
- let rel = import_node_path48.default.relative(import_node_path48.default.dirname(entryFile), otelInitFile);
10681
+ let rel = import_node_path50.default.relative(import_node_path50.default.dirname(entryFile), otelInitFile);
9939
10682
  if (!rel.startsWith(".")) rel = `./${rel}`;
9940
- rel = rel.split(import_node_path48.default.sep).join("/");
10683
+ rel = rel.split(import_node_path50.default.sep).join("/");
9941
10684
  if (flavor === "cjs") return `require('${rel}')`;
9942
10685
  if (flavor === "esm") return `import '${rel}'`;
9943
10686
  const tsRel = rel.replace(/\.ts$/, "");
@@ -9950,23 +10693,23 @@ function lineIsOtelInjection(line) {
9950
10693
  }
9951
10694
  async function detectsSrcLayout(serviceDir) {
9952
10695
  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"))
10696
+ exists3(import_node_path50.default.join(serviceDir, "src", "app")),
10697
+ exists3(import_node_path50.default.join(serviceDir, "src", "pages")),
10698
+ exists3(import_node_path50.default.join(serviceDir, "app")),
10699
+ exists3(import_node_path50.default.join(serviceDir, "pages"))
9957
10700
  ]);
9958
10701
  return (hasSrcApp || hasSrcPages) && !hasRootApp && !hasRootPages;
9959
10702
  }
9960
10703
  async function planNext(serviceDir, pkg, manifestPath, nextConfigPath, project) {
9961
10704
  const useTs = await isTypeScriptProject(serviceDir);
9962
10705
  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(
10706
+ const baseDir = srcLayout ? import_node_path50.default.join(serviceDir, "src") : serviceDir;
10707
+ const instrumentationFile = import_node_path50.default.join(baseDir, useTs ? "instrumentation.ts" : "instrumentation.js");
10708
+ const instrumentationNodeFile = import_node_path50.default.join(
9966
10709
  baseDir,
9967
10710
  useTs ? "instrumentation.node.ts" : "instrumentation.node.js"
9968
10711
  );
9969
- const envNeatFile = import_node_path48.default.join(baseDir, ".env.neat");
10712
+ const envNeatFile = import_node_path50.default.join(baseDir, ".env.neat");
9970
10713
  const existingDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
9971
10714
  const dependencyEdits = [];
9972
10715
  for (const sdk of SDK_PACKAGES) {
@@ -10071,7 +10814,7 @@ function buildDependencyEdits(pkg, manifestPath) {
10071
10814
  return edits;
10072
10815
  }
10073
10816
  async function queueEnvNeat(serviceDir, pkg, project, generatedFiles) {
10074
- const envNeatFile = import_node_path48.default.join(serviceDir, ".env.neat");
10817
+ const envNeatFile = import_node_path50.default.join(serviceDir, ".env.neat");
10075
10818
  if (!await exists3(envNeatFile)) {
10076
10819
  generatedFiles.push({
10077
10820
  file: envNeatFile,
@@ -10106,7 +10849,7 @@ function fileImportsOtelHook(raw, specifiers) {
10106
10849
  }
10107
10850
  async function planRemix(serviceDir, pkg, manifestPath, entryFile, project) {
10108
10851
  const useTs = await isTypeScriptProject(serviceDir);
10109
- const otelServerFile = import_node_path48.default.join(
10852
+ const otelServerFile = import_node_path50.default.join(
10110
10853
  serviceDir,
10111
10854
  useTs ? "app/otel.server.ts" : "app/otel.server.js"
10112
10855
  );
@@ -10163,11 +10906,11 @@ async function planRemix(serviceDir, pkg, manifestPath, entryFile, project) {
10163
10906
  }
10164
10907
  async function planSvelteKit(serviceDir, pkg, manifestPath, hooksFile, project) {
10165
10908
  const useTs = await isTypeScriptProject(serviceDir);
10166
- const otelInitFile = import_node_path48.default.join(
10909
+ const otelInitFile = import_node_path50.default.join(
10167
10910
  serviceDir,
10168
10911
  useTs ? "src/otel-init.ts" : "src/otel-init.js"
10169
10912
  );
10170
- const resolvedHooksFile = hooksFile ?? import_node_path48.default.join(serviceDir, useTs ? "src/hooks.server.ts" : "src/hooks.server.js");
10913
+ const resolvedHooksFile = hooksFile ?? import_node_path50.default.join(serviceDir, useTs ? "src/hooks.server.ts" : "src/hooks.server.js");
10171
10914
  const dependencyEdits = buildDependencyEdits(pkg, manifestPath);
10172
10915
  const generatedFiles = [];
10173
10916
  const entrypointEdits = [];
@@ -10229,11 +10972,11 @@ async function planSvelteKit(serviceDir, pkg, manifestPath, hooksFile, project)
10229
10972
  }
10230
10973
  async function planNuxt(serviceDir, pkg, manifestPath, project) {
10231
10974
  const useTs = await isTypeScriptProject(serviceDir);
10232
- const otelPluginFile = import_node_path48.default.join(
10975
+ const otelPluginFile = import_node_path50.default.join(
10233
10976
  serviceDir,
10234
10977
  useTs ? "server/plugins/otel.ts" : "server/plugins/otel.js"
10235
10978
  );
10236
- const otelInitFile = import_node_path48.default.join(
10979
+ const otelInitFile = import_node_path50.default.join(
10237
10980
  serviceDir,
10238
10981
  useTs ? "server/plugins/otel-init.ts" : "server/plugins/otel-init.js"
10239
10982
  );
@@ -10284,19 +11027,19 @@ async function planNuxt(serviceDir, pkg, manifestPath, project) {
10284
11027
  var ASTRO_MIDDLEWARE_CANDIDATES = ["src/middleware.ts", "src/middleware.js"];
10285
11028
  async function findAstroMiddleware(serviceDir) {
10286
11029
  for (const rel of ASTRO_MIDDLEWARE_CANDIDATES) {
10287
- const candidate = import_node_path48.default.join(serviceDir, rel);
11030
+ const candidate = import_node_path50.default.join(serviceDir, rel);
10288
11031
  if (await exists3(candidate)) return candidate;
10289
11032
  }
10290
11033
  return null;
10291
11034
  }
10292
11035
  async function planAstro(serviceDir, pkg, manifestPath, project) {
10293
11036
  const useTs = await isTypeScriptProject(serviceDir);
10294
- const otelInitFile = import_node_path48.default.join(
11037
+ const otelInitFile = import_node_path50.default.join(
10295
11038
  serviceDir,
10296
11039
  useTs ? "src/otel-init.ts" : "src/otel-init.js"
10297
11040
  );
10298
11041
  const existingMiddleware = await findAstroMiddleware(serviceDir);
10299
- const middlewareFile = existingMiddleware ?? import_node_path48.default.join(serviceDir, useTs ? "src/middleware.ts" : "src/middleware.js");
11042
+ const middlewareFile = existingMiddleware ?? import_node_path50.default.join(serviceDir, useTs ? "src/middleware.ts" : "src/middleware.js");
10300
11043
  const dependencyEdits = buildDependencyEdits(pkg, manifestPath);
10301
11044
  const generatedFiles = [];
10302
11045
  const entrypointEdits = [];
@@ -10392,7 +11135,7 @@ async function findFrameworkDispatch(serviceDir, pkg, manifestPath, project) {
10392
11135
  }
10393
11136
  async function plan(serviceDir, opts) {
10394
11137
  const pkg = await readPackageJson2(serviceDir);
10395
- const manifestPath = import_node_path48.default.join(serviceDir, "package.json");
11138
+ const manifestPath = import_node_path50.default.join(serviceDir, "package.json");
10396
11139
  const project = opts?.project;
10397
11140
  const empty = {
10398
11141
  language: "javascript",
@@ -10427,8 +11170,8 @@ async function plan(serviceDir, opts) {
10427
11170
  return { ...empty, libOnly: true };
10428
11171
  }
10429
11172
  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");
11173
+ const otelInitFile = import_node_path50.default.join(import_node_path50.default.dirname(entryFile), otelInitFilename(flavor));
11174
+ const envNeatFile = import_node_path50.default.join(serviceDir, ".env.neat");
10432
11175
  const existingDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
10433
11176
  const dependencyEdits = [];
10434
11177
  for (const sdk of SDK_PACKAGES) {
@@ -10497,13 +11240,13 @@ async function plan(serviceDir, opts) {
10497
11240
  };
10498
11241
  }
10499
11242
  function isAllowedWritePath(serviceDir, target) {
10500
- const rel = import_node_path48.default.relative(serviceDir, target);
11243
+ const rel = import_node_path50.default.relative(serviceDir, target);
10501
11244
  if (rel.startsWith("..")) return false;
10502
- const base = import_node_path48.default.basename(target);
11245
+ const base = import_node_path50.default.basename(target);
10503
11246
  if (base === "package.json") return true;
10504
11247
  if (base === ".env.neat") return true;
10505
11248
  if (/^otel-init\.(?:js|cjs|mjs|ts)$/.test(base)) return true;
10506
- const relPosix = rel.split(import_node_path48.default.sep).join("/");
11249
+ const relPosix = rel.split(import_node_path50.default.sep).join("/");
10507
11250
  if (/^instrumentation(?:\.node)?\.(?:js|cjs|mjs|ts)$/.test(base)) {
10508
11251
  if (relPosix === base) return true;
10509
11252
  if (relPosix === `src/${base}`) return true;
@@ -10520,7 +11263,7 @@ function isAllowedWritePath(serviceDir, target) {
10520
11263
  return false;
10521
11264
  }
10522
11265
  async function writeAtomic(file, contents) {
10523
- await import_node_fs31.promises.mkdir(import_node_path48.default.dirname(file), { recursive: true });
11266
+ await import_node_fs31.promises.mkdir(import_node_path50.default.dirname(file), { recursive: true });
10524
11267
  const tmp = `${file}.${process.pid}.${Date.now()}.tmp`;
10525
11268
  await import_node_fs31.promises.writeFile(tmp, contents, "utf8");
10526
11269
  await import_node_fs31.promises.rename(tmp, file);
@@ -10704,7 +11447,7 @@ async function rollback(installPlan, originals, createdFiles) {
10704
11447
  ...removed.map((f) => `removed: ${f}`),
10705
11448
  ""
10706
11449
  ];
10707
- const rollbackPath = import_node_path48.default.join(installPlan.serviceDir, "neat-rollback.patch");
11450
+ const rollbackPath = import_node_path50.default.join(installPlan.serviceDir, "neat-rollback.patch");
10708
11451
  await import_node_fs31.promises.writeFile(rollbackPath, lines.join("\n"), "utf8");
10709
11452
  }
10710
11453
  function injectInstrumentationHook(raw) {
@@ -10735,7 +11478,7 @@ var javascriptInstaller = {
10735
11478
  // src/installers/python.ts
10736
11479
  init_cjs_shims();
10737
11480
  var import_node_fs32 = require("fs");
10738
- var import_node_path49 = __toESM(require("path"), 1);
11481
+ var import_node_path51 = __toESM(require("path"), 1);
10739
11482
  var SDK_PACKAGES2 = [
10740
11483
  { name: "opentelemetry-distro", version: ">=0.49b0" },
10741
11484
  { name: "opentelemetry-exporter-otlp", version: ">=1.28.0" }
@@ -10756,7 +11499,7 @@ async function exists4(p) {
10756
11499
  async function detect2(serviceDir) {
10757
11500
  const markers = ["requirements.txt", "pyproject.toml", "setup.py"];
10758
11501
  for (const m of markers) {
10759
- if (await exists4(import_node_path49.default.join(serviceDir, m))) return true;
11502
+ if (await exists4(import_node_path51.default.join(serviceDir, m))) return true;
10760
11503
  }
10761
11504
  return false;
10762
11505
  }
@@ -10766,7 +11509,7 @@ function reqPackageName(line) {
10766
11509
  return head.replace(/[<>=!~].*$/, "").toLowerCase();
10767
11510
  }
10768
11511
  async function planRequirementsTxtEdits(serviceDir) {
10769
- const file = import_node_path49.default.join(serviceDir, "requirements.txt");
11512
+ const file = import_node_path51.default.join(serviceDir, "requirements.txt");
10770
11513
  if (!await exists4(file)) return null;
10771
11514
  const raw = await import_node_fs32.promises.readFile(file, "utf8");
10772
11515
  const presentNames = new Set(
@@ -10776,7 +11519,7 @@ async function planRequirementsTxtEdits(serviceDir) {
10776
11519
  return { manifest: file, missing: [...missing] };
10777
11520
  }
10778
11521
  async function planProcfileEdits(serviceDir) {
10779
- const procfile = import_node_path49.default.join(serviceDir, "Procfile");
11522
+ const procfile = import_node_path51.default.join(serviceDir, "Procfile");
10780
11523
  if (!await exists4(procfile)) return [];
10781
11524
  const raw = await import_node_fs32.promises.readFile(procfile, "utf8");
10782
11525
  const edits = [];
@@ -10865,7 +11608,7 @@ async function apply2(installPlan) {
10865
11608
  if (raw === void 0) {
10866
11609
  throw new Error(`python installer: cannot read ${file} during apply`);
10867
11610
  }
10868
- const base = import_node_path49.default.basename(file);
11611
+ const base = import_node_path51.default.basename(file);
10869
11612
  if (base === "requirements.txt") {
10870
11613
  const edits = installPlan.dependencyEdits.filter((e) => e.file === file);
10871
11614
  if (edits.length > 0) {
@@ -10904,7 +11647,7 @@ async function rollback2(installPlan, originals) {
10904
11647
  ...restored.map((f) => `restored: ${f}`),
10905
11648
  ""
10906
11649
  ];
10907
- const rollbackPath = import_node_path49.default.join(installPlan.serviceDir, "neat-rollback.patch");
11650
+ const rollbackPath = import_node_path51.default.join(installPlan.serviceDir, "neat-rollback.patch");
10908
11651
  await import_node_fs32.promises.writeFile(rollbackPath, lines.join("\n"), "utf8");
10909
11652
  }
10910
11653
  var pythonInstaller = {
@@ -11031,7 +11774,7 @@ init_cjs_shims();
11031
11774
  var import_node_fs33 = require("fs");
11032
11775
  var import_node_http = __toESM(require("http"), 1);
11033
11776
  var import_node_net = __toESM(require("net"), 1);
11034
- var import_node_path50 = __toESM(require("path"), 1);
11777
+ var import_node_path52 = __toESM(require("path"), 1);
11035
11778
  var import_node_child_process3 = require("child_process");
11036
11779
  var import_node_readline = __toESM(require("readline"), 1);
11037
11780
  async function extractAndPersist(opts) {
@@ -11040,7 +11783,7 @@ async function extractAndPersist(opts) {
11040
11783
  const graphKey = opts.projectExplicit ? opts.project : DEFAULT_PROJECT;
11041
11784
  resetGraph(graphKey);
11042
11785
  const graph = getGraph(graphKey);
11043
- const projectPaths = pathsForProject(graphKey, import_node_path50.default.join(opts.scanPath, "neat-out"));
11786
+ const projectPaths = pathsForProject(graphKey, import_node_path52.default.join(opts.scanPath, "neat-out"));
11044
11787
  const extraction = await extractFromDirectory(graph, opts.scanPath, {
11045
11788
  errorsPath: projectPaths.errorsPath
11046
11789
  });
@@ -11092,7 +11835,7 @@ async function applyInstallersOver(services, project, options = {}) {
11092
11835
  libOnly++;
11093
11836
  const appDeps = svc.pkg ? appFrameworkDependencies(svc.pkg) : [];
11094
11837
  if (appDeps.length > 0) {
11095
- const svcName = import_node_path50.default.basename(svc.dir);
11838
+ const svcName = import_node_path52.default.basename(svc.dir);
11096
11839
  const list = appDeps.join(", ");
11097
11840
  console.warn(
11098
11841
  `neat: runtime layer won't engage for ${svcName}: no entry point found.
@@ -11105,7 +11848,7 @@ async function applyInstallersOver(services, project, options = {}) {
11105
11848
  console.log(`skipping ${svc.dir}: browser bundle; browser-OTel support lands in a future release.`);
11106
11849
  } else if (outcome.outcome === "react-native") {
11107
11850
  reactNative++;
11108
- const svcName = import_node_path50.default.basename(svc.dir);
11851
+ const svcName = import_node_path52.default.basename(svc.dir);
11109
11852
  console.log(
11110
11853
  `neat: ${svc.dir} detected as React Native / Expo
11111
11854
  The installer doesn't cover this runtime deterministically.
@@ -11116,7 +11859,7 @@ async function applyInstallersOver(services, project, options = {}) {
11116
11859
  );
11117
11860
  } else if (outcome.outcome === "bun") {
11118
11861
  bun++;
11119
- const svcName = import_node_path50.default.basename(svc.dir);
11862
+ const svcName = import_node_path52.default.basename(svc.dir);
11120
11863
  console.log(
11121
11864
  `neat: ${svc.dir} detected as Bun
11122
11865
  The installer doesn't cover this runtime deterministically.
@@ -11127,7 +11870,7 @@ async function applyInstallersOver(services, project, options = {}) {
11127
11870
  );
11128
11871
  } else if (outcome.outcome === "deno") {
11129
11872
  deno++;
11130
- const svcName = import_node_path50.default.basename(svc.dir);
11873
+ const svcName = import_node_path52.default.basename(svc.dir);
11131
11874
  console.log(
11132
11875
  `neat: ${svc.dir} detected as Deno
11133
11876
  The installer doesn't cover this runtime deterministically.
@@ -11138,7 +11881,7 @@ async function applyInstallersOver(services, project, options = {}) {
11138
11881
  );
11139
11882
  } else if (outcome.outcome === "cloudflare-workers") {
11140
11883
  cloudflareWorkers++;
11141
- const svcName = import_node_path50.default.basename(svc.dir);
11884
+ const svcName = import_node_path52.default.basename(svc.dir);
11142
11885
  console.log(
11143
11886
  `neat: ${svc.dir} detected as Cloudflare Workers
11144
11887
  The installer doesn't cover this runtime deterministically.
@@ -11149,7 +11892,7 @@ async function applyInstallersOver(services, project, options = {}) {
11149
11892
  );
11150
11893
  } else if (outcome.outcome === "electron") {
11151
11894
  electron++;
11152
- const svcName = import_node_path50.default.basename(svc.dir);
11895
+ const svcName = import_node_path52.default.basename(svc.dir);
11153
11896
  console.log(
11154
11897
  `neat: ${svc.dir} detected as Electron
11155
11898
  The installer doesn't cover this runtime deterministically.
@@ -11162,7 +11905,7 @@ async function applyInstallersOver(services, project, options = {}) {
11162
11905
  if (svc.pkg && (outcome.outcome === "instrumented" || outcome.outcome === "already-instrumented")) {
11163
11906
  const gaps = uninstrumentedLibraries(svc.pkg);
11164
11907
  if (gaps.length > 0) {
11165
- const svcName = import_node_path50.default.basename(svc.dir);
11908
+ const svcName = import_node_path52.default.basename(svc.dir);
11166
11909
  const list = gaps.join(", ");
11167
11910
  const subject = gaps.length === 1 ? "this library" : "these libraries";
11168
11911
  const aux = gaps.length === 1 ? "isn't" : "aren't";
@@ -11368,8 +12111,8 @@ async function persistedPortsFor(scanPath) {
11368
12111
  return { rest: record.ports.rest, otlp: record.ports.otlp, web: record.ports.web };
11369
12112
  }
11370
12113
  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 });
12114
+ const lockPath = import_node_path52.default.join(scanPath, "neat-out", "daemon.spawn.lock");
12115
+ await import_node_fs33.promises.mkdir(import_node_path52.default.dirname(lockPath), { recursive: true });
11373
12116
  const STALE_LOCK_MS = 6e4;
11374
12117
  try {
11375
12118
  const fd = await import_node_fs33.promises.open(lockPath, "wx");
@@ -11414,13 +12157,13 @@ async function healthIsForProject(restPort, project) {
11414
12157
  return false;
11415
12158
  }
11416
12159
  function daemonLogPath(projectPath2) {
11417
- return import_node_path50.default.join(projectPath2, "neat-out", "daemon.log");
12160
+ return import_node_path52.default.join(projectPath2, "neat-out", "daemon.log");
11418
12161
  }
11419
12162
  function spawnDaemonDetached(spec) {
11420
- const here = import_node_path50.default.dirname(new URL(importMetaUrl).pathname);
12163
+ const here = import_node_path52.default.dirname(new URL(importMetaUrl).pathname);
11421
12164
  const candidates = [
11422
- import_node_path50.default.join(here, "neatd.cjs"),
11423
- import_node_path50.default.join(here, "neatd.js")
12165
+ import_node_path52.default.join(here, "neatd.cjs"),
12166
+ import_node_path52.default.join(here, "neatd.js")
11424
12167
  ];
11425
12168
  let entry2 = null;
11426
12169
  const fsSync = require("fs");
@@ -11450,7 +12193,7 @@ function spawnDaemonDetached(spec) {
11450
12193
  let logFd = null;
11451
12194
  if (spec) {
11452
12195
  const logPath = daemonLogPath(spec.projectPath);
11453
- fsSync.mkdirSync(import_node_path50.default.dirname(logPath), { recursive: true });
12196
+ fsSync.mkdirSync(import_node_path52.default.dirname(logPath), { recursive: true });
11454
12197
  logFd = fsSync.openSync(logPath, "a");
11455
12198
  }
11456
12199
  const child = (0, import_node_child_process3.spawn)(process.execPath, [entry2, "start"], {
@@ -11649,7 +12392,7 @@ async function runOrchestrator(opts) {
11649
12392
  result.steps.browser = openBrowser(dashboardUrl);
11650
12393
  }
11651
12394
  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;
12395
+ const daemonLog = daemonRunning ? import_node_path52.default.relative(opts.scanPath, daemonLogPath(opts.scanPath)) : null;
11653
12396
  printSummary(result, graph, dashboardUrl, daemonLog);
11654
12397
  return result;
11655
12398
  }
@@ -11687,11 +12430,11 @@ function printSummary(result, graph, dashboardUrl, daemonLog) {
11687
12430
 
11688
12431
  // src/cli-verbs.ts
11689
12432
  init_cjs_shims();
11690
- var import_node_path51 = __toESM(require("path"), 1);
12433
+ var import_node_path53 = __toESM(require("path"), 1);
11691
12434
 
11692
12435
  // src/cli-client.ts
11693
12436
  init_cjs_shims();
11694
- var import_types29 = require("@neat.is/types");
12437
+ var import_types31 = require("@neat.is/types");
11695
12438
  var HttpError = class extends Error {
11696
12439
  constructor(status2, message, responseBody = "") {
11697
12440
  super(message);
@@ -11716,10 +12459,10 @@ function createHttpClient(baseUrl, bearerToken) {
11716
12459
  const root = baseUrl.replace(/\/$/, "");
11717
12460
  const authHeader = bearerToken && bearerToken.length > 0 ? { authorization: `Bearer ${bearerToken}` } : {};
11718
12461
  return {
11719
- async get(path53) {
12462
+ async get(path55) {
11720
12463
  let res;
11721
12464
  try {
11722
- res = await fetch(`${root}${path53}`, {
12465
+ res = await fetch(`${root}${path55}`, {
11723
12466
  headers: { ...authHeader }
11724
12467
  });
11725
12468
  } catch (err) {
@@ -11731,16 +12474,16 @@ function createHttpClient(baseUrl, bearerToken) {
11731
12474
  const body = await res.text().catch(() => "");
11732
12475
  throw new HttpError(
11733
12476
  res.status,
11734
- `${res.status} ${res.statusText} on GET ${path53}: ${body}`,
12477
+ `${res.status} ${res.statusText} on GET ${path55}: ${body}`,
11735
12478
  body
11736
12479
  );
11737
12480
  }
11738
12481
  return await res.json();
11739
12482
  },
11740
- async post(path53, body) {
12483
+ async post(path55, body) {
11741
12484
  let res;
11742
12485
  try {
11743
- res = await fetch(`${root}${path53}`, {
12486
+ res = await fetch(`${root}${path55}`, {
11744
12487
  method: "POST",
11745
12488
  headers: { "content-type": "application/json", ...authHeader },
11746
12489
  body: JSON.stringify(body)
@@ -11754,7 +12497,7 @@ function createHttpClient(baseUrl, bearerToken) {
11754
12497
  const text = await res.text().catch(() => "");
11755
12498
  throw new HttpError(
11756
12499
  res.status,
11757
- `${res.status} ${res.statusText} on POST ${path53}: ${text}`,
12500
+ `${res.status} ${res.statusText} on POST ${path55}: ${text}`,
11758
12501
  text
11759
12502
  );
11760
12503
  }
@@ -11768,12 +12511,12 @@ function projectPath(project, suffix) {
11768
12511
  }
11769
12512
  async function runRootCause(client, input) {
11770
12513
  const qs = input.errorId ? `?errorId=${encodeURIComponent(input.errorId)}` : "";
11771
- const path53 = projectPath(
12514
+ const path55 = projectPath(
11772
12515
  input.project,
11773
12516
  `/graph/root-cause/${encodeURIComponent(input.errorNode)}${qs}`
11774
12517
  );
11775
12518
  try {
11776
- const result = await client.get(path53);
12519
+ const result = await client.get(path55);
11777
12520
  const arrowPath = result.traversalPath.join(" \u2190 ");
11778
12521
  const provenances = result.edgeProvenances.length ? result.edgeProvenances.join(", ") : "(direct, no edges traversed)";
11779
12522
  const summary = `Root cause for ${input.errorNode} is ${result.rootCauseNode}. ` + result.rootCauseReason + (result.fixRecommendation ? ` Recommended fix: ${result.fixRecommendation}.` : "");
@@ -11799,12 +12542,12 @@ async function runRootCause(client, input) {
11799
12542
  }
11800
12543
  async function runBlastRadius(client, input) {
11801
12544
  const qs = input.depth !== void 0 ? `?depth=${input.depth}` : "";
11802
- const path53 = projectPath(
12545
+ const path55 = projectPath(
11803
12546
  input.project,
11804
12547
  `/graph/blast-radius/${encodeURIComponent(input.nodeId)}${qs}`
11805
12548
  );
11806
12549
  try {
11807
- const result = await client.get(path53);
12550
+ const result = await client.get(path55);
11808
12551
  if (result.totalAffected === 0) {
11809
12552
  return {
11810
12553
  summary: `${result.origin} has no dependents. Nothing else would break if it failed.`
@@ -11833,17 +12576,17 @@ async function runBlastRadius(client, input) {
11833
12576
  }
11834
12577
  }
11835
12578
  function formatBlastEntry(n) {
11836
- const tag = n.edgeProvenance === import_types29.Provenance.STALE ? " [STALE \u2014 last seen too long ago]" : "";
12579
+ const tag = n.edgeProvenance === import_types31.Provenance.STALE ? " [STALE \u2014 last seen too long ago]" : "";
11837
12580
  return ` \u2022 ${n.nodeId} (distance ${n.distance}, ${n.edgeProvenance})${tag}`;
11838
12581
  }
11839
12582
  async function runDependencies(client, input) {
11840
12583
  const depth = input.depth ?? 3;
11841
- const path53 = projectPath(
12584
+ const path55 = projectPath(
11842
12585
  input.project,
11843
12586
  `/graph/dependencies/${encodeURIComponent(input.nodeId)}?depth=${depth}`
11844
12587
  );
11845
12588
  try {
11846
- const result = await client.get(path53);
12589
+ const result = await client.get(path55);
11847
12590
  if (result.total === 0) {
11848
12591
  return {
11849
12592
  summary: depth === 1 ? `${input.nodeId} has no direct dependencies in the graph.` : `${input.nodeId} has no dependencies (BFS to depth ${depth}).`
@@ -11890,7 +12633,7 @@ async function runObservedDependencies(client, input) {
11890
12633
  if (result.observed) {
11891
12634
  return {
11892
12635
  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
12636
+ provenance: import_types31.Provenance.OBSERVED
11894
12637
  };
11895
12638
  }
11896
12639
  const note = result.hasExtractedOutbound ? " Static (EXTRACTED) dependencies exist but no runtime traffic has been seen \u2014 is OTel running?" : "";
@@ -11900,7 +12643,7 @@ async function runObservedDependencies(client, input) {
11900
12643
  return {
11901
12644
  summary: `${input.nodeId} has ${result.dependencies.length} runtime dependenc${result.dependencies.length === 1 ? "y" : "ies"} confirmed by OTel.`,
11902
12645
  block: blockLines.join("\n"),
11903
- provenance: import_types29.Provenance.OBSERVED
12646
+ provenance: import_types31.Provenance.OBSERVED
11904
12647
  };
11905
12648
  } catch (err) {
11906
12649
  if (err instanceof HttpError && err.status === 404) {
@@ -11935,9 +12678,9 @@ function formatDuration(ms) {
11935
12678
  return `${Math.round(h / 24)}d`;
11936
12679
  }
11937
12680
  async function runIncidents(client, input) {
11938
- const path53 = input.nodeId ? projectPath(input.project, `/incidents/${encodeURIComponent(input.nodeId)}`) : projectPath(input.project, "/incidents");
12681
+ const path55 = input.nodeId ? projectPath(input.project, `/incidents/${encodeURIComponent(input.nodeId)}`) : projectPath(input.project, "/incidents");
11939
12682
  try {
11940
- const body = await client.get(path53);
12683
+ const body = await client.get(path55);
11941
12684
  const events = body.events;
11942
12685
  if (events.length === 0) {
11943
12686
  return {
@@ -11954,7 +12697,7 @@ async function runIncidents(client, input) {
11954
12697
  return {
11955
12698
  summary: `${target} has ${body.total} recorded incident${body.total === 1 ? "" : "s"}; showing the ${ordered.length} most recent.`,
11956
12699
  block: blockLines.join("\n"),
11957
- provenance: import_types29.Provenance.OBSERVED
12700
+ provenance: import_types31.Provenance.OBSERVED
11958
12701
  };
11959
12702
  } catch (err) {
11960
12703
  if (err instanceof HttpError && err.status === 404) {
@@ -12063,7 +12806,7 @@ async function runStaleEdges(client, input) {
12063
12806
  return {
12064
12807
  summary: `${events.length} stale-edge transition${events.length === 1 ? "" : "s"} recorded${input.edgeType ? ` for ${input.edgeType}` : ""}.`,
12065
12808
  block: blockLines.join("\n"),
12066
- provenance: import_types29.Provenance.STALE
12809
+ provenance: import_types31.Provenance.STALE
12067
12810
  };
12068
12811
  }
12069
12812
  async function runPolicies(client, input) {
@@ -12227,7 +12970,7 @@ async function resolveProjectEntry(opts) {
12227
12970
  const cwd = opts.cwd ?? process.cwd();
12228
12971
  const resolvedCwd = await normalizeProjectPath(cwd);
12229
12972
  for (const entry2 of entries) {
12230
- if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${import_node_path51.default.sep}`)) {
12973
+ if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${import_node_path53.default.sep}`)) {
12231
12974
  return entry2;
12232
12975
  }
12233
12976
  }
@@ -12380,7 +13123,7 @@ async function runSync(opts) {
12380
13123
  }
12381
13124
 
12382
13125
  // src/cli.ts
12383
- var import_types30 = require("@neat.is/types");
13126
+ var import_types32 = require("@neat.is/types");
12384
13127
  function isNpxInvocation() {
12385
13128
  if (process.env.npm_command === "exec") return true;
12386
13129
  const execpath = process.env.npm_execpath ?? "";
@@ -12671,12 +13414,12 @@ async function runInit(opts) {
12671
13414
  printDiscoveryReport(opts, services);
12672
13415
  const sections = opts.noInstall ? [] : await buildPatchSections(services, opts.project);
12673
13416
  const patch = renderPatch(sections);
12674
- const patchPath = import_node_path52.default.join(opts.scanPath, "neat.patch");
13417
+ const patchPath = import_node_path54.default.join(opts.scanPath, "neat.patch");
12675
13418
  if (opts.dryRun) {
12676
13419
  await import_node_fs34.promises.writeFile(patchPath, patch, "utf8");
12677
13420
  written.push(patchPath);
12678
13421
  console.log(`dry-run: patch written to ${patchPath}`);
12679
- const gitignorePath = import_node_path52.default.join(opts.scanPath, ".gitignore");
13422
+ const gitignorePath = import_node_path54.default.join(opts.scanPath, ".gitignore");
12680
13423
  const gitignoreExists = await import_node_fs34.promises.stat(gitignorePath).then(() => true).catch(() => false);
12681
13424
  const verb = gitignoreExists ? "append" : "create";
12682
13425
  console.log(`dry-run: would ${verb} ${gitignorePath} (add neat-out/)`);
@@ -12688,9 +13431,9 @@ async function runInit(opts) {
12688
13431
  const graph = getGraph(graphKey);
12689
13432
  const projectPaths = pathsForProject(
12690
13433
  graphKey,
12691
- import_node_path52.default.join(opts.scanPath, "neat-out")
13434
+ import_node_path54.default.join(opts.scanPath, "neat-out")
12692
13435
  );
12693
- const errorsPath = import_node_path52.default.join(import_node_path52.default.dirname(opts.outPath), import_node_path52.default.basename(projectPaths.errorsPath));
13436
+ const errorsPath = import_node_path54.default.join(import_node_path54.default.dirname(opts.outPath), import_node_path54.default.basename(projectPaths.errorsPath));
12694
13437
  const result = await extractFromDirectory(graph, opts.scanPath, { errorsPath });
12695
13438
  await saveGraphToDisk(graph, opts.outPath);
12696
13439
  written.push(opts.outPath);
@@ -12809,9 +13552,9 @@ var CLAUDE_SKILL_CONFIG = {
12809
13552
  };
12810
13553
  function claudeConfigPath() {
12811
13554
  const override = process.env.NEAT_CLAUDE_CONFIG;
12812
- if (override && override.length > 0) return import_node_path52.default.resolve(override);
13555
+ if (override && override.length > 0) return import_node_path54.default.resolve(override);
12813
13556
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
12814
- return import_node_path52.default.join(home, ".claude.json");
13557
+ return import_node_path54.default.join(home, ".claude.json");
12815
13558
  }
12816
13559
  async function runSkill(opts) {
12817
13560
  const snippet2 = JSON.stringify(CLAUDE_SKILL_CONFIG, null, 2) + "\n";
@@ -12835,7 +13578,7 @@ async function runSkill(opts) {
12835
13578
  ...existing,
12836
13579
  mcpServers: { ...mcp, neat: CLAUDE_SKILL_CONFIG.mcpServers.neat }
12837
13580
  };
12838
- await import_node_fs34.promises.mkdir(import_node_path52.default.dirname(target), { recursive: true });
13581
+ await import_node_fs34.promises.mkdir(import_node_path54.default.dirname(target), { recursive: true });
12839
13582
  await import_node_fs34.promises.writeFile(target, JSON.stringify(merged, null, 2) + "\n", "utf8");
12840
13583
  console.log(`neat skill: wrote mcpServers.neat to ${target}`);
12841
13584
  console.log("restart Claude Code to pick up the new MCP server.");
@@ -12885,12 +13628,12 @@ async function main() {
12885
13628
  console.error("neat init: --apply and --dry-run are mutually exclusive");
12886
13629
  process.exit(2);
12887
13630
  }
12888
- const scanPath = import_node_path52.default.resolve(target);
13631
+ const scanPath = import_node_path54.default.resolve(target);
12889
13632
  const projectExplicit = parsed.project !== null;
12890
- const projectName = projectExplicit ? project : import_node_path52.default.basename(scanPath);
13633
+ const projectName = projectExplicit ? project : import_node_path54.default.basename(scanPath);
12891
13634
  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);
13635
+ const fallback = pathsForProject(projectKey, import_node_path54.default.join(scanPath, "neat-out")).snapshotPath;
13636
+ const outPath = import_node_path54.default.resolve(process.env.NEAT_OUT_PATH ?? fallback);
12894
13637
  const result = await runInit({
12895
13638
  scanPath,
12896
13639
  outPath,
@@ -12911,21 +13654,21 @@ async function main() {
12911
13654
  usage();
12912
13655
  process.exit(2);
12913
13656
  }
12914
- const scanPath = import_node_path52.default.resolve(target);
13657
+ const scanPath = import_node_path54.default.resolve(target);
12915
13658
  const stat = await import_node_fs34.promises.stat(scanPath).catch(() => null);
12916
13659
  if (!stat || !stat.isDirectory()) {
12917
13660
  console.error(`neat watch: ${scanPath} is not a directory`);
12918
13661
  process.exit(2);
12919
13662
  }
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))
13663
+ const projectPaths = pathsForProject(project, import_node_path54.default.join(scanPath, "neat-out"));
13664
+ const outPath = import_node_path54.default.resolve(process.env.NEAT_OUT_PATH ?? projectPaths.snapshotPath);
13665
+ const errorsPath = import_node_path54.default.resolve(
13666
+ process.env.NEAT_ERRORS_PATH ?? import_node_path54.default.join(import_node_path54.default.dirname(outPath), import_node_path54.default.basename(projectPaths.errorsPath))
12924
13667
  );
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))
13668
+ const staleEventsPath = import_node_path54.default.resolve(
13669
+ process.env.NEAT_STALE_EVENTS_PATH ?? import_node_path54.default.join(import_node_path54.default.dirname(outPath), import_node_path54.default.basename(projectPaths.staleEventsPath))
12927
13670
  );
12928
- const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ? import_node_path52.default.resolve(process.env.NEAT_EMBEDDINGS_CACHE_PATH) : void 0;
13671
+ const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ? import_node_path54.default.resolve(process.env.NEAT_EMBEDDINGS_CACHE_PATH) : void 0;
12929
13672
  const handle = await startWatch(getGraph(project), {
12930
13673
  scanPath,
12931
13674
  outPath,
@@ -13108,11 +13851,11 @@ async function main() {
13108
13851
  process.exit(1);
13109
13852
  }
13110
13853
  async function tryOrchestrator(cmd, parsed) {
13111
- const scanPath = import_node_path52.default.resolve(cmd);
13854
+ const scanPath = import_node_path54.default.resolve(cmd);
13112
13855
  const stat = await import_node_fs34.promises.stat(scanPath).catch(() => null);
13113
13856
  if (!stat || !stat.isDirectory()) return null;
13114
13857
  const projectExplicit = parsed.project !== null;
13115
- const projectName = projectExplicit ? parsed.project : import_node_path52.default.basename(scanPath);
13858
+ const projectName = projectExplicit ? parsed.project : import_node_path54.default.basename(scanPath);
13116
13859
  const result = await runOrchestrator({
13117
13860
  scanPath,
13118
13861
  project: projectName,
@@ -13301,10 +14044,10 @@ async function runQueryVerb(cmd, parsed) {
13301
14044
  const parts = parsed.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
13302
14045
  const out = [];
13303
14046
  for (const p of parts) {
13304
- const r = import_types30.DivergenceTypeSchema.safeParse(p);
14047
+ const r = import_types32.DivergenceTypeSchema.safeParse(p);
13305
14048
  if (!r.success) {
13306
14049
  console.error(
13307
- `neat divergences: unknown --type "${p}". allowed: ${import_types30.DivergenceTypeSchema.options.join(", ")}`
14050
+ `neat divergences: unknown --type "${p}". allowed: ${import_types32.DivergenceTypeSchema.options.join(", ")}`
13308
14051
  );
13309
14052
  return 2;
13310
14053
  }