@neat.is/core 0.4.26-dev.20260702 → 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/neatd.cjs CHANGED
@@ -58,9 +58,9 @@ function mountBearerAuth(app, opts) {
58
58
  const suffixes = [...DEFAULT_UNAUTH_SUFFIXES, ...opts.extraUnauthenticatedSuffixes ?? []];
59
59
  const publicRead = opts.publicRead === true;
60
60
  app.addHook("preHandler", (req2, reply, done) => {
61
- const path45 = (req2.url.split("?")[0] ?? "").replace(/\/+$/, "");
61
+ const path47 = (req2.url.split("?")[0] ?? "").replace(/\/+$/, "");
62
62
  for (const suffix of suffixes) {
63
- if (path45 === suffix || path45.endsWith(suffix)) {
63
+ if (path47 === suffix || path47.endsWith(suffix)) {
64
64
  done();
65
65
  return;
66
66
  }
@@ -71,11 +71,13 @@ function mountBearerAuth(app, opts) {
71
71
  }
72
72
  const header = req2.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(req2) {
188
190
  };
189
191
  }
190
192
  function resolveProtoRoot() {
191
- const here = import_node_path39.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
192
- return import_node_path39.default.resolve(here, "..", "proto");
193
+ const here = import_node_path41.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
194
+ return import_node_path41.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_url, import_node_path39, import_node_crypto2, grpc, protoLoader;
262
+ var import_node_url, import_node_path41, 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_url = require("url");
266
- import_node_path39 = __toESM(require("path"), 1);
268
+ import_node_path41 = __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_path40.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
377
- const protoRoot = import_node_path40.default.resolve(here, "..", "proto");
389
+ const here = import_node_path42.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
390
+ const protoRoot = import_node_path42.default.resolve(here, "..", "proto");
378
391
  const root = new import_protobufjs.default.Root();
379
- root.resolvePath = (_origin, target) => import_node_path40.default.resolve(protoRoot, target);
392
+ root.resolvePath = (_origin, target) => import_node_path42.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_path40, import_node_url2, 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_path42, import_node_url2, import_fastify2, import_protobufjs, ENV_ATTR_CANONICAL, ENV_ATTR_COMPAT, ENV_FALLBACK, exportTraceServiceRequestType, exportTraceServiceResponseType, cachedProtobufResponseBody, OTLP_STEP_ATTEMPTS, OTLP_STEP_STRIDE;
594
621
  var init_otel = __esm({
595
622
  "src/otel.ts"() {
596
623
  "use strict";
597
624
  init_cjs_shims();
598
- import_node_path40 = __toESM(require("path"), 1);
625
+ import_node_path42 = __toESM(require("path"), 1);
599
626
  import_node_url2 = require("url");
600
627
  import_fastify2 = __toESM(require("fastify"), 1);
601
628
  import_protobufjs = __toESM(require("protobufjs"), 1);
@@ -619,13 +646,13 @@ __export(neatd_exports, {
619
646
  module.exports = __toCommonJS(neatd_exports);
620
647
  init_cjs_shims();
621
648
  var import_node_fs27 = require("fs");
622
- var import_node_path44 = __toESM(require("path"), 1);
649
+ var import_node_path46 = __toESM(require("path"), 1);
623
650
  var import_node_module2 = require("module");
624
651
 
625
652
  // src/daemon.ts
626
653
  init_cjs_shims();
627
654
  var import_node_fs25 = require("fs");
628
- var import_node_path42 = __toESM(require("path"), 1);
655
+ var import_node_path44 = __toESM(require("path"), 1);
629
656
  var import_node_module = require("module");
630
657
 
631
658
  // src/graph.ts
@@ -1155,19 +1182,19 @@ function confidenceFromMix(edges, now = Date.now()) {
1155
1182
  function longestIncomingWalk(graph, start, maxDepth) {
1156
1183
  let best = { path: [start], edges: [] };
1157
1184
  const visited = /* @__PURE__ */ new Set([start]);
1158
- function step(node, path45, edges) {
1159
- if (path45.length > best.path.length) {
1160
- best = { path: [...path45], edges: [...edges] };
1185
+ function step(node, path47, edges) {
1186
+ if (path47.length > best.path.length) {
1187
+ best = { path: [...path47], edges: [...edges] };
1161
1188
  }
1162
- if (path45.length - 1 >= maxDepth) return;
1189
+ if (path47.length - 1 >= maxDepth) return;
1163
1190
  const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
1164
1191
  for (const [srcId, edge] of incoming) {
1165
1192
  if (visited.has(srcId)) continue;
1166
1193
  visited.add(srcId);
1167
- path45.push(srcId);
1194
+ path47.push(srcId);
1168
1195
  edges.push(edge);
1169
- step(srcId, path45, edges);
1170
- path45.pop();
1196
+ step(srcId, path47, edges);
1197
+ path47.pop();
1171
1198
  edges.pop();
1172
1199
  visited.delete(srcId);
1173
1200
  }
@@ -1175,14 +1202,14 @@ function longestIncomingWalk(graph, start, maxDepth) {
1175
1202
  step(start, [start], []);
1176
1203
  return best;
1177
1204
  }
1178
- function databaseRootCauseShape(graph, origin, walk) {
1205
+ function databaseRootCauseShape(graph, origin, walk3) {
1179
1206
  const targetDb = origin;
1180
1207
  const candidatePairs = compatPairs().filter((p) => p.engine === targetDb.engine);
1181
1208
  if (candidatePairs.length === 0) return null;
1182
- for (const id of walk.path) {
1209
+ for (const id of walk3.path) {
1183
1210
  const owner = resolveOwningService(graph, id);
1184
1211
  if (!owner) continue;
1185
- const { id: serviceId3, svc } = owner;
1212
+ const { id: serviceId4, svc } = owner;
1186
1213
  const deps = svc.dependencies ?? {};
1187
1214
  for (const pair of candidatePairs) {
1188
1215
  const declared = deps[pair.driver];
@@ -1195,7 +1222,7 @@ function databaseRootCauseShape(graph, origin, walk) {
1195
1222
  );
1196
1223
  if (!result.compatible) {
1197
1224
  return {
1198
- rootCauseNode: serviceId3,
1225
+ rootCauseNode: serviceId4,
1199
1226
  rootCauseReason: result.reason ?? "incompatible driver",
1200
1227
  ...result.minDriverVersion ? {
1201
1228
  fixRecommendation: `Upgrade ${svc.name} ${pair.driver} driver to >= ${result.minDriverVersion}`
@@ -1206,11 +1233,11 @@ function databaseRootCauseShape(graph, origin, walk) {
1206
1233
  }
1207
1234
  return null;
1208
1235
  }
1209
- function serviceRootCauseShape(graph, _origin, walk) {
1210
- for (const id of walk.path) {
1236
+ function serviceRootCauseShape(graph, _origin, walk3) {
1237
+ for (const id of walk3.path) {
1211
1238
  const owner = resolveOwningService(graph, id);
1212
1239
  if (!owner) continue;
1213
- const { id: serviceId3, svc } = owner;
1240
+ const { id: serviceId4, svc } = owner;
1214
1241
  const deps = svc.dependencies ?? {};
1215
1242
  const serviceNodeEngine = svc.nodeEngine;
1216
1243
  for (const constraint of nodeEngineConstraints()) {
@@ -1219,7 +1246,7 @@ function serviceRootCauseShape(graph, _origin, walk) {
1219
1246
  const result = checkNodeEngineConstraint(constraint, declared, serviceNodeEngine);
1220
1247
  if (!result.compatible && result.reason) {
1221
1248
  return {
1222
- rootCauseNode: serviceId3,
1249
+ rootCauseNode: serviceId4,
1223
1250
  rootCauseReason: result.reason,
1224
1251
  ...result.requiredNodeVersion ? {
1225
1252
  fixRecommendation: `Bump ${svc.name}'s engines.node to >= ${result.requiredNodeVersion}`
@@ -1234,7 +1261,7 @@ function serviceRootCauseShape(graph, _origin, walk) {
1234
1261
  const result = checkPackageConflict(conflict, declared, requiredDeclared);
1235
1262
  if (!result.compatible && result.reason) {
1236
1263
  return {
1237
- rootCauseNode: serviceId3,
1264
+ rootCauseNode: serviceId4,
1238
1265
  rootCauseReason: result.reason,
1239
1266
  fixRecommendation: `Upgrade ${svc.name}'s ${conflict.requires.name} to >= ${conflict.requires.minVersion}`
1240
1267
  };
@@ -1243,10 +1270,10 @@ function serviceRootCauseShape(graph, _origin, walk) {
1243
1270
  }
1244
1271
  return null;
1245
1272
  }
1246
- function fileRootCauseShape(graph, origin, walk) {
1273
+ function fileRootCauseShape(graph, origin, walk3) {
1247
1274
  const owner = resolveOwningService(graph, origin.id);
1248
1275
  if (!owner) return null;
1249
- return serviceRootCauseShape(graph, owner.svc, walk);
1276
+ return serviceRootCauseShape(graph, owner.svc, walk3);
1250
1277
  }
1251
1278
  var rootCauseShapes = {
1252
1279
  [import_types.NodeType.DatabaseNode]: databaseRootCauseShape,
@@ -1258,16 +1285,16 @@ function getRootCause(graph, errorNodeId, errorEvent, incidents) {
1258
1285
  const origin = graph.getNodeAttributes(errorNodeId);
1259
1286
  const shape = rootCauseShapes[origin.type];
1260
1287
  if (shape) {
1261
- const walk = longestIncomingWalk(graph, errorNodeId, ROOT_CAUSE_MAX_DEPTH);
1262
- const match = shape(graph, origin, walk);
1288
+ const walk3 = longestIncomingWalk(graph, errorNodeId, ROOT_CAUSE_MAX_DEPTH);
1289
+ const match = shape(graph, origin, walk3);
1263
1290
  if (match) {
1264
1291
  const reason = errorEvent ? `${match.rootCauseReason} (observed error: ${errorEvent.errorMessage})` : match.rootCauseReason;
1265
1292
  return import_types.RootCauseResultSchema.parse({
1266
1293
  rootCauseNode: match.rootCauseNode,
1267
1294
  rootCauseReason: reason,
1268
- traversalPath: walk.path,
1269
- edgeProvenances: walk.edges.map((e) => e.provenance),
1270
- confidence: confidenceFromMix(walk.edges),
1295
+ traversalPath: walk3.path,
1296
+ edgeProvenances: walk3.edges.map((e) => e.provenance),
1297
+ confidence: confidenceFromMix(walk3.edges),
1271
1298
  fixRecommendation: match.fixRecommendation
1272
1299
  });
1273
1300
  }
@@ -1325,9 +1352,9 @@ function rootCauseFromIncidents(nodeId, incidents, errorEvent) {
1325
1352
  function isFailingCallEdge(e) {
1326
1353
  return e.type === import_types.EdgeType.CALLS && (e.signal?.errorCount ?? 0) > 0;
1327
1354
  }
1328
- function callSourcesForService(graph, serviceId3) {
1329
- const ids = [serviceId3];
1330
- for (const edgeId of graph.outboundEdges(serviceId3)) {
1355
+ function callSourcesForService(graph, serviceId4) {
1356
+ const ids = [serviceId4];
1357
+ for (const edgeId of graph.outboundEdges(serviceId4)) {
1331
1358
  const e = graph.getEdgeAttributes(edgeId);
1332
1359
  if (e.type !== import_types.EdgeType.CONTAINS) continue;
1333
1360
  const tgt = graph.getNodeAttributes(e.target);
@@ -1344,9 +1371,9 @@ function failingCallDominates(e, id, curEdge, curId) {
1344
1371
  }
1345
1372
  return id < curId;
1346
1373
  }
1347
- function dominantFailingCall(graph, serviceId3, visited) {
1374
+ function dominantFailingCall(graph, serviceId4, visited) {
1348
1375
  let best = null;
1349
- for (const src of callSourcesForService(graph, serviceId3)) {
1376
+ for (const src of callSourcesForService(graph, serviceId4)) {
1350
1377
  for (const edgeId of graph.outboundEdges(src)) {
1351
1378
  const e = graph.getEdgeAttributes(edgeId);
1352
1379
  if (!isFailingCallEdge(e)) continue;
@@ -1361,26 +1388,26 @@ function dominantFailingCall(graph, serviceId3, visited) {
1361
1388
  return best;
1362
1389
  }
1363
1390
  function followFailingCallChain(graph, originServiceId, maxDepth) {
1364
- const path45 = [originServiceId];
1391
+ const path47 = [originServiceId];
1365
1392
  const edges = [];
1366
1393
  const visited = /* @__PURE__ */ new Set([originServiceId]);
1367
1394
  let current = originServiceId;
1368
1395
  for (let depth = 0; depth < maxDepth; depth++) {
1369
1396
  const hop = dominantFailingCall(graph, current, visited);
1370
1397
  if (!hop) break;
1371
- path45.push(hop.nextService);
1398
+ path47.push(hop.nextService);
1372
1399
  edges.push(hop.edge);
1373
1400
  visited.add(hop.nextService);
1374
1401
  current = hop.nextService;
1375
1402
  }
1376
1403
  if (edges.length === 0) return null;
1377
- return { path: path45, edges, culprit: current };
1404
+ return { path: path47, edges, culprit: current };
1378
1405
  }
1379
1406
  function crossServiceRootCause(graph, originId, incidents, errorEvent) {
1380
1407
  const chain = followFailingCallChain(graph, originId, ROOT_CAUSE_MAX_DEPTH);
1381
1408
  if (!chain) return null;
1382
1409
  const culprit = chain.culprit;
1383
- const path45 = [...chain.path];
1410
+ const path47 = [...chain.path];
1384
1411
  const edgeProvenances = chain.edges.map((e) => e.provenance);
1385
1412
  const baseConfidence = confidenceFromMix(chain.edges);
1386
1413
  const confidence = Math.max(0, Math.min(1, baseConfidence * INCIDENT_ROOT_CAUSE_CONFIDENCE));
@@ -1388,14 +1415,14 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
1388
1415
  if (loc) {
1389
1416
  let rootCauseNode = culprit;
1390
1417
  if (loc.fileNode) {
1391
- path45.push(loc.fileNode);
1418
+ path47.push(loc.fileNode);
1392
1419
  edgeProvenances.push(import_types.Provenance.OBSERVED);
1393
1420
  rootCauseNode = loc.fileNode;
1394
1421
  }
1395
1422
  return import_types.RootCauseResultSchema.parse({
1396
1423
  rootCauseNode,
1397
1424
  rootCauseReason: loc.rootCauseReason,
1398
- traversalPath: path45,
1425
+ traversalPath: path47,
1399
1426
  edgeProvenances,
1400
1427
  confidence,
1401
1428
  ...loc.fixRecommendation ? { fixRecommendation: loc.fixRecommendation } : {}
@@ -1407,7 +1434,7 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
1407
1434
  return import_types.RootCauseResultSchema.parse({
1408
1435
  rootCauseNode: culprit,
1409
1436
  rootCauseReason: `${culpritName} is failing downstream calls (${errs} observed error${errs === 1 ? "" : "s"})`,
1410
- traversalPath: path45,
1437
+ traversalPath: path47,
1411
1438
  edgeProvenances,
1412
1439
  confidence,
1413
1440
  fixRecommendation: `Inspect ${culpritName}'s failing handler`
@@ -2007,6 +2034,28 @@ var DEFAULT_STALE_THRESHOLDS = {
2007
2034
  CONFIGURED_BY: DAY_MS,
2008
2035
  RUNS_ON: DAY_MS
2009
2036
  };
2037
+ var FALLBACK_STALE_THRESHOLD_MS = DAY_MS;
2038
+ function loadStaleThresholdsFromEnv() {
2039
+ const raw = process.env.NEAT_STALE_THRESHOLDS;
2040
+ if (!raw) return DEFAULT_STALE_THRESHOLDS;
2041
+ try {
2042
+ const overrides = JSON.parse(raw);
2043
+ const merged = { ...DEFAULT_STALE_THRESHOLDS };
2044
+ for (const [k, v] of Object.entries(overrides)) {
2045
+ if (typeof v === "number" && Number.isFinite(v) && v >= 0) merged[k] = v;
2046
+ }
2047
+ return merged;
2048
+ } catch (err) {
2049
+ console.warn(
2050
+ `[neat] NEAT_STALE_THRESHOLDS could not be parsed (${err.message}); using defaults`
2051
+ );
2052
+ return DEFAULT_STALE_THRESHOLDS;
2053
+ }
2054
+ }
2055
+ function thresholdForEdgeType(edgeType, overrides) {
2056
+ const map = overrides ?? loadStaleThresholdsFromEnv();
2057
+ return map[edgeType] ?? FALLBACK_STALE_THRESHOLD_MS;
2058
+ }
2010
2059
  var DEFAULT_INCIDENT_THRESHOLDS = {
2011
2060
  threshold: 5,
2012
2061
  windowMs: 6e4
@@ -2309,10 +2358,48 @@ var STITCH_EDGE_TYPES = /* @__PURE__ */ new Set([
2309
2358
  ]);
2310
2359
  var WIRE_SPAN_KIND_CLIENT = 3;
2311
2360
  var WIRE_SPAN_KIND_PRODUCER = 4;
2361
+ var WIRE_SPAN_KIND_CONSUMER = 5;
2312
2362
  function spanMintsObservedEdge(kind) {
2313
2363
  if (kind === void 0 || kind === 0) return true;
2314
2364
  return kind === WIRE_SPAN_KIND_CLIENT || kind === WIRE_SPAN_KIND_PRODUCER;
2315
2365
  }
2366
+ function spanMintsMessagingEdge(kind) {
2367
+ return kind === WIRE_SPAN_KIND_PRODUCER || kind === WIRE_SPAN_KIND_CONSUMER;
2368
+ }
2369
+ function spanServesGraphqlOperation(kind) {
2370
+ return kind !== WIRE_SPAN_KIND_CLIENT && kind !== WIRE_SPAN_KIND_PRODUCER && kind !== WIRE_SPAN_KIND_CONSUMER;
2371
+ }
2372
+ function ensureGraphqlOperationNode(graph, serviceName, operationType, operationName) {
2373
+ const id = (0, import_types3.graphqlOperationId)(serviceName, operationType, operationName);
2374
+ if (graph.hasNode(id)) return id;
2375
+ const node = {
2376
+ id,
2377
+ type: import_types3.NodeType.GraphQLOperationNode,
2378
+ name: operationName,
2379
+ service: serviceName,
2380
+ operationType: operationType.toLowerCase(),
2381
+ operationName,
2382
+ discoveredVia: "otel"
2383
+ };
2384
+ graph.addNode(id, node);
2385
+ return id;
2386
+ }
2387
+ function messagingDestinationKind(system) {
2388
+ return `${system}-topic`;
2389
+ }
2390
+ function ensureMessagingDestinationNode(graph, system, destination) {
2391
+ const id = (0, import_types3.infraId)(messagingDestinationKind(system), destination);
2392
+ if (graph.hasNode(id)) return id;
2393
+ const node = {
2394
+ id,
2395
+ type: import_types3.NodeType.InfraNode,
2396
+ name: destination,
2397
+ provider: "self",
2398
+ kind: messagingDestinationKind(system)
2399
+ };
2400
+ graph.addNode(id, node);
2401
+ return id;
2402
+ }
2316
2403
  var PARENT_SPAN_CACHE_SIZE = 1e4;
2317
2404
  var PARENT_SPAN_CACHE_TTL_MS = 5 * 60 * 1e3;
2318
2405
  var parentSpanCache = /* @__PURE__ */ new Map();
@@ -2406,6 +2493,21 @@ function ensureDatabaseNode(graph, host, engine) {
2406
2493
  graph.addNode(id, node);
2407
2494
  return id;
2408
2495
  }
2496
+ function ensureLocalDatabaseNode(graph, serviceName, name, engine) {
2497
+ const id = (0, import_types3.localDatabaseId)(serviceName, name);
2498
+ if (graph.hasNode(id)) return id;
2499
+ const node = {
2500
+ id,
2501
+ type: import_types3.NodeType.DatabaseNode,
2502
+ name,
2503
+ engine,
2504
+ engineVersion: "unknown",
2505
+ compatibleDrivers: [],
2506
+ discoveredVia: "otel"
2507
+ };
2508
+ graph.addNode(id, node);
2509
+ return id;
2510
+ }
2409
2511
  function ensureFrontierNode(graph, host, ts) {
2410
2512
  const id = frontierIdFor(host);
2411
2513
  if (graph.hasNode(id)) {
@@ -2657,10 +2759,21 @@ async function handleSpan(ctx, span) {
2657
2759
  let affectedNode = sourceId;
2658
2760
  const mintsFromCallerSide = spanMintsObservedEdge(span.kind);
2659
2761
  if (span.dbSystem) {
2660
- const host = pickAddress(span);
2661
- if (mintsFromCallerSide && host) {
2662
- ensureDatabaseNode(ctx.graph, host, span.dbSystem);
2663
- const targetId = (0, import_types3.databaseId)(host);
2762
+ if (mintsFromCallerSide) {
2763
+ const host = pickAddress(span);
2764
+ let targetId;
2765
+ if (host) {
2766
+ ensureDatabaseNode(ctx.graph, host, span.dbSystem);
2767
+ targetId = (0, import_types3.databaseId)(host);
2768
+ } else {
2769
+ const localName = span.dbName ?? span.dbSystem;
2770
+ targetId = ensureLocalDatabaseNode(
2771
+ ctx.graph,
2772
+ span.service,
2773
+ localName,
2774
+ span.dbSystem
2775
+ );
2776
+ }
2664
2777
  const result = upsertObservedEdge(
2665
2778
  ctx.graph,
2666
2779
  import_types3.EdgeType.CONNECTS_TO,
@@ -2672,6 +2785,40 @@ async function handleSpan(ctx, span) {
2672
2785
  );
2673
2786
  if (result) affectedNode = targetId;
2674
2787
  }
2788
+ } else if (span.messagingSystem && span.messagingDestination && spanMintsMessagingEdge(span.kind)) {
2789
+ const targetId = ensureMessagingDestinationNode(
2790
+ ctx.graph,
2791
+ span.messagingSystem,
2792
+ span.messagingDestination
2793
+ );
2794
+ const edgeType = span.kind === WIRE_SPAN_KIND_CONSUMER ? import_types3.EdgeType.CONSUMES_FROM : import_types3.EdgeType.PUBLISHES_TO;
2795
+ const result = upsertObservedEdge(
2796
+ ctx.graph,
2797
+ edgeType,
2798
+ observedSource(),
2799
+ targetId,
2800
+ ts,
2801
+ isError,
2802
+ callSiteEvidence
2803
+ );
2804
+ if (result) affectedNode = targetId;
2805
+ } else if (span.graphqlOperationName && span.graphqlOperationType && spanServesGraphqlOperation(span.kind)) {
2806
+ const targetId = ensureGraphqlOperationNode(
2807
+ ctx.graph,
2808
+ span.service,
2809
+ span.graphqlOperationType,
2810
+ span.graphqlOperationName
2811
+ );
2812
+ const result = upsertObservedEdge(
2813
+ ctx.graph,
2814
+ import_types3.EdgeType.CONTAINS,
2815
+ observedSource(),
2816
+ targetId,
2817
+ ts,
2818
+ isError,
2819
+ callSiteEvidence
2820
+ );
2821
+ if (result) affectedNode = targetId;
2675
2822
  } else {
2676
2823
  const host = pickAddress(span);
2677
2824
  let resolvedViaAddress = false;
@@ -2781,29 +2928,29 @@ function promoteFrontierNodes(graph, opts = {}) {
2781
2928
  toPromote.push({ frontierId: id, serviceId: target });
2782
2929
  });
2783
2930
  let promoted = 0;
2784
- for (const { frontierId: frontierId2, serviceId: serviceId3 } of toPromote) {
2931
+ for (const { frontierId: frontierId2, serviceId: serviceId4 } of toPromote) {
2785
2932
  if (opts.policies && opts.policies.length > 0 && opts.policyCtx) {
2786
2933
  const gate = canPromoteFrontier(graph, frontierId2, opts.policies, opts.policyCtx);
2787
2934
  if (!gate.allowed) {
2788
2935
  continue;
2789
2936
  }
2790
2937
  }
2791
- rewireFrontierEdges(graph, frontierId2, serviceId3);
2938
+ rewireFrontierEdges(graph, frontierId2, serviceId4);
2792
2939
  graph.dropNode(frontierId2);
2793
2940
  promoted++;
2794
2941
  }
2795
2942
  return promoted;
2796
2943
  }
2797
- function rewireFrontierEdges(graph, frontierId2, serviceId3) {
2944
+ function rewireFrontierEdges(graph, frontierId2, serviceId4) {
2798
2945
  const inbound = [...graph.inboundEdges(frontierId2)];
2799
2946
  const outbound = [...graph.outboundEdges(frontierId2)];
2800
2947
  for (const edgeId of inbound) {
2801
2948
  const edge = graph.getEdgeAttributes(edgeId);
2802
- rebuildEdge(graph, edge, edge.source, serviceId3, edgeId);
2949
+ rebuildEdge(graph, edge, edge.source, serviceId4, edgeId);
2803
2950
  }
2804
2951
  for (const edgeId of outbound) {
2805
2952
  const edge = graph.getEdgeAttributes(edgeId);
2806
- rebuildEdge(graph, edge, serviceId3, edge.target, edgeId);
2953
+ rebuildEdge(graph, edge, serviceId4, edge.target, edgeId);
2807
2954
  }
2808
2955
  }
2809
2956
  function rebuildEdge(graph, edge, newSource, newTarget, oldEdgeId) {
@@ -2832,6 +2979,51 @@ function pickLater(a, b) {
2832
2979
  if (!b) return a;
2833
2980
  return new Date(a).getTime() >= new Date(b).getTime() ? a : b;
2834
2981
  }
2982
+ async function markStaleEdges(graph, options = {}) {
2983
+ const thresholds = options.thresholds ?? loadStaleThresholdsFromEnv();
2984
+ const now = options.now ?? Date.now();
2985
+ const events = [];
2986
+ const project = options.project ?? DEFAULT_PROJECT;
2987
+ graph.forEachEdge((id, attrs) => {
2988
+ const e = attrs;
2989
+ if (e.provenance !== import_types3.Provenance.OBSERVED) return;
2990
+ if (!e.lastObserved) return;
2991
+ const threshold = thresholdForEdgeType(e.type, thresholds);
2992
+ const age = now - new Date(e.lastObserved).getTime();
2993
+ if (age > threshold) {
2994
+ const updated = { ...e, provenance: import_types3.Provenance.STALE, confidence: 0.3 };
2995
+ graph.replaceEdgeAttributes(id, updated);
2996
+ events.push({
2997
+ edgeId: id,
2998
+ source: e.source,
2999
+ target: e.target,
3000
+ edgeType: e.type,
3001
+ thresholdMs: threshold,
3002
+ ageMs: age,
3003
+ lastObserved: e.lastObserved,
3004
+ transitionedAt: new Date(now).toISOString()
3005
+ });
3006
+ emitNeatEvent({
3007
+ type: "stale-transition",
3008
+ project,
3009
+ payload: {
3010
+ edgeId: id,
3011
+ from: import_types3.Provenance.OBSERVED,
3012
+ to: import_types3.Provenance.STALE
3013
+ }
3014
+ });
3015
+ }
3016
+ });
3017
+ if (options.staleEventsPath && events.length > 0) {
3018
+ await appendStaleEvents(options.staleEventsPath, events);
3019
+ }
3020
+ return { count: events.length, events };
3021
+ }
3022
+ async function appendStaleEvents(staleEventsPath, events) {
3023
+ await import_node_fs3.promises.mkdir(import_node_path3.default.dirname(staleEventsPath), { recursive: true });
3024
+ const lines = events.map((e) => JSON.stringify(e)).join("\n") + "\n";
3025
+ await import_node_fs3.promises.appendFile(staleEventsPath, lines, "utf8");
3026
+ }
2835
3027
  async function readStaleEvents(staleEventsPath) {
2836
3028
  try {
2837
3029
  const raw = await import_node_fs3.promises.readFile(staleEventsPath, "utf8");
@@ -2841,6 +3033,31 @@ async function readStaleEvents(staleEventsPath) {
2841
3033
  throw err;
2842
3034
  }
2843
3035
  }
3036
+ function startStalenessLoop(graph, options = {}) {
3037
+ let stopped = false;
3038
+ const intervalMs = options.intervalMs ?? 6e4;
3039
+ const tick = () => {
3040
+ if (stopped) return;
3041
+ void (async () => {
3042
+ try {
3043
+ await markStaleEdges(graph, {
3044
+ thresholds: options.thresholds,
3045
+ staleEventsPath: options.staleEventsPath,
3046
+ project: options.project
3047
+ });
3048
+ if (options.onPolicyTrigger) await options.onPolicyTrigger(graph);
3049
+ } catch (err) {
3050
+ console.error("staleness tick failed", err);
3051
+ }
3052
+ })();
3053
+ };
3054
+ const interval = setInterval(tick, intervalMs);
3055
+ if (typeof interval.unref === "function") interval.unref();
3056
+ return () => {
3057
+ stopped = true;
3058
+ clearInterval(interval);
3059
+ };
3060
+ }
2844
3061
  async function readErrorEvents(errorsPath) {
2845
3062
  try {
2846
3063
  const raw = await import_node_fs3.promises.readFile(errorsPath, "utf8");
@@ -3492,9 +3709,9 @@ var K8S_KINDS_WITH_HOSTNAMES = /* @__PURE__ */ new Set([
3492
3709
  "StatefulSet",
3493
3710
  "DaemonSet"
3494
3711
  ]);
3495
- function addAliases(graph, serviceId3, candidates) {
3496
- if (!graph.hasNode(serviceId3)) return;
3497
- const node = graph.getNodeAttributes(serviceId3);
3712
+ function addAliases(graph, serviceId4, candidates) {
3713
+ if (!graph.hasNode(serviceId4)) return;
3714
+ const node = graph.getNodeAttributes(serviceId4);
3498
3715
  if (node.type !== import_types6.NodeType.ServiceNode) return;
3499
3716
  const set = new Set(node.aliases ?? []);
3500
3717
  for (const c of candidates) {
@@ -3504,7 +3721,7 @@ function addAliases(graph, serviceId3, candidates) {
3504
3721
  }
3505
3722
  if (set.size === 0) return;
3506
3723
  const updated = { ...node, aliases: [...set].sort() };
3507
- graph.replaceNodeAttributes(serviceId3, updated);
3724
+ graph.replaceNodeAttributes(serviceId4, updated);
3508
3725
  }
3509
3726
  function indexServicesByName(services) {
3510
3727
  const map = /* @__PURE__ */ new Map();
@@ -3537,12 +3754,12 @@ async function collectComposeAliases(graph, scanPath, serviceIndex) {
3537
3754
  }
3538
3755
  if (!compose?.services) return;
3539
3756
  for (const [composeName, svc] of Object.entries(compose.services)) {
3540
- const serviceId3 = serviceIndex.get(composeName);
3541
- if (!serviceId3) continue;
3757
+ const serviceId4 = serviceIndex.get(composeName);
3758
+ if (!serviceId4) continue;
3542
3759
  const aliases = /* @__PURE__ */ new Set([composeName]);
3543
3760
  if (svc.container_name) aliases.add(svc.container_name);
3544
3761
  if (svc.hostname) aliases.add(svc.hostname);
3545
- addAliases(graph, serviceId3, aliases);
3762
+ addAliases(graph, serviceId4, aliases);
3546
3763
  }
3547
3764
  }
3548
3765
  var LABEL_KEYS = /* @__PURE__ */ new Set([
@@ -3655,16 +3872,28 @@ init_cjs_shims();
3655
3872
  var import_node_fs10 = require("fs");
3656
3873
  var import_node_path10 = __toESM(require("path"), 1);
3657
3874
  var import_types7 = require("@neat.is/types");
3875
+ function buildServiceHostIndex(services) {
3876
+ const knownHosts = /* @__PURE__ */ new Set();
3877
+ const hostToNodeId = /* @__PURE__ */ new Map();
3878
+ for (const service of services) {
3879
+ const base = import_node_path10.default.basename(service.dir);
3880
+ knownHosts.add(base);
3881
+ knownHosts.add(service.pkg.name);
3882
+ hostToNodeId.set(base, service.node.id);
3883
+ hostToNodeId.set(service.pkg.name, service.node.id);
3884
+ }
3885
+ return { knownHosts, hostToNodeId };
3886
+ }
3658
3887
  async function walkSourceFiles(dir) {
3659
3888
  const out = [];
3660
- async function walk(current) {
3889
+ async function walk3(current) {
3661
3890
  const entries = await import_node_fs10.promises.readdir(current, { withFileTypes: true }).catch(() => []);
3662
3891
  for (const entry2 of entries) {
3663
3892
  const full = import_node_path10.default.join(current, entry2.name);
3664
3893
  if (entry2.isDirectory()) {
3665
3894
  if (IGNORED_DIRS.has(entry2.name)) continue;
3666
3895
  if (await isPythonVenvDir(full)) continue;
3667
- await walk(full);
3896
+ await walk3(full);
3668
3897
  } else if (entry2.isFile() && SERVICE_FILE_EXTENSIONS.has(import_node_path10.default.extname(entry2.name)) && // Skip NEAT's own generated `otel-init.*` bootstrap — extracting it
3669
3898
  // would attribute our instrumentation imports to the user's service.
3670
3899
  !isNeatAuthoredSourceFile(entry2.name)) {
@@ -3672,7 +3901,7 @@ async function walkSourceFiles(dir) {
3672
3901
  }
3673
3902
  }
3674
3903
  }
3675
- await walk(dir);
3904
+ await walk3(dir);
3676
3905
  return out;
3677
3906
  }
3678
3907
  async function loadSourceFiles(dir) {
@@ -4766,20 +4995,20 @@ var import_node_path21 = __toESM(require("path"), 1);
4766
4995
  var import_types10 = require("@neat.is/types");
4767
4996
  async function walkConfigFiles(dir) {
4768
4997
  const out = [];
4769
- async function walk(current) {
4998
+ async function walk3(current) {
4770
4999
  const entries = await import_node_fs14.promises.readdir(current, { withFileTypes: true });
4771
5000
  for (const entry2 of entries) {
4772
5001
  const full = import_node_path21.default.join(current, entry2.name);
4773
5002
  if (entry2.isDirectory()) {
4774
5003
  if (IGNORED_DIRS.has(entry2.name)) continue;
4775
5004
  if (await isPythonVenvDir(full)) continue;
4776
- await walk(full);
5005
+ await walk3(full);
4777
5006
  } else if (entry2.isFile() && isConfigFile(entry2.name).match) {
4778
5007
  out.push(full);
4779
5008
  }
4780
5009
  }
4781
5010
  }
4782
- await walk(dir);
5011
+ await walk3(dir);
4783
5012
  return out;
4784
5013
  }
4785
5014
  async function addConfigNodes(graph, services, scanPath) {
@@ -4827,17 +5056,347 @@ async function addConfigNodes(graph, services, scanPath) {
4827
5056
  return { nodesAdded, edgesAdded };
4828
5057
  }
4829
5058
 
5059
+ // src/extract/routes.ts
5060
+ init_cjs_shims();
5061
+ var import_node_path22 = __toESM(require("path"), 1);
5062
+ var import_tree_sitter2 = __toESM(require("tree-sitter"), 1);
5063
+ var import_tree_sitter_javascript2 = __toESM(require("tree-sitter-javascript"), 1);
5064
+ var import_types11 = require("@neat.is/types");
5065
+ var PARSE_CHUNK2 = 16384;
5066
+ function parseSource2(parser, source) {
5067
+ return parser.parse(
5068
+ (index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK2)
5069
+ );
5070
+ }
5071
+ function makeJsParser2() {
5072
+ const p = new import_tree_sitter2.default();
5073
+ p.setLanguage(import_tree_sitter_javascript2.default);
5074
+ return p;
5075
+ }
5076
+ var ROUTER_METHODS = /* @__PURE__ */ new Set([
5077
+ "get",
5078
+ "post",
5079
+ "put",
5080
+ "patch",
5081
+ "delete",
5082
+ "options",
5083
+ "head",
5084
+ "all"
5085
+ ]);
5086
+ var NEXT_APP_METHODS = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]);
5087
+ var JS_ROUTE_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx"]);
5088
+ function canonicalizeTemplate(raw) {
5089
+ let p = raw.split("?")[0].split("#")[0];
5090
+ if (!p.startsWith("/")) p = "/" + p;
5091
+ if (p.length > 1 && p.endsWith("/")) p = p.slice(0, -1);
5092
+ return p;
5093
+ }
5094
+ function isDynamicSegment(seg) {
5095
+ if (seg.length === 0) return false;
5096
+ if (seg.includes(":")) return true;
5097
+ if (seg.startsWith("{") || seg.startsWith("[")) return true;
5098
+ if (/^\d+$/.test(seg)) return true;
5099
+ 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;
5100
+ if (/^[0-9a-f]{24,}$/i.test(seg)) return true;
5101
+ return false;
5102
+ }
5103
+ function normalizePathTemplate(raw) {
5104
+ const canonical = canonicalizeTemplate(raw);
5105
+ const segments = canonical.split("/").filter((s) => s.length > 0);
5106
+ const normalised = segments.map((seg) => isDynamicSegment(seg) ? ":param" : seg.toLowerCase());
5107
+ return "/" + normalised.join("/");
5108
+ }
5109
+ function walk(node, visit) {
5110
+ visit(node);
5111
+ for (let i = 0; i < node.namedChildCount; i++) {
5112
+ const child = node.namedChild(i);
5113
+ if (child) walk(child, visit);
5114
+ }
5115
+ }
5116
+ function staticStringText(node) {
5117
+ if (node.type === "string") {
5118
+ for (let i = 0; i < node.namedChildCount; i++) {
5119
+ const child = node.namedChild(i);
5120
+ if (child?.type === "string_fragment") return child.text;
5121
+ }
5122
+ return "";
5123
+ }
5124
+ if (node.type === "template_string") {
5125
+ for (let i = 0; i < node.namedChildCount; i++) {
5126
+ if (node.namedChild(i)?.type === "template_substitution") return null;
5127
+ }
5128
+ const raw = node.text;
5129
+ return raw.length >= 2 ? raw.slice(1, -1) : "";
5130
+ }
5131
+ return null;
5132
+ }
5133
+ function objectStringProp(objNode, key) {
5134
+ for (let i = 0; i < objNode.namedChildCount; i++) {
5135
+ const pair = objNode.namedChild(i);
5136
+ if (!pair || pair.type !== "pair") continue;
5137
+ const k = pair.childForFieldName("key");
5138
+ if (!k) continue;
5139
+ const kText = k.type === "string" ? staticStringText(k) : k.text;
5140
+ if (kText !== key) continue;
5141
+ const v = pair.childForFieldName("value");
5142
+ if (v) return staticStringText(v);
5143
+ }
5144
+ return null;
5145
+ }
5146
+ function fastifyRouteMethods(objNode) {
5147
+ for (let i = 0; i < objNode.namedChildCount; i++) {
5148
+ const pair = objNode.namedChild(i);
5149
+ if (!pair || pair.type !== "pair") continue;
5150
+ const k = pair.childForFieldName("key");
5151
+ const kText = k ? k.type === "string" ? staticStringText(k) : k.text : null;
5152
+ if (kText !== "method") continue;
5153
+ const v = pair.childForFieldName("value");
5154
+ if (!v) return [];
5155
+ if (v.type === "string" || v.type === "template_string") {
5156
+ const s = staticStringText(v);
5157
+ return s ? [s.toUpperCase()] : [];
5158
+ }
5159
+ if (v.type === "array") {
5160
+ const out = [];
5161
+ for (let j = 0; j < v.namedChildCount; j++) {
5162
+ const el = v.namedChild(j);
5163
+ if (el && (el.type === "string" || el.type === "template_string")) {
5164
+ const s = staticStringText(el);
5165
+ if (s) out.push(s.toUpperCase());
5166
+ }
5167
+ }
5168
+ return out;
5169
+ }
5170
+ }
5171
+ return [];
5172
+ }
5173
+ function serverRoutesFromSource(source, parser, hasExpress, hasFastify) {
5174
+ const tree = parseSource2(parser, source);
5175
+ const out = [];
5176
+ const framework = hasExpress ? "express" : "fastify";
5177
+ walk(tree.rootNode, (node) => {
5178
+ if (node.type !== "call_expression") return;
5179
+ const fn = node.childForFieldName("function");
5180
+ if (!fn || fn.type !== "member_expression") return;
5181
+ const prop = fn.childForFieldName("property");
5182
+ if (!prop) return;
5183
+ const method = prop.text.toLowerCase();
5184
+ const args = node.childForFieldName("arguments");
5185
+ const first = args?.namedChild(0);
5186
+ if (!first) return;
5187
+ const line = node.startPosition.row + 1;
5188
+ if (ROUTER_METHODS.has(method)) {
5189
+ const p = staticStringText(first);
5190
+ if (p && p.startsWith("/")) {
5191
+ out.push({
5192
+ method: method === "all" ? "ALL" : method.toUpperCase(),
5193
+ pathTemplate: canonicalizeTemplate(p),
5194
+ line,
5195
+ framework
5196
+ });
5197
+ }
5198
+ return;
5199
+ }
5200
+ if (method === "route" && hasFastify && first.type === "object") {
5201
+ const url = objectStringProp(first, "url");
5202
+ if (!url || !url.startsWith("/")) return;
5203
+ const methods = fastifyRouteMethods(first);
5204
+ const list = methods.length > 0 ? methods : ["ALL"];
5205
+ for (const m of list) {
5206
+ out.push({
5207
+ method: m === "ALL" ? "ALL" : m.toUpperCase(),
5208
+ pathTemplate: canonicalizeTemplate(url),
5209
+ line,
5210
+ framework: "fastify"
5211
+ });
5212
+ }
5213
+ }
5214
+ });
5215
+ return out;
5216
+ }
5217
+ function segmentsOf(relFile) {
5218
+ return toPosix2(relFile).split("/").filter((s) => s.length > 0);
5219
+ }
5220
+ function isNextAppRouteFile(relFile) {
5221
+ const segs = segmentsOf(relFile);
5222
+ if (!segs.includes("app")) return false;
5223
+ const base = segs[segs.length - 1] ?? "";
5224
+ return /^route\.(?:js|jsx|mjs|cjs|ts|tsx)$/.test(base);
5225
+ }
5226
+ function isNextPagesApiFile(relFile) {
5227
+ const segs = segmentsOf(relFile);
5228
+ const pagesIdx = segs.indexOf("pages");
5229
+ if (pagesIdx === -1 || segs[pagesIdx + 1] !== "api") return false;
5230
+ const base = segs[segs.length - 1] ?? "";
5231
+ if (/^_(app|document|middleware)\./.test(base)) return false;
5232
+ return JS_ROUTE_EXTENSIONS.has(import_node_path22.default.extname(base));
5233
+ }
5234
+ function nextSegment(seg) {
5235
+ if (seg.startsWith("(") && seg.endsWith(")")) return null;
5236
+ const catchAll = seg.match(/^\[\[?\.\.\.(.+?)\]?\]$/);
5237
+ if (catchAll) return ":" + catchAll[1];
5238
+ const dynamic = seg.match(/^\[(.+?)\]$/);
5239
+ if (dynamic) return ":" + dynamic[1];
5240
+ return seg;
5241
+ }
5242
+ function nextAppPathTemplate(relFile) {
5243
+ const segs = segmentsOf(relFile);
5244
+ const appIdx = segs.lastIndexOf("app");
5245
+ const between = segs.slice(appIdx + 1, segs.length - 1);
5246
+ const parts = [];
5247
+ for (const seg of between) {
5248
+ const mapped = nextSegment(seg);
5249
+ if (mapped !== null) parts.push(mapped);
5250
+ }
5251
+ return "/" + parts.join("/");
5252
+ }
5253
+ function nextPagesApiPathTemplate(relFile) {
5254
+ const segs = segmentsOf(relFile);
5255
+ const pagesIdx = segs.indexOf("pages");
5256
+ const rest = segs.slice(pagesIdx + 1);
5257
+ const parts = [];
5258
+ for (let i = 0; i < rest.length; i++) {
5259
+ let seg = rest[i];
5260
+ if (i === rest.length - 1) {
5261
+ seg = seg.replace(/\.(?:js|jsx|mjs|cjs|ts|tsx)$/, "");
5262
+ if (seg === "index") continue;
5263
+ }
5264
+ const mapped = nextSegment(seg);
5265
+ if (mapped !== null) parts.push(mapped);
5266
+ }
5267
+ return "/" + parts.join("/");
5268
+ }
5269
+ function nextAppMethods(root) {
5270
+ const out = [];
5271
+ walk(root, (node) => {
5272
+ if (node.type !== "export_statement") return;
5273
+ const decl = node.childForFieldName("declaration");
5274
+ if (!decl) return;
5275
+ const line = node.startPosition.row + 1;
5276
+ if (decl.type === "function_declaration") {
5277
+ const name = decl.childForFieldName("name")?.text;
5278
+ if (name && NEXT_APP_METHODS.has(name)) out.push({ method: name, line });
5279
+ return;
5280
+ }
5281
+ if (decl.type === "lexical_declaration" || decl.type === "variable_declaration") {
5282
+ for (let i = 0; i < decl.namedChildCount; i++) {
5283
+ const d = decl.namedChild(i);
5284
+ if (d?.type !== "variable_declarator") continue;
5285
+ const name = d.childForFieldName("name")?.text;
5286
+ if (name && NEXT_APP_METHODS.has(name)) out.push({ method: name, line });
5287
+ }
5288
+ }
5289
+ });
5290
+ return out;
5291
+ }
5292
+ function nextRoutesFromFile(source, relFile, parser) {
5293
+ if (isNextAppRouteFile(relFile)) {
5294
+ const tree = parseSource2(parser, source);
5295
+ const template = nextAppPathTemplate(relFile);
5296
+ return nextAppMethods(tree.rootNode).map(({ method, line }) => ({
5297
+ method,
5298
+ pathTemplate: canonicalizeTemplate(template),
5299
+ line,
5300
+ framework: "next"
5301
+ }));
5302
+ }
5303
+ if (isNextPagesApiFile(relFile)) {
5304
+ return [
5305
+ {
5306
+ method: "ALL",
5307
+ pathTemplate: canonicalizeTemplate(nextPagesApiPathTemplate(relFile)),
5308
+ line: 1,
5309
+ framework: "next"
5310
+ }
5311
+ ];
5312
+ }
5313
+ return [];
5314
+ }
5315
+ async function addRoutes(graph, services) {
5316
+ const jsParser = makeJsParser2();
5317
+ let nodesAdded = 0;
5318
+ let edgesAdded = 0;
5319
+ for (const service of services) {
5320
+ const deps = {
5321
+ ...service.pkg.dependencies ?? {},
5322
+ ...service.pkg.devDependencies ?? {}
5323
+ };
5324
+ const hasExpress = deps["express"] !== void 0;
5325
+ const hasFastify = deps["fastify"] !== void 0;
5326
+ const hasNext = deps["next"] !== void 0;
5327
+ if (!hasExpress && !hasFastify && !hasNext) continue;
5328
+ const files = await loadSourceFiles(service.dir);
5329
+ for (const file of files) {
5330
+ if (isTestPath(file.path)) continue;
5331
+ if (!JS_ROUTE_EXTENSIONS.has(import_node_path22.default.extname(file.path))) continue;
5332
+ const relFile = toPosix2(import_node_path22.default.relative(service.dir, file.path));
5333
+ let routes;
5334
+ try {
5335
+ if (hasNext && (isNextAppRouteFile(relFile) || isNextPagesApiFile(relFile))) {
5336
+ routes = nextRoutesFromFile(file.content, relFile, jsParser);
5337
+ } else if (hasExpress || hasFastify) {
5338
+ routes = serverRoutesFromSource(file.content, jsParser, hasExpress, hasFastify);
5339
+ } else {
5340
+ routes = [];
5341
+ }
5342
+ } catch (err) {
5343
+ recordExtractionError("route extraction", file.path, err);
5344
+ continue;
5345
+ }
5346
+ if (routes.length === 0) continue;
5347
+ for (const route of routes) {
5348
+ const rid = (0, import_types11.routeId)(service.pkg.name, route.method, route.pathTemplate);
5349
+ if (!graph.hasNode(rid)) {
5350
+ const node = {
5351
+ id: rid,
5352
+ type: import_types11.NodeType.RouteNode,
5353
+ name: `${route.method} ${route.pathTemplate}`,
5354
+ service: service.pkg.name,
5355
+ method: route.method,
5356
+ pathTemplate: route.pathTemplate,
5357
+ path: relFile,
5358
+ line: route.line,
5359
+ framework: route.framework,
5360
+ discoveredVia: "static"
5361
+ };
5362
+ graph.addNode(rid, node);
5363
+ nodesAdded++;
5364
+ }
5365
+ const containsId = (0, import_types11.extractedEdgeId)(service.node.id, rid, import_types11.EdgeType.CONTAINS);
5366
+ if (!graph.hasEdge(containsId)) {
5367
+ const edge = {
5368
+ id: containsId,
5369
+ source: service.node.id,
5370
+ target: rid,
5371
+ type: import_types11.EdgeType.CONTAINS,
5372
+ provenance: import_types11.Provenance.EXTRACTED,
5373
+ confidence: (0, import_types11.confidenceForExtracted)("structural"),
5374
+ evidence: {
5375
+ file: relFile,
5376
+ line: route.line,
5377
+ snippet: snippet(file.content, route.line)
5378
+ }
5379
+ };
5380
+ graph.addEdgeWithKey(containsId, service.node.id, rid, edge);
5381
+ edgesAdded++;
5382
+ }
5383
+ }
5384
+ }
5385
+ }
5386
+ return { nodesAdded, edgesAdded };
5387
+ }
5388
+
4830
5389
  // src/extract/calls/index.ts
4831
5390
  init_cjs_shims();
4832
- var import_types17 = require("@neat.is/types");
5391
+ var import_types19 = require("@neat.is/types");
4833
5392
 
4834
5393
  // src/extract/calls/http.ts
4835
5394
  init_cjs_shims();
4836
- var import_node_path22 = __toESM(require("path"), 1);
4837
- var import_tree_sitter2 = __toESM(require("tree-sitter"), 1);
4838
- var import_tree_sitter_javascript2 = __toESM(require("tree-sitter-javascript"), 1);
5395
+ var import_node_path23 = __toESM(require("path"), 1);
5396
+ var import_tree_sitter3 = __toESM(require("tree-sitter"), 1);
5397
+ var import_tree_sitter_javascript3 = __toESM(require("tree-sitter-javascript"), 1);
4839
5398
  var import_tree_sitter_python2 = __toESM(require("tree-sitter-python"), 1);
4840
- var import_types11 = require("@neat.is/types");
5399
+ var import_types12 = require("@neat.is/types");
4841
5400
  var STRING_LITERAL_NODE_TYPES = /* @__PURE__ */ new Set(["string_fragment", "string_content"]);
4842
5401
  var JSX_EXTERNAL_LINK_TAGS = /* @__PURE__ */ new Set(["a", "Link", "NavLink", "ExternalLink", "Anchor"]);
4843
5402
  function isInsideJsxExternalLink(node) {
@@ -4865,14 +5424,14 @@ function collectStringLiterals(node, out) {
4865
5424
  if (child) collectStringLiterals(child, out);
4866
5425
  }
4867
5426
  }
4868
- var PARSE_CHUNK2 = 16384;
4869
- function parseSource2(parser, source) {
5427
+ var PARSE_CHUNK3 = 16384;
5428
+ function parseSource3(parser, source) {
4870
5429
  return parser.parse(
4871
- (index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK2)
5430
+ (index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK3)
4872
5431
  );
4873
5432
  }
4874
5433
  function callsFromSource(source, parser, knownHosts) {
4875
- const tree = parseSource2(parser, source);
5434
+ const tree = parseSource3(parser, source);
4876
5435
  const literals = [];
4877
5436
  collectStringLiterals(tree.rootNode, literals);
4878
5437
  const out = [];
@@ -4886,27 +5445,20 @@ function callsFromSource(source, parser, knownHosts) {
4886
5445
  }
4887
5446
  return out;
4888
5447
  }
4889
- function makeJsParser2() {
4890
- const p = new import_tree_sitter2.default();
4891
- p.setLanguage(import_tree_sitter_javascript2.default);
5448
+ function makeJsParser3() {
5449
+ const p = new import_tree_sitter3.default();
5450
+ p.setLanguage(import_tree_sitter_javascript3.default);
4892
5451
  return p;
4893
5452
  }
4894
5453
  function makePyParser2() {
4895
- const p = new import_tree_sitter2.default();
5454
+ const p = new import_tree_sitter3.default();
4896
5455
  p.setLanguage(import_tree_sitter_python2.default);
4897
5456
  return p;
4898
5457
  }
4899
5458
  async function addHttpCallEdges(graph, services) {
4900
- const jsParser = makeJsParser2();
5459
+ const jsParser = makeJsParser3();
4901
5460
  const pyParser = makePyParser2();
4902
- const knownHosts = /* @__PURE__ */ new Set();
4903
- const hostToNodeId = /* @__PURE__ */ new Map();
4904
- for (const service of services) {
4905
- knownHosts.add(import_node_path22.default.basename(service.dir));
4906
- knownHosts.add(service.pkg.name);
4907
- hostToNodeId.set(import_node_path22.default.basename(service.dir), service.node.id);
4908
- hostToNodeId.set(service.pkg.name, service.node.id);
4909
- }
5461
+ const { knownHosts, hostToNodeId } = buildServiceHostIndex(services);
4910
5462
  let nodesAdded = 0;
4911
5463
  let edgesAdded = 0;
4912
5464
  for (const service of services) {
@@ -4914,7 +5466,7 @@ async function addHttpCallEdges(graph, services) {
4914
5466
  const seen = /* @__PURE__ */ new Set();
4915
5467
  for (const file of files) {
4916
5468
  if (isTestPath(file.path)) continue;
4917
- const parser = import_node_path22.default.extname(file.path) === ".py" ? pyParser : jsParser;
5469
+ const parser = import_node_path23.default.extname(file.path) === ".py" ? pyParser : jsParser;
4918
5470
  let sites;
4919
5471
  try {
4920
5472
  sites = callsFromSource(file.content, parser, knownHosts);
@@ -4923,14 +5475,14 @@ async function addHttpCallEdges(graph, services) {
4923
5475
  continue;
4924
5476
  }
4925
5477
  if (sites.length === 0) continue;
4926
- const relFile = toPosix2(import_node_path22.default.relative(service.dir, file.path));
5478
+ const relFile = toPosix2(import_node_path23.default.relative(service.dir, file.path));
4927
5479
  for (const site of sites) {
4928
5480
  const targetId = hostToNodeId.get(site.host);
4929
5481
  if (!targetId || targetId === service.node.id) continue;
4930
5482
  const dedupKey = `${relFile}|${targetId}`;
4931
5483
  if (seen.has(dedupKey)) continue;
4932
5484
  seen.add(dedupKey);
4933
- const confidence = (0, import_types11.confidenceForExtracted)("url-literal-service-target");
5485
+ const confidence = (0, import_types12.confidenceForExtracted)("url-literal-service-target");
4934
5486
  const ev = {
4935
5487
  file: relFile,
4936
5488
  line: site.line,
@@ -4944,25 +5496,25 @@ async function addHttpCallEdges(graph, services) {
4944
5496
  );
4945
5497
  nodesAdded += n;
4946
5498
  edgesAdded += e;
4947
- if (!(0, import_types11.passesExtractedFloor)(confidence)) {
5499
+ if (!(0, import_types12.passesExtractedFloor)(confidence)) {
4948
5500
  noteExtractedDropped({
4949
5501
  source: fileNodeId,
4950
5502
  target: targetId,
4951
- type: import_types11.EdgeType.CALLS,
5503
+ type: import_types12.EdgeType.CALLS,
4952
5504
  confidence,
4953
5505
  confidenceKind: "url-literal-service-target",
4954
5506
  evidence: ev
4955
5507
  });
4956
5508
  continue;
4957
5509
  }
4958
- const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, targetId, import_types11.EdgeType.CALLS);
5510
+ const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, targetId, import_types12.EdgeType.CALLS);
4959
5511
  if (!graph.hasEdge(edgeId)) {
4960
5512
  const edge = {
4961
5513
  id: edgeId,
4962
5514
  source: fileNodeId,
4963
5515
  target: targetId,
4964
- type: import_types11.EdgeType.CALLS,
4965
- provenance: import_types11.Provenance.EXTRACTED,
5516
+ type: import_types12.EdgeType.CALLS,
5517
+ provenance: import_types12.Provenance.EXTRACTED,
4966
5518
  confidence,
4967
5519
  evidence: ev
4968
5520
  };
@@ -4975,10 +5527,279 @@ async function addHttpCallEdges(graph, services) {
4975
5527
  return { nodesAdded, edgesAdded };
4976
5528
  }
4977
5529
 
5530
+ // src/extract/calls/route-match.ts
5531
+ init_cjs_shims();
5532
+ var import_node_path24 = __toESM(require("path"), 1);
5533
+ var import_tree_sitter4 = __toESM(require("tree-sitter"), 1);
5534
+ var import_tree_sitter_javascript4 = __toESM(require("tree-sitter-javascript"), 1);
5535
+ var import_types13 = require("@neat.is/types");
5536
+ var PARSE_CHUNK4 = 16384;
5537
+ function parseSource4(parser, source) {
5538
+ return parser.parse(
5539
+ (index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK4)
5540
+ );
5541
+ }
5542
+ function makeJsParser4() {
5543
+ const p = new import_tree_sitter4.default();
5544
+ p.setLanguage(import_tree_sitter_javascript4.default);
5545
+ return p;
5546
+ }
5547
+ var JS_CLIENT_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx"]);
5548
+ var AXIOS_METHODS = /* @__PURE__ */ new Set(["get", "post", "put", "patch", "delete", "head", "options", "request"]);
5549
+ function walk2(node, visit) {
5550
+ visit(node);
5551
+ for (let i = 0; i < node.namedChildCount; i++) {
5552
+ const child = node.namedChild(i);
5553
+ if (child) walk2(child, visit);
5554
+ }
5555
+ }
5556
+ function reconstructUrl(node) {
5557
+ if (node.type === "string") {
5558
+ for (let i = 0; i < node.namedChildCount; i++) {
5559
+ const child = node.namedChild(i);
5560
+ if (child?.type === "string_fragment") return child.text;
5561
+ }
5562
+ return "";
5563
+ }
5564
+ if (node.type === "template_string") {
5565
+ let out = "";
5566
+ for (let i = 0; i < node.namedChildCount; i++) {
5567
+ const child = node.namedChild(i);
5568
+ if (!child) continue;
5569
+ if (child.type === "string_fragment") out += child.text;
5570
+ else if (child.type === "template_substitution") out += ":param";
5571
+ }
5572
+ if (out.length === 0) {
5573
+ const raw = node.text;
5574
+ return raw.length >= 2 ? raw.slice(1, -1) : "";
5575
+ }
5576
+ return out;
5577
+ }
5578
+ return null;
5579
+ }
5580
+ function methodFromOptions(objNode) {
5581
+ for (let i = 0; i < objNode.namedChildCount; i++) {
5582
+ const pair = objNode.namedChild(i);
5583
+ if (!pair || pair.type !== "pair") continue;
5584
+ const k = pair.childForFieldName("key");
5585
+ const kText = k ? k.type === "string" ? stringText(k) : k.text : null;
5586
+ if (kText !== "method") continue;
5587
+ const v = pair.childForFieldName("value");
5588
+ if (v && (v.type === "string" || v.type === "template_string")) {
5589
+ const s = stringText(v);
5590
+ return s ? s.toUpperCase() : void 0;
5591
+ }
5592
+ }
5593
+ return void 0;
5594
+ }
5595
+ function stringText(node) {
5596
+ if (node.type === "string") {
5597
+ for (let i = 0; i < node.namedChildCount; i++) {
5598
+ const child = node.namedChild(i);
5599
+ if (child?.type === "string_fragment") return child.text;
5600
+ }
5601
+ return "";
5602
+ }
5603
+ return null;
5604
+ }
5605
+ function urlNodeFromConfig(objNode) {
5606
+ for (let i = 0; i < objNode.namedChildCount; i++) {
5607
+ const pair = objNode.namedChild(i);
5608
+ if (!pair || pair.type !== "pair") continue;
5609
+ const k = pair.childForFieldName("key");
5610
+ const kText = k ? k.type === "string" ? stringText(k) : k.text : null;
5611
+ if (kText === "url") return pair.childForFieldName("value");
5612
+ }
5613
+ return null;
5614
+ }
5615
+ function pathOf(urlStr) {
5616
+ try {
5617
+ const candidate = urlStr.startsWith("//") ? `http:${urlStr}` : urlStr;
5618
+ const parsed = new URL(candidate);
5619
+ return parsed.pathname || "/";
5620
+ } catch {
5621
+ return null;
5622
+ }
5623
+ }
5624
+ function matchHost(urlStr, knownHosts) {
5625
+ for (const host of knownHosts) {
5626
+ if (urlMatchesHost(urlStr, host)) return host;
5627
+ }
5628
+ return null;
5629
+ }
5630
+ function clientCallSitesFromSource(source, parser, knownHosts) {
5631
+ const tree = parseSource4(parser, source);
5632
+ const out = [];
5633
+ const push = (urlNode, method, callNode) => {
5634
+ const urlStr = reconstructUrl(urlNode);
5635
+ if (!urlStr) return;
5636
+ const host = matchHost(urlStr, knownHosts);
5637
+ if (!host) return;
5638
+ const p = pathOf(urlStr);
5639
+ if (p === null) return;
5640
+ const line = callNode.startPosition.row + 1;
5641
+ out.push({
5642
+ host,
5643
+ method,
5644
+ pathTemplate: p,
5645
+ line,
5646
+ snippet: snippet(source, line)
5647
+ });
5648
+ };
5649
+ walk2(tree.rootNode, (node) => {
5650
+ if (node.type !== "call_expression") return;
5651
+ const fn = node.childForFieldName("function");
5652
+ if (!fn) return;
5653
+ const args = node.childForFieldName("arguments");
5654
+ const first = args?.namedChild(0);
5655
+ if (!first) return;
5656
+ const fnName = fn.type === "identifier" ? fn.text : fn.type === "member_expression" ? fn.childForFieldName("property")?.text ?? "" : "";
5657
+ if (fn.type === "identifier" && fnName === "fetch") {
5658
+ const opts = args?.namedChild(1);
5659
+ const method = opts && opts.type === "object" ? methodFromOptions(opts) ?? "GET" : "GET";
5660
+ push(first, method, node);
5661
+ return;
5662
+ }
5663
+ if (fn.type === "identifier" && fnName === "axios") {
5664
+ if (first.type === "object") {
5665
+ const urlNode = urlNodeFromConfig(first);
5666
+ if (urlNode) push(urlNode, methodFromOptions(first) ?? "GET", node);
5667
+ } else {
5668
+ const opts = args?.namedChild(1);
5669
+ const method = opts && opts.type === "object" ? methodFromOptions(opts) ?? "GET" : "GET";
5670
+ push(first, method, node);
5671
+ }
5672
+ return;
5673
+ }
5674
+ if (fn.type === "member_expression") {
5675
+ const obj = fn.childForFieldName("object");
5676
+ const objName = obj?.text ?? "";
5677
+ if (objName === "axios" && AXIOS_METHODS.has(fnName)) {
5678
+ if (fnName === "request" && first.type === "object") {
5679
+ const urlNode = urlNodeFromConfig(first);
5680
+ if (urlNode) push(urlNode, methodFromOptions(first) ?? "GET", node);
5681
+ } else {
5682
+ push(first, fnName.toUpperCase(), node);
5683
+ }
5684
+ return;
5685
+ }
5686
+ if ((objName === "http" || objName === "https") && (fnName === "request" || fnName === "get")) {
5687
+ const opts = args?.namedChild(1);
5688
+ const method = opts && opts.type === "object" ? methodFromOptions(opts) ?? (fnName === "get" ? "GET" : "GET") : "GET";
5689
+ push(first, method, node);
5690
+ return;
5691
+ }
5692
+ }
5693
+ });
5694
+ return out;
5695
+ }
5696
+ function buildRouteIndex(graph) {
5697
+ const index = /* @__PURE__ */ new Map();
5698
+ graph.forEachNode((_id, attrs) => {
5699
+ const node = attrs;
5700
+ if (node.type !== import_types13.NodeType.RouteNode) return;
5701
+ const route = attrs;
5702
+ const owner = (0, import_types13.serviceId)(route.service);
5703
+ const entry2 = {
5704
+ method: route.method.toUpperCase(),
5705
+ normalizedPath: normalizePathTemplate(route.pathTemplate),
5706
+ routeNodeId: route.id
5707
+ };
5708
+ const list = index.get(owner);
5709
+ if (list) list.push(entry2);
5710
+ else index.set(owner, [entry2]);
5711
+ });
5712
+ return index;
5713
+ }
5714
+ function findRoute(entries, method, normalizedPath) {
5715
+ return entries.find(
5716
+ (e) => e.normalizedPath === normalizedPath && (e.method === "ALL" || method === void 0 || e.method === method)
5717
+ );
5718
+ }
5719
+ async function addRouteCallEdges(graph, services) {
5720
+ const jsParser = makeJsParser4();
5721
+ const { knownHosts, hostToNodeId } = buildServiceHostIndex(services);
5722
+ const routeIndex = buildRouteIndex(graph);
5723
+ if (routeIndex.size === 0) return { nodesAdded: 0, edgesAdded: 0 };
5724
+ let nodesAdded = 0;
5725
+ let edgesAdded = 0;
5726
+ for (const service of services) {
5727
+ const files = await loadSourceFiles(service.dir);
5728
+ const seen = /* @__PURE__ */ new Set();
5729
+ for (const file of files) {
5730
+ if (isTestPath(file.path)) continue;
5731
+ if (!JS_CLIENT_EXTENSIONS.has(import_node_path24.default.extname(file.path))) continue;
5732
+ let sites;
5733
+ try {
5734
+ sites = clientCallSitesFromSource(file.content, jsParser, knownHosts);
5735
+ } catch (err) {
5736
+ recordExtractionError("route-match call extraction", file.path, err);
5737
+ continue;
5738
+ }
5739
+ if (sites.length === 0) continue;
5740
+ const relFile = toPosix2(import_node_path24.default.relative(service.dir, file.path));
5741
+ for (const site of sites) {
5742
+ const serverServiceId = hostToNodeId.get(site.host);
5743
+ if (!serverServiceId || serverServiceId === service.node.id) continue;
5744
+ const entries = routeIndex.get(serverServiceId);
5745
+ if (!entries) continue;
5746
+ const normalizedPath = normalizePathTemplate(site.pathTemplate);
5747
+ const match = findRoute(entries, site.method, normalizedPath);
5748
+ if (!match) continue;
5749
+ const dedupKey = `${relFile}|${match.routeNodeId}`;
5750
+ if (seen.has(dedupKey)) continue;
5751
+ seen.add(dedupKey);
5752
+ const { fileNodeId, nodesAdded: n, edgesAdded: e } = ensureFileNode(
5753
+ graph,
5754
+ service.pkg.name,
5755
+ service.node.id,
5756
+ relFile
5757
+ );
5758
+ nodesAdded += n;
5759
+ edgesAdded += e;
5760
+ const confidence = (0, import_types13.confidenceForExtracted)("verified-call-site");
5761
+ const ev = {
5762
+ file: relFile,
5763
+ line: site.line,
5764
+ snippet: site.snippet,
5765
+ method: site.method ?? match.method,
5766
+ pathTemplate: site.pathTemplate
5767
+ };
5768
+ if (!(0, import_types13.passesExtractedFloor)(confidence)) {
5769
+ noteExtractedDropped({
5770
+ source: fileNodeId,
5771
+ target: match.routeNodeId,
5772
+ type: import_types13.EdgeType.CALLS,
5773
+ confidence,
5774
+ confidenceKind: "verified-call-site",
5775
+ evidence: ev
5776
+ });
5777
+ continue;
5778
+ }
5779
+ const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, match.routeNodeId, import_types13.EdgeType.CALLS);
5780
+ if (!graph.hasEdge(edgeId)) {
5781
+ const edge = {
5782
+ id: edgeId,
5783
+ source: fileNodeId,
5784
+ target: match.routeNodeId,
5785
+ type: import_types13.EdgeType.CALLS,
5786
+ provenance: import_types13.Provenance.EXTRACTED,
5787
+ confidence,
5788
+ evidence: ev
5789
+ };
5790
+ graph.addEdgeWithKey(edgeId, fileNodeId, match.routeNodeId, edge);
5791
+ edgesAdded++;
5792
+ }
5793
+ }
5794
+ }
5795
+ }
5796
+ return { nodesAdded, edgesAdded };
5797
+ }
5798
+
4978
5799
  // src/extract/calls/kafka.ts
4979
5800
  init_cjs_shims();
4980
- var import_node_path23 = __toESM(require("path"), 1);
4981
- var import_types12 = require("@neat.is/types");
5801
+ var import_node_path25 = __toESM(require("path"), 1);
5802
+ var import_types14 = require("@neat.is/types");
4982
5803
  var PRODUCER_TOPIC_RE = /(?:producer|kafkaProducer)[\s\S]{0,40}?\.send\s*\(\s*\{[\s\S]{0,200}?topic\s*:\s*['"`]([^'"`]+)['"`]/g;
4983
5804
  var CONSUMER_TOPIC_RE = /(?:consumer|kafkaConsumer)[\s\S]{0,40}?\.(?:subscribe|run)\s*\(\s*\{[\s\S]{0,200}?topic[s]?\s*:\s*(?:\[\s*)?['"`]([^'"`]+)['"`]/g;
4984
5805
  function findAll(re, text) {
@@ -4999,7 +5820,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
4999
5820
  seen.add(key);
5000
5821
  const line = lineOf(file.content, topic);
5001
5822
  out.push({
5002
- infraId: (0, import_types12.infraId)("kafka-topic", topic),
5823
+ infraId: (0, import_types14.infraId)("kafka-topic", topic),
5003
5824
  name: topic,
5004
5825
  kind: "kafka-topic",
5005
5826
  edgeType,
@@ -5008,7 +5829,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
5008
5829
  // tier (ADR-066).
5009
5830
  confidenceKind: "verified-call-site",
5010
5831
  evidence: {
5011
- file: import_node_path23.default.relative(serviceDir, file.path),
5832
+ file: import_node_path25.default.relative(serviceDir, file.path),
5012
5833
  line,
5013
5834
  snippet: snippet(file.content, line)
5014
5835
  }
@@ -5021,8 +5842,8 @@ function kafkaEndpointsFromFile(file, serviceDir) {
5021
5842
 
5022
5843
  // src/extract/calls/redis.ts
5023
5844
  init_cjs_shims();
5024
- var import_node_path24 = __toESM(require("path"), 1);
5025
- var import_types13 = require("@neat.is/types");
5845
+ var import_node_path26 = __toESM(require("path"), 1);
5846
+ var import_types15 = require("@neat.is/types");
5026
5847
  var REDIS_URL_RE = /redis(?:s)?:\/\/(?:[^@'"`\s]+@)?([^:/'"`\s]+)(?::(\d+))?/g;
5027
5848
  function redisEndpointsFromFile(file, serviceDir) {
5028
5849
  const out = [];
@@ -5035,7 +5856,7 @@ function redisEndpointsFromFile(file, serviceDir) {
5035
5856
  seen.add(host);
5036
5857
  const line = lineOf(file.content, host);
5037
5858
  out.push({
5038
- infraId: (0, import_types13.infraId)("redis", host),
5859
+ infraId: (0, import_types15.infraId)("redis", host),
5039
5860
  name: host,
5040
5861
  kind: "redis",
5041
5862
  edgeType: "CALLS",
@@ -5044,7 +5865,7 @@ function redisEndpointsFromFile(file, serviceDir) {
5044
5865
  // support tier (ADR-066).
5045
5866
  confidenceKind: "url-with-structural-support",
5046
5867
  evidence: {
5047
- file: import_node_path24.default.relative(serviceDir, file.path),
5868
+ file: import_node_path26.default.relative(serviceDir, file.path),
5048
5869
  line,
5049
5870
  snippet: snippet(file.content, line)
5050
5871
  }
@@ -5055,8 +5876,8 @@ function redisEndpointsFromFile(file, serviceDir) {
5055
5876
 
5056
5877
  // src/extract/calls/aws.ts
5057
5878
  init_cjs_shims();
5058
- var import_node_path25 = __toESM(require("path"), 1);
5059
- var import_types14 = require("@neat.is/types");
5879
+ var import_node_path27 = __toESM(require("path"), 1);
5880
+ var import_types16 = require("@neat.is/types");
5060
5881
  var S3_BUCKET_RE = /Bucket\s*:\s*['"`]([^'"`]+)['"`]/g;
5061
5882
  var DYNAMO_TABLE_RE = /TableName\s*:\s*['"`]([^'"`]+)['"`]/g;
5062
5883
  function hasMarker(text, markers) {
@@ -5080,7 +5901,7 @@ function awsEndpointsFromFile(file, serviceDir) {
5080
5901
  seen.add(key);
5081
5902
  const line = lineOf(file.content, name);
5082
5903
  out.push({
5083
- infraId: (0, import_types14.infraId)(kind, name),
5904
+ infraId: (0, import_types16.infraId)(kind, name),
5084
5905
  name,
5085
5906
  kind,
5086
5907
  edgeType: "CALLS",
@@ -5089,7 +5910,7 @@ function awsEndpointsFromFile(file, serviceDir) {
5089
5910
  // (ADR-066).
5090
5911
  confidenceKind: "verified-call-site",
5091
5912
  evidence: {
5092
- file: import_node_path25.default.relative(serviceDir, file.path),
5913
+ file: import_node_path27.default.relative(serviceDir, file.path),
5093
5914
  line,
5094
5915
  snippet: snippet(file.content, line)
5095
5916
  }
@@ -5114,8 +5935,8 @@ function awsEndpointsFromFile(file, serviceDir) {
5114
5935
 
5115
5936
  // src/extract/calls/grpc.ts
5116
5937
  init_cjs_shims();
5117
- var import_node_path26 = __toESM(require("path"), 1);
5118
- var import_types15 = require("@neat.is/types");
5938
+ var import_node_path28 = __toESM(require("path"), 1);
5939
+ var import_types17 = require("@neat.is/types");
5119
5940
  var GRPC_CLIENT_RE = /new\s+([A-Z][A-Za-z0-9_]*)Client\s*\(\s*['"`]?([^,'"`)]+)?/g;
5120
5941
  var AWS_SDK_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@aws-sdk\/client-([a-z0-9-]+)['"`]/g;
5121
5942
  var GRPC_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@grpc\/grpc-js['"`]|_grpc_pb['"`]/;
@@ -5164,7 +5985,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
5164
5985
  const { kind } = classified;
5165
5986
  const line = lineOf(file.content, m[0]);
5166
5987
  out.push({
5167
- infraId: (0, import_types15.infraId)(kind, name),
5988
+ infraId: (0, import_types17.infraId)(kind, name),
5168
5989
  name,
5169
5990
  kind,
5170
5991
  edgeType: "CALLS",
@@ -5173,7 +5994,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
5173
5994
  // tier (ADR-066).
5174
5995
  confidenceKind: "verified-call-site",
5175
5996
  evidence: {
5176
- file: import_node_path26.default.relative(serviceDir, file.path),
5997
+ file: import_node_path28.default.relative(serviceDir, file.path),
5177
5998
  line,
5178
5999
  snippet: snippet(file.content, line)
5179
6000
  }
@@ -5184,8 +6005,8 @@ function grpcEndpointsFromFile(file, serviceDir) {
5184
6005
 
5185
6006
  // src/extract/calls/supabase.ts
5186
6007
  init_cjs_shims();
5187
- var import_node_path27 = __toESM(require("path"), 1);
5188
- var import_types16 = require("@neat.is/types");
6008
+ var import_node_path29 = __toESM(require("path"), 1);
6009
+ var import_types18 = require("@neat.is/types");
5189
6010
  var SUPABASE_JS_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@supabase\/supabase-js['"`]/;
5190
6011
  var SUPABASE_SSR_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@supabase\/ssr['"`]/;
5191
6012
  var SUPABASE_CLIENT_RE = /\b(createClient|createServerClient|createBrowserClient)\s*\(\s*(?:['"`]([^'"`]*)['"`])?/g;
@@ -5223,7 +6044,7 @@ function supabaseEndpointsFromFile(file, serviceDir) {
5223
6044
  seen.add(name);
5224
6045
  const line = lineOf(file.content, m[0]);
5225
6046
  out.push({
5226
- infraId: (0, import_types16.infraId)("supabase", name),
6047
+ infraId: (0, import_types18.infraId)("supabase", name),
5227
6048
  name,
5228
6049
  kind: "supabase",
5229
6050
  edgeType: "CALLS",
@@ -5233,7 +6054,7 @@ function supabaseEndpointsFromFile(file, serviceDir) {
5233
6054
  // tier (ADR-066), the same grade aws.ts / grpc.ts emit at.
5234
6055
  confidenceKind: "verified-call-site",
5235
6056
  evidence: {
5236
- file: import_node_path27.default.relative(serviceDir, file.path),
6057
+ file: import_node_path29.default.relative(serviceDir, file.path),
5237
6058
  line,
5238
6059
  snippet: snippet(file.content, line)
5239
6060
  }
@@ -5246,11 +6067,11 @@ function supabaseEndpointsFromFile(file, serviceDir) {
5246
6067
  function edgeTypeFromEndpoint(ep) {
5247
6068
  switch (ep.edgeType) {
5248
6069
  case "PUBLISHES_TO":
5249
- return import_types17.EdgeType.PUBLISHES_TO;
6070
+ return import_types19.EdgeType.PUBLISHES_TO;
5250
6071
  case "CONSUMES_FROM":
5251
- return import_types17.EdgeType.CONSUMES_FROM;
6072
+ return import_types19.EdgeType.CONSUMES_FROM;
5252
6073
  default:
5253
- return import_types17.EdgeType.CALLS;
6074
+ return import_types19.EdgeType.CALLS;
5254
6075
  }
5255
6076
  }
5256
6077
  function isAwsKind(kind) {
@@ -5278,7 +6099,7 @@ async function addExternalEndpointEdges(graph, services) {
5278
6099
  if (!graph.hasNode(ep.infraId)) {
5279
6100
  const node = {
5280
6101
  id: ep.infraId,
5281
- type: import_types17.NodeType.InfraNode,
6102
+ type: import_types19.NodeType.InfraNode,
5282
6103
  name: ep.name,
5283
6104
  // #238 — `aws-*` covers AWS-SDK client kinds (aws-s3, aws-dynamodb,
5284
6105
  // aws-cognito-identity-provider, …); `s3-` / `dynamodb-` cover the
@@ -5290,7 +6111,7 @@ async function addExternalEndpointEdges(graph, services) {
5290
6111
  nodesAdded++;
5291
6112
  }
5292
6113
  const edgeType = edgeTypeFromEndpoint(ep);
5293
- const confidence = (0, import_types17.confidenceForExtracted)(ep.confidenceKind);
6114
+ const confidence = (0, import_types19.confidenceForExtracted)(ep.confidenceKind);
5294
6115
  const relFile = toPosix2(ep.evidence.file);
5295
6116
  const { fileNodeId, nodesAdded: n, edgesAdded: e } = ensureFileNode(
5296
6117
  graph,
@@ -5300,7 +6121,7 @@ async function addExternalEndpointEdges(graph, services) {
5300
6121
  );
5301
6122
  nodesAdded += n;
5302
6123
  edgesAdded += e;
5303
- if (!(0, import_types17.passesExtractedFloor)(confidence)) {
6124
+ if (!(0, import_types19.passesExtractedFloor)(confidence)) {
5304
6125
  noteExtractedDropped({
5305
6126
  source: fileNodeId,
5306
6127
  target: ep.infraId,
@@ -5320,7 +6141,7 @@ async function addExternalEndpointEdges(graph, services) {
5320
6141
  source: fileNodeId,
5321
6142
  target: ep.infraId,
5322
6143
  type: edgeType,
5323
- provenance: import_types17.Provenance.EXTRACTED,
6144
+ provenance: import_types19.Provenance.EXTRACTED,
5324
6145
  confidence,
5325
6146
  evidence: ep.evidence
5326
6147
  };
@@ -5334,9 +6155,10 @@ async function addExternalEndpointEdges(graph, services) {
5334
6155
  async function addCallEdges(graph, services) {
5335
6156
  const http = await addHttpCallEdges(graph, services);
5336
6157
  const ext = await addExternalEndpointEdges(graph, services);
6158
+ const routes = await addRouteCallEdges(graph, services);
5337
6159
  return {
5338
- nodesAdded: http.nodesAdded + ext.nodesAdded,
5339
- edgesAdded: http.edgesAdded + ext.edgesAdded
6160
+ nodesAdded: http.nodesAdded + ext.nodesAdded + routes.nodesAdded,
6161
+ edgesAdded: http.edgesAdded + ext.edgesAdded + routes.edgesAdded
5340
6162
  };
5341
6163
  }
5342
6164
 
@@ -5345,16 +6167,16 @@ init_cjs_shims();
5345
6167
 
5346
6168
  // src/extract/infra/docker-compose.ts
5347
6169
  init_cjs_shims();
5348
- var import_node_path28 = __toESM(require("path"), 1);
5349
- var import_types19 = require("@neat.is/types");
6170
+ var import_node_path30 = __toESM(require("path"), 1);
6171
+ var import_types21 = require("@neat.is/types");
5350
6172
 
5351
6173
  // src/extract/infra/shared.ts
5352
6174
  init_cjs_shims();
5353
- var import_types18 = require("@neat.is/types");
6175
+ var import_types20 = require("@neat.is/types");
5354
6176
  function makeInfraNode(kind, name, provider = "self", extras) {
5355
6177
  return {
5356
- id: (0, import_types18.infraId)(kind, name),
5357
- type: import_types18.NodeType.InfraNode,
6178
+ id: (0, import_types20.infraId)(kind, name),
6179
+ type: import_types20.NodeType.InfraNode,
5358
6180
  name,
5359
6181
  provider,
5360
6182
  kind,
@@ -5383,7 +6205,7 @@ function dependsOnList(value) {
5383
6205
  }
5384
6206
  function serviceNameToServiceNode(name, services) {
5385
6207
  for (const s of services) {
5386
- if (s.node.name === name || import_node_path28.default.basename(s.dir) === name) return s.node.id;
6208
+ if (s.node.name === name || import_node_path30.default.basename(s.dir) === name) return s.node.id;
5387
6209
  }
5388
6210
  return null;
5389
6211
  }
@@ -5392,7 +6214,7 @@ async function addComposeInfra(graph, scanPath, services) {
5392
6214
  let edgesAdded = 0;
5393
6215
  let composePath = null;
5394
6216
  for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
5395
- const abs = import_node_path28.default.join(scanPath, name);
6217
+ const abs = import_node_path30.default.join(scanPath, name);
5396
6218
  if (await exists(abs)) {
5397
6219
  composePath = abs;
5398
6220
  break;
@@ -5405,13 +6227,13 @@ async function addComposeInfra(graph, scanPath, services) {
5405
6227
  } catch (err) {
5406
6228
  recordExtractionError(
5407
6229
  "infra docker-compose",
5408
- import_node_path28.default.relative(scanPath, composePath),
6230
+ import_node_path30.default.relative(scanPath, composePath),
5409
6231
  err
5410
6232
  );
5411
6233
  return { nodesAdded, edgesAdded };
5412
6234
  }
5413
6235
  if (!compose?.services) return { nodesAdded, edgesAdded };
5414
- const evidenceFile = import_node_path28.default.relative(scanPath, composePath).split(import_node_path28.default.sep).join("/");
6236
+ const evidenceFile = import_node_path30.default.relative(scanPath, composePath).split(import_node_path30.default.sep).join("/");
5415
6237
  const composeNameToNodeId = /* @__PURE__ */ new Map();
5416
6238
  for (const [composeName, svc] of Object.entries(compose.services)) {
5417
6239
  const matchedServiceId = serviceNameToServiceNode(composeName, services);
@@ -5433,15 +6255,15 @@ async function addComposeInfra(graph, scanPath, services) {
5433
6255
  for (const dep of dependsOnList(svc.depends_on)) {
5434
6256
  const targetId = composeNameToNodeId.get(dep);
5435
6257
  if (!targetId) continue;
5436
- const edgeId = (0, import_types4.extractedEdgeId)(sourceId, targetId, import_types19.EdgeType.DEPENDS_ON);
6258
+ const edgeId = (0, import_types4.extractedEdgeId)(sourceId, targetId, import_types21.EdgeType.DEPENDS_ON);
5437
6259
  if (graph.hasEdge(edgeId)) continue;
5438
6260
  const edge = {
5439
6261
  id: edgeId,
5440
6262
  source: sourceId,
5441
6263
  target: targetId,
5442
- type: import_types19.EdgeType.DEPENDS_ON,
5443
- provenance: import_types19.Provenance.EXTRACTED,
5444
- confidence: (0, import_types19.confidenceForExtracted)("structural"),
6264
+ type: import_types21.EdgeType.DEPENDS_ON,
6265
+ provenance: import_types21.Provenance.EXTRACTED,
6266
+ confidence: (0, import_types21.confidenceForExtracted)("structural"),
5445
6267
  evidence: { file: evidenceFile }
5446
6268
  };
5447
6269
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -5453,9 +6275,9 @@ async function addComposeInfra(graph, scanPath, services) {
5453
6275
 
5454
6276
  // src/extract/infra/dockerfile.ts
5455
6277
  init_cjs_shims();
5456
- var import_node_path29 = __toESM(require("path"), 1);
6278
+ var import_node_path31 = __toESM(require("path"), 1);
5457
6279
  var import_node_fs15 = require("fs");
5458
- var import_types20 = require("@neat.is/types");
6280
+ var import_types22 = require("@neat.is/types");
5459
6281
  function readDockerfile(content) {
5460
6282
  let image = null;
5461
6283
  const ports = [];
@@ -5484,7 +6306,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
5484
6306
  let nodesAdded = 0;
5485
6307
  let edgesAdded = 0;
5486
6308
  for (const service of services) {
5487
- const dockerfilePath = import_node_path29.default.join(service.dir, "Dockerfile");
6309
+ const dockerfilePath = import_node_path31.default.join(service.dir, "Dockerfile");
5488
6310
  if (!await exists(dockerfilePath)) continue;
5489
6311
  let content;
5490
6312
  try {
@@ -5492,7 +6314,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
5492
6314
  } catch (err) {
5493
6315
  recordExtractionError(
5494
6316
  "infra dockerfile",
5495
- import_node_path29.default.relative(scanPath, dockerfilePath),
6317
+ import_node_path31.default.relative(scanPath, dockerfilePath),
5496
6318
  err
5497
6319
  );
5498
6320
  continue;
@@ -5504,8 +6326,8 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
5504
6326
  graph.addNode(node.id, node);
5505
6327
  nodesAdded++;
5506
6328
  }
5507
- const relDockerfile = toPosix2(import_node_path29.default.relative(service.dir, dockerfilePath));
5508
- const evidenceFile = toPosix2(import_node_path29.default.relative(scanPath, dockerfilePath));
6329
+ const relDockerfile = toPosix2(import_node_path31.default.relative(service.dir, dockerfilePath));
6330
+ const evidenceFile = toPosix2(import_node_path31.default.relative(scanPath, dockerfilePath));
5509
6331
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
5510
6332
  graph,
5511
6333
  service.pkg.name,
@@ -5514,15 +6336,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
5514
6336
  );
5515
6337
  nodesAdded += fn;
5516
6338
  edgesAdded += fe;
5517
- const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, node.id, import_types20.EdgeType.RUNS_ON);
6339
+ const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, node.id, import_types22.EdgeType.RUNS_ON);
5518
6340
  if (!graph.hasEdge(edgeId)) {
5519
6341
  const edge = {
5520
6342
  id: edgeId,
5521
6343
  source: fileNodeId,
5522
6344
  target: node.id,
5523
- type: import_types20.EdgeType.RUNS_ON,
5524
- provenance: import_types20.Provenance.EXTRACTED,
5525
- confidence: (0, import_types20.confidenceForExtracted)("structural"),
6345
+ type: import_types22.EdgeType.RUNS_ON,
6346
+ provenance: import_types22.Provenance.EXTRACTED,
6347
+ confidence: (0, import_types22.confidenceForExtracted)("structural"),
5526
6348
  evidence: {
5527
6349
  file: evidenceFile,
5528
6350
  ...facts.entrypoint ? { snippet: facts.entrypoint.slice(0, 120) } : {}
@@ -5537,15 +6359,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
5537
6359
  graph.addNode(portNode.id, portNode);
5538
6360
  nodesAdded++;
5539
6361
  }
5540
- const portEdgeId = (0, import_types4.extractedEdgeId)(fileNodeId, portNode.id, import_types20.EdgeType.CONNECTS_TO);
6362
+ const portEdgeId = (0, import_types4.extractedEdgeId)(fileNodeId, portNode.id, import_types22.EdgeType.CONNECTS_TO);
5541
6363
  if (graph.hasEdge(portEdgeId)) continue;
5542
6364
  const portEdge = {
5543
6365
  id: portEdgeId,
5544
6366
  source: fileNodeId,
5545
6367
  target: portNode.id,
5546
- type: import_types20.EdgeType.CONNECTS_TO,
5547
- provenance: import_types20.Provenance.EXTRACTED,
5548
- confidence: (0, import_types20.confidenceForExtracted)("structural"),
6368
+ type: import_types22.EdgeType.CONNECTS_TO,
6369
+ provenance: import_types22.Provenance.EXTRACTED,
6370
+ confidence: (0, import_types22.confidenceForExtracted)("structural"),
5549
6371
  evidence: { file: evidenceFile, snippet: `EXPOSE ${port}` }
5550
6372
  };
5551
6373
  graph.addEdgeWithKey(portEdgeId, portEdge.source, portEdge.target, portEdge);
@@ -5558,8 +6380,8 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
5558
6380
  // src/extract/infra/terraform.ts
5559
6381
  init_cjs_shims();
5560
6382
  var import_node_fs16 = require("fs");
5561
- var import_node_path30 = __toESM(require("path"), 1);
5562
- var import_types21 = require("@neat.is/types");
6383
+ var import_node_path32 = __toESM(require("path"), 1);
6384
+ var import_types23 = require("@neat.is/types");
5563
6385
  var RESOURCE_RE = /resource\s+"(aws_[A-Za-z0-9_]+)"\s+"([A-Za-z0-9_-]+)"/g;
5564
6386
  var REFERENCE_RE = /(?<![\w.])(aws_[A-Za-z0-9_]+)\.([A-Za-z0-9_-]+)/g;
5565
6387
  async function walkTfFiles(start, depth = 0, max = 5) {
@@ -5569,11 +6391,11 @@ async function walkTfFiles(start, depth = 0, max = 5) {
5569
6391
  for (const entry2 of entries) {
5570
6392
  if (entry2.isDirectory()) {
5571
6393
  if (IGNORED_DIRS.has(entry2.name) || entry2.name === ".terraform") continue;
5572
- const child = import_node_path30.default.join(start, entry2.name);
6394
+ const child = import_node_path32.default.join(start, entry2.name);
5573
6395
  if (await isPythonVenvDir(child)) continue;
5574
6396
  out.push(...await walkTfFiles(child, depth + 1, max));
5575
6397
  } else if (entry2.isFile() && entry2.name.endsWith(".tf")) {
5576
- out.push(import_node_path30.default.join(start, entry2.name));
6398
+ out.push(import_node_path32.default.join(start, entry2.name));
5577
6399
  }
5578
6400
  }
5579
6401
  return out;
@@ -5605,7 +6427,7 @@ async function addTerraformResources(graph, scanPath) {
5605
6427
  const files = await walkTfFiles(scanPath);
5606
6428
  for (const file of files) {
5607
6429
  const content = await import_node_fs16.promises.readFile(file, "utf8");
5608
- const evidenceFile = toPosix2(import_node_path30.default.relative(scanPath, file));
6430
+ const evidenceFile = toPosix2(import_node_path32.default.relative(scanPath, file));
5609
6431
  const resources = [];
5610
6432
  const byKey = /* @__PURE__ */ new Map();
5611
6433
  RESOURCE_RE.lastIndex = 0;
@@ -5640,16 +6462,16 @@ async function addTerraformResources(graph, scanPath) {
5640
6462
  if (!target) continue;
5641
6463
  if (seen.has(target.nodeId)) continue;
5642
6464
  seen.add(target.nodeId);
5643
- const edgeId = (0, import_types4.extractedEdgeId)(resource.nodeId, target.nodeId, import_types21.EdgeType.DEPENDS_ON);
6465
+ const edgeId = (0, import_types4.extractedEdgeId)(resource.nodeId, target.nodeId, import_types23.EdgeType.DEPENDS_ON);
5644
6466
  if (graph.hasEdge(edgeId)) continue;
5645
6467
  const line = lineAt(content, resource.bodyOffset + ref.index);
5646
6468
  const edge = {
5647
6469
  id: edgeId,
5648
6470
  source: resource.nodeId,
5649
6471
  target: target.nodeId,
5650
- type: import_types21.EdgeType.DEPENDS_ON,
5651
- provenance: import_types21.Provenance.EXTRACTED,
5652
- confidence: (0, import_types21.confidenceForExtracted)("structural"),
6472
+ type: import_types23.EdgeType.DEPENDS_ON,
6473
+ provenance: import_types23.Provenance.EXTRACTED,
6474
+ confidence: (0, import_types23.confidenceForExtracted)("structural"),
5653
6475
  evidence: { file: evidenceFile, line, snippet: key }
5654
6476
  };
5655
6477
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -5663,7 +6485,7 @@ async function addTerraformResources(graph, scanPath) {
5663
6485
  // src/extract/infra/k8s.ts
5664
6486
  init_cjs_shims();
5665
6487
  var import_node_fs17 = require("fs");
5666
- var import_node_path31 = __toESM(require("path"), 1);
6488
+ var import_node_path33 = __toESM(require("path"), 1);
5667
6489
  var import_yaml3 = require("yaml");
5668
6490
  var K8S_KIND_TO_INFRA_KIND = {
5669
6491
  Service: "k8s-service",
@@ -5681,11 +6503,11 @@ async function walkYamlFiles2(start, depth = 0, max = 5) {
5681
6503
  for (const entry2 of entries) {
5682
6504
  if (entry2.isDirectory()) {
5683
6505
  if (IGNORED_DIRS.has(entry2.name)) continue;
5684
- const child = import_node_path31.default.join(start, entry2.name);
6506
+ const child = import_node_path33.default.join(start, entry2.name);
5685
6507
  if (await isPythonVenvDir(child)) continue;
5686
6508
  out.push(...await walkYamlFiles2(child, depth + 1, max));
5687
- } else if (entry2.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path31.default.extname(entry2.name))) {
5688
- out.push(import_node_path31.default.join(start, entry2.name));
6509
+ } else if (entry2.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path33.default.extname(entry2.name))) {
6510
+ out.push(import_node_path33.default.join(start, entry2.name));
5689
6511
  }
5690
6512
  }
5691
6513
  return out;
@@ -5729,17 +6551,17 @@ async function addInfra(graph, scanPath, services) {
5729
6551
  }
5730
6552
 
5731
6553
  // src/extract/index.ts
5732
- var import_node_path33 = __toESM(require("path"), 1);
6554
+ var import_node_path35 = __toESM(require("path"), 1);
5733
6555
 
5734
6556
  // src/extract/retire.ts
5735
6557
  init_cjs_shims();
5736
6558
  var import_node_fs18 = require("fs");
5737
- var import_node_path32 = __toESM(require("path"), 1);
5738
- var import_types22 = require("@neat.is/types");
6559
+ var import_node_path34 = __toESM(require("path"), 1);
6560
+ var import_types24 = require("@neat.is/types");
5739
6561
  function dropOrphanedFileNodes(graph) {
5740
6562
  const orphans = [];
5741
6563
  graph.forEachNode((id, attrs) => {
5742
- if (attrs.type !== import_types22.NodeType.FileNode) return;
6564
+ if (attrs.type !== import_types24.NodeType.FileNode) return;
5743
6565
  if (graph.inboundEdges(id).length === 0 && graph.outboundEdges(id).length === 0) {
5744
6566
  orphans.push(id);
5745
6567
  }
@@ -5752,14 +6574,14 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
5752
6574
  const bases = [scanPath, ...serviceDirs];
5753
6575
  graph.forEachEdge((id, attrs) => {
5754
6576
  const edge = attrs;
5755
- if (edge.provenance !== import_types22.Provenance.EXTRACTED) return;
6577
+ if (edge.provenance !== import_types24.Provenance.EXTRACTED) return;
5756
6578
  const evidenceFile = edge.evidence?.file;
5757
6579
  if (!evidenceFile) return;
5758
- if (import_node_path32.default.isAbsolute(evidenceFile)) {
6580
+ if (import_node_path34.default.isAbsolute(evidenceFile)) {
5759
6581
  if (!(0, import_node_fs18.existsSync)(evidenceFile)) toDrop.push(id);
5760
6582
  return;
5761
6583
  }
5762
- const found = bases.some((base) => (0, import_node_fs18.existsSync)(import_node_path32.default.join(base, evidenceFile)));
6584
+ const found = bases.some((base) => (0, import_node_fs18.existsSync)(import_node_path34.default.join(base, evidenceFile)));
5763
6585
  if (!found) toDrop.push(id);
5764
6586
  });
5765
6587
  for (const id of toDrop) graph.dropEdge(id);
@@ -5778,6 +6600,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
5778
6600
  const importGraph = await addImports(graph, services);
5779
6601
  const phase2 = await addDatabasesAndCompat(graph, services, scanPath);
5780
6602
  const phase3 = await addConfigNodes(graph, services, scanPath);
6603
+ const routePhase = await addRoutes(graph, services);
5781
6604
  const phase4 = await addCallEdges(graph, services);
5782
6605
  const phase5 = await addInfra(graph, scanPath, services);
5783
6606
  const ghostsRetired = retireExtractedEdgesByMissingFile(
@@ -5799,7 +6622,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
5799
6622
  }
5800
6623
  const droppedEntries = drainDroppedExtracted();
5801
6624
  if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
5802
- const rejectedPath = import_node_path33.default.join(import_node_path33.default.dirname(opts.errorsPath), "rejected.ndjson");
6625
+ const rejectedPath = import_node_path35.default.join(import_node_path35.default.dirname(opts.errorsPath), "rejected.ndjson");
5803
6626
  try {
5804
6627
  await writeRejectedExtracted(droppedEntries, rejectedPath);
5805
6628
  } catch (err) {
@@ -5809,8 +6632,8 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
5809
6632
  }
5810
6633
  }
5811
6634
  const result = {
5812
- nodesAdded: phase1Nodes + fileEnum.nodesAdded + importGraph.nodesAdded + phase2.nodesAdded + phase3.nodesAdded + phase4.nodesAdded + phase5.nodesAdded,
5813
- edgesAdded: fileEnum.edgesAdded + importGraph.edgesAdded + phase2.edgesAdded + phase3.edgesAdded + phase4.edgesAdded + phase5.edgesAdded,
6635
+ nodesAdded: phase1Nodes + fileEnum.nodesAdded + importGraph.nodesAdded + phase2.nodesAdded + phase3.nodesAdded + routePhase.nodesAdded + phase4.nodesAdded + phase5.nodesAdded,
6636
+ edgesAdded: fileEnum.edgesAdded + importGraph.edgesAdded + phase2.edgesAdded + phase3.edgesAdded + routePhase.edgesAdded + phase4.edgesAdded + phase5.edgesAdded,
5814
6637
  frontiersPromoted,
5815
6638
  extractionErrors: errorEntries.length,
5816
6639
  errorEntries,
@@ -5834,8 +6657,8 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
5834
6657
  // src/persist.ts
5835
6658
  init_cjs_shims();
5836
6659
  var import_node_fs19 = require("fs");
5837
- var import_node_path34 = __toESM(require("path"), 1);
5838
- var import_types23 = require("@neat.is/types");
6660
+ var import_node_path36 = __toESM(require("path"), 1);
6661
+ var import_types25 = require("@neat.is/types");
5839
6662
  var SCHEMA_VERSION = 4;
5840
6663
  function migrateV1ToV2(payload) {
5841
6664
  const nodes = payload.graph.nodes;
@@ -5857,12 +6680,12 @@ function migrateV2ToV3(payload) {
5857
6680
  for (const edge of edges) {
5858
6681
  const attrs = edge.attributes;
5859
6682
  if (!attrs || attrs.provenance !== "FRONTIER") continue;
5860
- attrs.provenance = import_types23.Provenance.OBSERVED;
6683
+ attrs.provenance = import_types25.Provenance.OBSERVED;
5861
6684
  const type = typeof attrs.type === "string" ? attrs.type : void 0;
5862
6685
  const source = typeof attrs.source === "string" ? attrs.source : void 0;
5863
6686
  const target = typeof attrs.target === "string" ? attrs.target : void 0;
5864
6687
  if (type && source && target) {
5865
- const newId = (0, import_types23.observedEdgeId)(source, target, type);
6688
+ const newId = (0, import_types25.observedEdgeId)(source, target, type);
5866
6689
  attrs.id = newId;
5867
6690
  if (edge.key) edge.key = newId;
5868
6691
  }
@@ -5871,7 +6694,7 @@ function migrateV2ToV3(payload) {
5871
6694
  return { ...payload, schemaVersion: 3 };
5872
6695
  }
5873
6696
  async function ensureDir(filePath) {
5874
- await import_node_fs19.promises.mkdir(import_node_path34.default.dirname(filePath), { recursive: true });
6697
+ await import_node_fs19.promises.mkdir(import_node_path36.default.dirname(filePath), { recursive: true });
5875
6698
  }
5876
6699
  async function saveGraphToDisk(graph, outPath) {
5877
6700
  await ensureDir(outPath);
@@ -5952,23 +6775,23 @@ function startPersistLoop(graph, outPath, opts = {}) {
5952
6775
 
5953
6776
  // src/projects.ts
5954
6777
  init_cjs_shims();
5955
- var import_node_path35 = __toESM(require("path"), 1);
6778
+ var import_node_path37 = __toESM(require("path"), 1);
5956
6779
  function pathsForProject(project, baseDir) {
5957
6780
  if (project === DEFAULT_PROJECT) {
5958
6781
  return {
5959
- snapshotPath: import_node_path35.default.join(baseDir, "graph.json"),
5960
- errorsPath: import_node_path35.default.join(baseDir, "errors.ndjson"),
5961
- staleEventsPath: import_node_path35.default.join(baseDir, "stale-events.ndjson"),
5962
- embeddingsCachePath: import_node_path35.default.join(baseDir, "embeddings.json"),
5963
- policyViolationsPath: import_node_path35.default.join(baseDir, "policy-violations.ndjson")
6782
+ snapshotPath: import_node_path37.default.join(baseDir, "graph.json"),
6783
+ errorsPath: import_node_path37.default.join(baseDir, "errors.ndjson"),
6784
+ staleEventsPath: import_node_path37.default.join(baseDir, "stale-events.ndjson"),
6785
+ embeddingsCachePath: import_node_path37.default.join(baseDir, "embeddings.json"),
6786
+ policyViolationsPath: import_node_path37.default.join(baseDir, "policy-violations.ndjson")
5964
6787
  };
5965
6788
  }
5966
6789
  return {
5967
- snapshotPath: import_node_path35.default.join(baseDir, `${project}.json`),
5968
- errorsPath: import_node_path35.default.join(baseDir, `errors.${project}.ndjson`),
5969
- staleEventsPath: import_node_path35.default.join(baseDir, `stale-events.${project}.ndjson`),
5970
- embeddingsCachePath: import_node_path35.default.join(baseDir, `embeddings.${project}.json`),
5971
- policyViolationsPath: import_node_path35.default.join(baseDir, `policy-violations.${project}.ndjson`)
6790
+ snapshotPath: import_node_path37.default.join(baseDir, `${project}.json`),
6791
+ errorsPath: import_node_path37.default.join(baseDir, `errors.${project}.ndjson`),
6792
+ staleEventsPath: import_node_path37.default.join(baseDir, `stale-events.${project}.ndjson`),
6793
+ embeddingsCachePath: import_node_path37.default.join(baseDir, `embeddings.${project}.json`),
6794
+ policyViolationsPath: import_node_path37.default.join(baseDir, `policy-violations.${project}.ndjson`)
5972
6795
  };
5973
6796
  }
5974
6797
  var Projects = class {
@@ -6006,19 +6829,19 @@ var Projects = class {
6006
6829
  init_cjs_shims();
6007
6830
  var import_fastify = __toESM(require("fastify"), 1);
6008
6831
  var import_cors = __toESM(require("@fastify/cors"), 1);
6009
- var import_types26 = require("@neat.is/types");
6832
+ var import_types28 = require("@neat.is/types");
6010
6833
 
6011
6834
  // src/extend/index.ts
6012
6835
  init_cjs_shims();
6013
6836
  var import_node_fs21 = require("fs");
6014
- var import_node_path37 = __toESM(require("path"), 1);
6837
+ var import_node_path39 = __toESM(require("path"), 1);
6015
6838
  var import_node_os2 = __toESM(require("os"), 1);
6016
6839
  var import_instrumentation_registry = require("@neat.is/instrumentation-registry");
6017
6840
 
6018
6841
  // src/installers/package-manager.ts
6019
6842
  init_cjs_shims();
6020
6843
  var import_node_fs20 = require("fs");
6021
- var import_node_path36 = __toESM(require("path"), 1);
6844
+ var import_node_path38 = __toESM(require("path"), 1);
6022
6845
  var import_node_child_process = require("child_process");
6023
6846
  var LOCKFILE_PRIORITY = [
6024
6847
  { lockfile: "bun.lockb", pm: "bun", args: ["install", "--no-summary"] },
@@ -6040,22 +6863,22 @@ async function exists2(p) {
6040
6863
  }
6041
6864
  }
6042
6865
  async function detectPackageManager(serviceDir) {
6043
- let dir = import_node_path36.default.resolve(serviceDir);
6866
+ let dir = import_node_path38.default.resolve(serviceDir);
6044
6867
  const stops = /* @__PURE__ */ new Set();
6045
6868
  for (let i = 0; i < 64; i++) {
6046
6869
  if (stops.has(dir)) break;
6047
6870
  stops.add(dir);
6048
6871
  for (const candidate of LOCKFILE_PRIORITY) {
6049
- const lockPath = import_node_path36.default.join(dir, candidate.lockfile);
6872
+ const lockPath = import_node_path38.default.join(dir, candidate.lockfile);
6050
6873
  if (await exists2(lockPath)) {
6051
6874
  return { pm: candidate.pm, cwd: dir, args: [...candidate.args] };
6052
6875
  }
6053
6876
  }
6054
- const parent = import_node_path36.default.dirname(dir);
6877
+ const parent = import_node_path38.default.dirname(dir);
6055
6878
  if (parent === dir) break;
6056
6879
  dir = parent;
6057
6880
  }
6058
- return { pm: "npm", cwd: import_node_path36.default.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
6881
+ return { pm: "npm", cwd: import_node_path38.default.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
6059
6882
  }
6060
6883
  async function runPackageManagerInstall(cmd) {
6061
6884
  return new Promise((resolve) => {
@@ -6104,7 +6927,7 @@ async function fileExists2(p) {
6104
6927
  }
6105
6928
  }
6106
6929
  async function readPackageJson(scanPath) {
6107
- const pkgPath = import_node_path37.default.join(scanPath, "package.json");
6930
+ const pkgPath = import_node_path39.default.join(scanPath, "package.json");
6108
6931
  const raw = await import_node_fs21.promises.readFile(pkgPath, "utf8");
6109
6932
  return JSON.parse(raw);
6110
6933
  }
@@ -6115,11 +6938,11 @@ async function findHookFiles(scanPath) {
6115
6938
  ).sort();
6116
6939
  }
6117
6940
  function extendLogPath() {
6118
- return process.env.NEAT_EXTEND_LOG ?? import_node_path37.default.join(import_node_os2.default.homedir(), ".neat", "extend-log.ndjson");
6941
+ return process.env.NEAT_EXTEND_LOG ?? import_node_path39.default.join(import_node_os2.default.homedir(), ".neat", "extend-log.ndjson");
6119
6942
  }
6120
6943
  async function appendExtendLog(entry2) {
6121
6944
  const logPath = extendLogPath();
6122
- await import_node_fs21.promises.mkdir(import_node_path37.default.dirname(logPath), { recursive: true });
6945
+ await import_node_fs21.promises.mkdir(import_node_path39.default.dirname(logPath), { recursive: true });
6123
6946
  await import_node_fs21.promises.appendFile(logPath, JSON.stringify(entry2) + "\n", "utf8");
6124
6947
  }
6125
6948
  function splicedContent(fileContent, snippet2) {
@@ -6178,7 +7001,7 @@ function lookupInstrumentation(library, installedVersion) {
6178
7001
  }
6179
7002
  async function describeProjectInstrumentation(ctx) {
6180
7003
  const hookFiles = await findHookFiles(ctx.scanPath);
6181
- const envNeat = await fileExists2(import_node_path37.default.join(ctx.scanPath, ".env.neat"));
7004
+ const envNeat = await fileExists2(import_node_path39.default.join(ctx.scanPath, ".env.neat"));
6182
7005
  const registryInstrPackages = new Set(
6183
7006
  (0, import_instrumentation_registry.list)().map((e) => e.instrumentation_package).filter((p) => !!p)
6184
7007
  );
@@ -6200,16 +7023,16 @@ async function applyExtension(ctx, args, options) {
6200
7023
  );
6201
7024
  }
6202
7025
  for (const file of hookFiles) {
6203
- const content = await import_node_fs21.promises.readFile(import_node_path37.default.join(ctx.scanPath, file), "utf8");
7026
+ const content = await import_node_fs21.promises.readFile(import_node_path39.default.join(ctx.scanPath, file), "utf8");
6204
7027
  if (content.includes(args.registration_snippet)) {
6205
7028
  return { library: args.library, filesTouched: [], depsAdded: [], installOutput: "", alreadyApplied: true };
6206
7029
  }
6207
7030
  }
6208
7031
  const primaryFile = hookFiles[0];
6209
- const primaryPath = import_node_path37.default.join(ctx.scanPath, primaryFile);
7032
+ const primaryPath = import_node_path39.default.join(ctx.scanPath, primaryFile);
6210
7033
  const filesTouched = [];
6211
7034
  const depsAdded = [];
6212
- const pkgPath = import_node_path37.default.join(ctx.scanPath, "package.json");
7035
+ const pkgPath = import_node_path39.default.join(ctx.scanPath, "package.json");
6213
7036
  const pkg = await readPackageJson(ctx.scanPath);
6214
7037
  if (!(pkg.dependencies ?? {})[args.instrumentation_package]) {
6215
7038
  pkg.dependencies = { ...pkg.dependencies ?? {}, [args.instrumentation_package]: args.version };
@@ -6255,7 +7078,7 @@ async function dryRunExtension(ctx, args) {
6255
7078
  };
6256
7079
  }
6257
7080
  for (const file of hookFiles) {
6258
- const content = await import_node_fs21.promises.readFile(import_node_path37.default.join(ctx.scanPath, file), "utf8");
7081
+ const content = await import_node_fs21.promises.readFile(import_node_path39.default.join(ctx.scanPath, file), "utf8");
6259
7082
  if (content.includes(args.registration_snippet)) {
6260
7083
  return {
6261
7084
  library: args.library,
@@ -6277,7 +7100,7 @@ async function dryRunExtension(ctx, args) {
6277
7100
  depsToAdd.push(`${args.instrumentation_package}@${args.version}`);
6278
7101
  filesTouched.push("package.json");
6279
7102
  }
6280
- const hookContent = await import_node_fs21.promises.readFile(import_node_path37.default.join(ctx.scanPath, primaryFile), "utf8");
7103
+ const hookContent = await import_node_fs21.promises.readFile(import_node_path39.default.join(ctx.scanPath, primaryFile), "utf8");
6281
7104
  const patched = splicedContent(hookContent, args.registration_snippet);
6282
7105
  if (patched) {
6283
7106
  filesTouched.push(primaryFile);
@@ -6298,7 +7121,7 @@ async function rollbackExtension(ctx, args) {
6298
7121
  if (!match) {
6299
7122
  return { undone: false, message: "no apply found for library" };
6300
7123
  }
6301
- const pkgPath = import_node_path37.default.join(ctx.scanPath, "package.json");
7124
+ const pkgPath = import_node_path39.default.join(ctx.scanPath, "package.json");
6302
7125
  if (await fileExists2(pkgPath)) {
6303
7126
  const pkg = await readPackageJson(ctx.scanPath);
6304
7127
  if (pkg.dependencies?.[match.instrumentation_package]) {
@@ -6309,7 +7132,7 @@ async function rollbackExtension(ctx, args) {
6309
7132
  }
6310
7133
  const hookFiles = await findHookFiles(ctx.scanPath);
6311
7134
  for (const file of hookFiles) {
6312
- const filePath = import_node_path37.default.join(ctx.scanPath, file);
7135
+ const filePath = import_node_path39.default.join(ctx.scanPath, file);
6313
7136
  const content = await import_node_fs21.promises.readFile(filePath, "utf8");
6314
7137
  if (content.includes(match.registration_snippet)) {
6315
7138
  const filtered = content.split("\n").filter((line) => !line.includes(match.registration_snippet)).join("\n");
@@ -6325,7 +7148,7 @@ async function rollbackExtension(ctx, args) {
6325
7148
 
6326
7149
  // src/divergences.ts
6327
7150
  init_cjs_shims();
6328
- var import_types24 = require("@neat.is/types");
7151
+ var import_types26 = require("@neat.is/types");
6329
7152
  function bucketKey(source, target, type) {
6330
7153
  return `${type}|${source}|${target}`;
6331
7154
  }
@@ -6333,22 +7156,22 @@ function bucketEdges(graph) {
6333
7156
  const buckets = /* @__PURE__ */ new Map();
6334
7157
  graph.forEachEdge((id, attrs) => {
6335
7158
  const e = attrs;
6336
- const parsed = (0, import_types24.parseEdgeId)(id);
7159
+ const parsed = (0, import_types26.parseEdgeId)(id);
6337
7160
  const provenance = parsed?.provenance ?? e.provenance;
6338
7161
  const key = bucketKey(e.source, e.target, e.type);
6339
7162
  const cur = buckets.get(key) ?? { source: e.source, target: e.target, type: e.type };
6340
7163
  switch (provenance) {
6341
- case import_types24.Provenance.EXTRACTED:
7164
+ case import_types26.Provenance.EXTRACTED:
6342
7165
  cur.extracted = e;
6343
7166
  break;
6344
- case import_types24.Provenance.OBSERVED:
7167
+ case import_types26.Provenance.OBSERVED:
6345
7168
  cur.observed = e;
6346
7169
  break;
6347
- case import_types24.Provenance.INFERRED:
7170
+ case import_types26.Provenance.INFERRED:
6348
7171
  cur.inferred = e;
6349
7172
  break;
6350
7173
  default:
6351
- if (e.provenance === import_types24.Provenance.STALE) cur.stale = e;
7174
+ if (e.provenance === import_types26.Provenance.STALE) cur.stale = e;
6352
7175
  }
6353
7176
  buckets.set(key, cur);
6354
7177
  });
@@ -6357,7 +7180,7 @@ function bucketEdges(graph) {
6357
7180
  function nodeIsFrontier(graph, nodeId) {
6358
7181
  if (!graph.hasNode(nodeId)) return false;
6359
7182
  const attrs = graph.getNodeAttributes(nodeId);
6360
- return attrs.type === import_types24.NodeType.FrontierNode;
7183
+ return attrs.type === import_types26.NodeType.FrontierNode;
6361
7184
  }
6362
7185
  function clampConfidence(n) {
6363
7186
  if (!Number.isFinite(n)) return 0;
@@ -6377,14 +7200,14 @@ function gradedConfidence(edge) {
6377
7200
  return clampConfidence(confidenceForEdge(edge));
6378
7201
  }
6379
7202
  var OBSERVABLE_EDGE_TYPES = /* @__PURE__ */ new Set([
6380
- import_types24.EdgeType.CALLS,
6381
- import_types24.EdgeType.CONNECTS_TO,
6382
- import_types24.EdgeType.PUBLISHES_TO,
6383
- import_types24.EdgeType.CONSUMES_FROM
7203
+ import_types26.EdgeType.CALLS,
7204
+ import_types26.EdgeType.CONNECTS_TO,
7205
+ import_types26.EdgeType.PUBLISHES_TO,
7206
+ import_types26.EdgeType.CONSUMES_FROM
6384
7207
  ]);
6385
7208
  function detectMissingDivergences(graph, bucket) {
6386
7209
  const out = [];
6387
- if (bucket.type === import_types24.EdgeType.CONTAINS) return out;
7210
+ if (bucket.type === import_types26.EdgeType.CONTAINS) return out;
6388
7211
  if (bucket.extracted && !bucket.observed && OBSERVABLE_EDGE_TYPES.has(bucket.type)) {
6389
7212
  if (!nodeIsFrontier(graph, bucket.target)) {
6390
7213
  out.push({
@@ -6425,7 +7248,7 @@ function declaredHostFor(svc) {
6425
7248
  function hasExtractedConfiguredBy(graph, svcId) {
6426
7249
  for (const edgeId of graph.outboundEdges(svcId)) {
6427
7250
  const e = graph.getEdgeAttributes(edgeId);
6428
- if (e.type === import_types24.EdgeType.CONFIGURED_BY && e.provenance === import_types24.Provenance.EXTRACTED) {
7251
+ if (e.type === import_types26.EdgeType.CONFIGURED_BY && e.provenance === import_types26.Provenance.EXTRACTED) {
6429
7252
  return true;
6430
7253
  }
6431
7254
  }
@@ -6438,10 +7261,10 @@ function detectHostMismatch(graph, svcId, svc) {
6438
7261
  const out = [];
6439
7262
  for (const edgeId of graph.outboundEdges(svcId)) {
6440
7263
  const edge = graph.getEdgeAttributes(edgeId);
6441
- if (edge.type !== import_types24.EdgeType.CONNECTS_TO) continue;
6442
- if (edge.provenance !== import_types24.Provenance.OBSERVED) continue;
7264
+ if (edge.type !== import_types26.EdgeType.CONNECTS_TO) continue;
7265
+ if (edge.provenance !== import_types26.Provenance.OBSERVED) continue;
6443
7266
  const target = graph.getNodeAttributes(edge.target);
6444
- if (target.type !== import_types24.NodeType.DatabaseNode) continue;
7267
+ if (target.type !== import_types26.NodeType.DatabaseNode) continue;
6445
7268
  const observedHost = target.host?.trim();
6446
7269
  if (!observedHost) continue;
6447
7270
  if (observedHost === declaredHost) continue;
@@ -6463,10 +7286,10 @@ function detectCompatDivergences(graph, svcId, svc) {
6463
7286
  const deps = svc.dependencies ?? {};
6464
7287
  for (const edgeId of graph.outboundEdges(svcId)) {
6465
7288
  const edge = graph.getEdgeAttributes(edgeId);
6466
- if (edge.type !== import_types24.EdgeType.CONNECTS_TO) continue;
6467
- if (edge.provenance !== import_types24.Provenance.OBSERVED) continue;
7289
+ if (edge.type !== import_types26.EdgeType.CONNECTS_TO) continue;
7290
+ if (edge.provenance !== import_types26.Provenance.OBSERVED) continue;
6468
7291
  const target = graph.getNodeAttributes(edge.target);
6469
- if (target.type !== import_types24.NodeType.DatabaseNode) continue;
7292
+ if (target.type !== import_types26.NodeType.DatabaseNode) continue;
6470
7293
  for (const pair of compatPairs()) {
6471
7294
  if (pair.engine !== target.engine) continue;
6472
7295
  const declared = deps[pair.driver];
@@ -6525,7 +7348,7 @@ function suppressHostMismatchHalves(all) {
6525
7348
  for (const d of all) {
6526
7349
  if (d.type !== "host-mismatch") continue;
6527
7350
  observedHalf.add(`${d.source}->${d.target}`);
6528
- declaredHalf.add((0, import_types24.databaseId)(d.extractedHost));
7351
+ declaredHalf.add((0, import_types26.databaseId)(d.extractedHost));
6529
7352
  }
6530
7353
  if (observedHalf.size === 0) return all;
6531
7354
  return all.filter((d) => {
@@ -6544,7 +7367,7 @@ function computeDivergences(graph, opts = {}) {
6544
7367
  }
6545
7368
  graph.forEachNode((nodeId, attrs) => {
6546
7369
  const n = attrs;
6547
- if (n.type !== import_types24.NodeType.ServiceNode) return;
7370
+ if (n.type !== import_types26.NodeType.ServiceNode) return;
6548
7371
  const svc = n;
6549
7372
  for (const d of detectHostMismatch(graph, nodeId, svc)) all.push(d);
6550
7373
  for (const d of detectCompatDivergences(graph, nodeId, svc)) all.push(d);
@@ -6578,7 +7401,7 @@ function computeDivergences(graph, opts = {}) {
6578
7401
  if (a.source !== b.source) return a.source.localeCompare(b.source);
6579
7402
  return a.target.localeCompare(b.target);
6580
7403
  });
6581
- return import_types24.DivergenceResultSchema.parse({
7404
+ return import_types26.DivergenceResultSchema.parse({
6582
7405
  divergences: filtered,
6583
7406
  totalAffected: filtered.length,
6584
7407
  computedAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -6666,23 +7489,23 @@ function canonicalJson(value) {
6666
7489
  init_cjs_shims();
6667
7490
  var import_node_fs23 = require("fs");
6668
7491
  var import_node_os3 = __toESM(require("os"), 1);
6669
- var import_node_path38 = __toESM(require("path"), 1);
6670
- var import_types25 = require("@neat.is/types");
7492
+ var import_node_path40 = __toESM(require("path"), 1);
7493
+ var import_types27 = require("@neat.is/types");
6671
7494
  var LOCK_TIMEOUT_MS = 5e3;
6672
7495
  var LOCK_RETRY_MS = 50;
6673
7496
  function neatHome() {
6674
7497
  const override = process.env.NEAT_HOME;
6675
- if (override && override.length > 0) return import_node_path38.default.resolve(override);
6676
- return import_node_path38.default.join(import_node_os3.default.homedir(), ".neat");
7498
+ if (override && override.length > 0) return import_node_path40.default.resolve(override);
7499
+ return import_node_path40.default.join(import_node_os3.default.homedir(), ".neat");
6677
7500
  }
6678
7501
  function registryPath() {
6679
- return import_node_path38.default.join(neatHome(), "projects.json");
7502
+ return import_node_path40.default.join(neatHome(), "projects.json");
6680
7503
  }
6681
7504
  function registryLockPath() {
6682
- return import_node_path38.default.join(neatHome(), "projects.json.lock");
7505
+ return import_node_path40.default.join(neatHome(), "projects.json.lock");
6683
7506
  }
6684
7507
  function daemonPidPath() {
6685
- return import_node_path38.default.join(neatHome(), "neatd.pid");
7508
+ return import_node_path40.default.join(neatHome(), "neatd.pid");
6686
7509
  }
6687
7510
  function isPidAliveDefault(pid) {
6688
7511
  try {
@@ -6742,7 +7565,7 @@ function lockHolderMessage(holder, lockPath, timeoutMs) {
6742
7565
  }
6743
7566
  }
6744
7567
  async function writeAtomically(target, contents) {
6745
- await import_node_fs23.promises.mkdir(import_node_path38.default.dirname(target), { recursive: true });
7568
+ await import_node_fs23.promises.mkdir(import_node_path40.default.dirname(target), { recursive: true });
6746
7569
  const tmp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
6747
7570
  const fd = await import_node_fs23.promises.open(tmp, "w");
6748
7571
  try {
@@ -6755,7 +7578,7 @@ async function writeAtomically(target, contents) {
6755
7578
  }
6756
7579
  async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaultLockHolderProbe) {
6757
7580
  const deadline = Date.now() + timeoutMs;
6758
- await import_node_fs23.promises.mkdir(import_node_path38.default.dirname(lockPath), { recursive: true });
7581
+ await import_node_fs23.promises.mkdir(import_node_path40.default.dirname(lockPath), { recursive: true });
6759
7582
  let probedHolder = false;
6760
7583
  while (true) {
6761
7584
  try {
@@ -6808,10 +7631,10 @@ async function readRegistry() {
6808
7631
  throw err;
6809
7632
  }
6810
7633
  const parsed = JSON.parse(raw);
6811
- return import_types25.RegistryFileSchema.parse(parsed);
7634
+ return import_types27.RegistryFileSchema.parse(parsed);
6812
7635
  }
6813
7636
  async function writeRegistry(reg) {
6814
- const validated = import_types25.RegistryFileSchema.parse(reg);
7637
+ const validated = import_types27.RegistryFileSchema.parse(reg);
6815
7638
  await writeAtomically(registryPath(), JSON.stringify(validated, null, 2) + "\n");
6816
7639
  }
6817
7640
  async function getProject(name) {
@@ -7102,11 +7925,11 @@ function registerRoutes(scope, ctx) {
7102
7925
  const candidates = req2.query.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
7103
7926
  const parsed = [];
7104
7927
  for (const c of candidates) {
7105
- const r = import_types26.DivergenceTypeSchema.safeParse(c);
7928
+ const r = import_types28.DivergenceTypeSchema.safeParse(c);
7106
7929
  if (!r.success) {
7107
7930
  return reply.code(400).send({
7108
7931
  error: `unknown divergence type "${c}"`,
7109
- allowed: import_types26.DivergenceTypeSchema.options
7932
+ allowed: import_types28.DivergenceTypeSchema.options
7110
7933
  });
7111
7934
  }
7112
7935
  parsed.push(r.data);
@@ -7325,7 +8148,7 @@ function registerRoutes(scope, ctx) {
7325
8148
  const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
7326
8149
  let violations = await log.readAll();
7327
8150
  if (req2.query.severity) {
7328
- const sev = import_types26.PolicySeveritySchema.safeParse(req2.query.severity);
8151
+ const sev = import_types28.PolicySeveritySchema.safeParse(req2.query.severity);
7329
8152
  if (!sev.success) {
7330
8153
  return reply.code(400).send({
7331
8154
  error: "invalid severity",
@@ -7364,7 +8187,7 @@ function registerRoutes(scope, ctx) {
7364
8187
  scope.post("/policies/check", async (req2, reply) => {
7365
8188
  const proj = resolveProject(registry, req2, reply, ctx.bootstrap, ctx.singleProject);
7366
8189
  if (!proj) return;
7367
- const parsed = import_types26.PoliciesCheckBodySchema.safeParse(req2.body ?? {});
8190
+ const parsed = import_types28.PoliciesCheckBodySchema.safeParse(req2.body ?? {});
7368
8191
  if (!parsed.success) {
7369
8192
  return reply.code(400).send({
7370
8193
  error: "invalid /policies/check body",
@@ -7627,7 +8450,7 @@ init_auth();
7627
8450
  // src/unrouted.ts
7628
8451
  init_cjs_shims();
7629
8452
  var import_node_fs24 = require("fs");
7630
- var import_node_path41 = __toESM(require("path"), 1);
8453
+ var import_node_path43 = __toESM(require("path"), 1);
7631
8454
  function buildUnroutedSpanRecord(serviceName, traceId, now = /* @__PURE__ */ new Date()) {
7632
8455
  return {
7633
8456
  timestamp: now.toISOString(),
@@ -7637,34 +8460,34 @@ function buildUnroutedSpanRecord(serviceName, traceId, now = /* @__PURE__ */ new
7637
8460
  };
7638
8461
  }
7639
8462
  async function appendUnroutedSpan(neatHome3, record) {
7640
- const target = import_node_path41.default.join(neatHome3, "errors.ndjson");
8463
+ const target = import_node_path43.default.join(neatHome3, "errors.ndjson");
7641
8464
  await import_node_fs24.promises.mkdir(neatHome3, { recursive: true });
7642
8465
  await import_node_fs24.promises.appendFile(target, JSON.stringify(record) + "\n", "utf8");
7643
8466
  }
7644
8467
  function unroutedErrorsPath(neatHome3) {
7645
- return import_node_path41.default.join(neatHome3, "errors.ndjson");
8468
+ return import_node_path43.default.join(neatHome3, "errors.ndjson");
7646
8469
  }
7647
8470
 
7648
8471
  // src/daemon.ts
7649
- var import_types27 = require("@neat.is/types");
8472
+ var import_types29 = require("@neat.is/types");
7650
8473
  function daemonJsonPath(scanPath) {
7651
- return import_node_path42.default.join(scanPath, "neat-out", "daemon.json");
8474
+ return import_node_path44.default.join(scanPath, "neat-out", "daemon.json");
7652
8475
  }
7653
8476
  function daemonsDiscoveryDir(home) {
7654
8477
  const base = home && home.length > 0 ? home : neatHomeFromEnv();
7655
- return import_node_path42.default.join(base, "daemons");
8478
+ return import_node_path44.default.join(base, "daemons");
7656
8479
  }
7657
8480
  function daemonDiscoveryPath(project, home) {
7658
- return import_node_path42.default.join(daemonsDiscoveryDir(home), `${sanitizeDiscoveryName(project)}.json`);
8481
+ return import_node_path44.default.join(daemonsDiscoveryDir(home), `${sanitizeDiscoveryName(project)}.json`);
7659
8482
  }
7660
8483
  function sanitizeDiscoveryName(project) {
7661
8484
  return project.replace(/[^A-Za-z0-9._-]/g, "_");
7662
8485
  }
7663
8486
  function neatHomeFromEnv() {
7664
8487
  const env = process.env.NEAT_HOME;
7665
- if (env && env.length > 0) return import_node_path42.default.resolve(env);
8488
+ if (env && env.length > 0) return import_node_path44.default.resolve(env);
7666
8489
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
7667
- return import_node_path42.default.join(home, ".neat");
8490
+ return import_node_path44.default.join(home, ".neat");
7668
8491
  }
7669
8492
  function resolveNeatVersion() {
7670
8493
  if (process.env.NEAT_LOCAL_VERSION && process.env.NEAT_LOCAL_VERSION.length > 0) {
@@ -7719,17 +8542,21 @@ function teardownSlot(slot) {
7719
8542
  slot.stopPersist();
7720
8543
  } catch {
7721
8544
  }
8545
+ try {
8546
+ slot.stopStaleness();
8547
+ } catch {
8548
+ }
7722
8549
  try {
7723
8550
  slot.detachEvents();
7724
8551
  } catch {
7725
8552
  }
7726
8553
  }
7727
8554
  function neatHomeFor(opts) {
7728
- if (opts.neatHome && opts.neatHome.length > 0) return import_node_path42.default.resolve(opts.neatHome);
8555
+ if (opts.neatHome && opts.neatHome.length > 0) return import_node_path44.default.resolve(opts.neatHome);
7729
8556
  const env = process.env.NEAT_HOME;
7730
- if (env && env.length > 0) return import_node_path42.default.resolve(env);
8557
+ if (env && env.length > 0) return import_node_path44.default.resolve(env);
7731
8558
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
7732
- return import_node_path42.default.join(home, ".neat");
8559
+ return import_node_path44.default.join(home, ".neat");
7733
8560
  }
7734
8561
  function routeSpanToProject(serviceName, projects) {
7735
8562
  if (!serviceName) return DEFAULT_PROJECT;
@@ -7773,11 +8600,11 @@ function spanBelongsToSingleProject(graph, project, serviceName) {
7773
8600
  if (!serviceName) return true;
7774
8601
  if (serviceNameMatchesProject(serviceName, project)) return true;
7775
8602
  return graph.someNode(
7776
- (_id, attrs) => attrs.type === import_types27.NodeType.ServiceNode && attrs.name === serviceName
8603
+ (_id, attrs) => attrs.type === import_types29.NodeType.ServiceNode && attrs.name === serviceName
7777
8604
  );
7778
8605
  }
7779
8606
  async function bootstrapProject(entry2) {
7780
- const paths = pathsForProject(entry2.name, import_node_path42.default.join(entry2.path, "neat-out"));
8607
+ const paths = pathsForProject(entry2.name, import_node_path44.default.join(entry2.path, "neat-out"));
7781
8608
  try {
7782
8609
  const stat = await import_node_fs25.promises.stat(entry2.path);
7783
8610
  if (!stat.isDirectory()) {
@@ -7795,6 +8622,8 @@ async function bootstrapProject(entry2) {
7795
8622
  paths,
7796
8623
  stopPersist: () => {
7797
8624
  },
8625
+ stopStaleness: () => {
8626
+ },
7798
8627
  detachEvents: () => {
7799
8628
  },
7800
8629
  status: "broken",
@@ -7809,6 +8638,10 @@ async function bootstrapProject(entry2) {
7809
8638
  try {
7810
8639
  await extractFromDirectory(graph, entry2.path);
7811
8640
  const stopPersist = startPersistLoop(graph, outPath, { exitOnSignal: false });
8641
+ const stopStaleness = startStalenessLoop(graph, {
8642
+ staleEventsPath: paths.staleEventsPath,
8643
+ project: entry2.name
8644
+ });
7812
8645
  await touchLastSeen(entry2.name).catch(() => {
7813
8646
  });
7814
8647
  return {
@@ -7817,6 +8650,7 @@ async function bootstrapProject(entry2) {
7817
8650
  outPath,
7818
8651
  paths,
7819
8652
  stopPersist,
8653
+ stopStaleness,
7820
8654
  detachEvents,
7821
8655
  status: "active"
7822
8656
  };
@@ -7873,7 +8707,7 @@ async function startDaemon(opts = {}) {
7873
8707
  const projectArg = typeof opts.project === "string" && opts.project.length > 0 ? opts.project : process.env.NEAT_PROJECT && process.env.NEAT_PROJECT.length > 0 ? process.env.NEAT_PROJECT : null;
7874
8708
  const projectPathArg = opts.projectPath && opts.projectPath.length > 0 ? opts.projectPath : process.env.NEAT_PROJECT_PATH && process.env.NEAT_PROJECT_PATH.length > 0 ? process.env.NEAT_PROJECT_PATH : null;
7875
8709
  const singleProject = projectArg;
7876
- const singleProjectPath = singleProject && projectPathArg ? import_node_path42.default.resolve(projectPathArg) : null;
8710
+ const singleProjectPath = singleProject && projectPathArg ? import_node_path44.default.resolve(projectPathArg) : null;
7877
8711
  if (singleProject && !singleProjectPath) {
7878
8712
  throw new Error(
7879
8713
  `neatd: project "${singleProject}" given without a projectPath; pass NEAT_PROJECT_PATH alongside NEAT_PROJECT.`
@@ -7888,7 +8722,7 @@ async function startDaemon(opts = {}) {
7888
8722
  );
7889
8723
  }
7890
8724
  }
7891
- const pidPath = import_node_path42.default.join(home, "neatd.pid");
8725
+ const pidPath = import_node_path44.default.join(home, "neatd.pid");
7892
8726
  await writeAtomically(pidPath, `${process.pid}
7893
8727
  `);
7894
8728
  const slots = /* @__PURE__ */ new Map();
@@ -8290,8 +9124,8 @@ async function startDaemon(opts = {}) {
8290
9124
  let registryWatcher = null;
8291
9125
  let reloadTimer = null;
8292
9126
  if (!singleProject) try {
8293
- const regDir = import_node_path42.default.dirname(regPath);
8294
- const regBase = import_node_path42.default.basename(regPath);
9127
+ const regDir = import_node_path44.default.dirname(regPath);
9128
+ const regBase = import_node_path44.default.basename(regPath);
8295
9129
  registryWatcher = (0, import_node_fs25.watch)(regDir, (_eventType, filename) => {
8296
9130
  if (filename !== null && filename !== regBase) return;
8297
9131
  if (reloadTimer) clearTimeout(reloadTimer);
@@ -8366,7 +9200,7 @@ init_cjs_shims();
8366
9200
  var import_node_child_process2 = require("child_process");
8367
9201
  var import_node_fs26 = require("fs");
8368
9202
  var import_node_net = __toESM(require("net"), 1);
8369
- var import_node_path43 = __toESM(require("path"), 1);
9203
+ var import_node_path45 = __toESM(require("path"), 1);
8370
9204
  var DEFAULT_WEB_PORT = 6328;
8371
9205
  var DEFAULT_REST_PORT = 8080;
8372
9206
  function asValidPort(value) {
@@ -8375,11 +9209,11 @@ function asValidPort(value) {
8375
9209
  }
8376
9210
  function projectRoot() {
8377
9211
  const fromEnv = process.env.NEAT_SCAN_PATH;
8378
- return import_node_path43.default.resolve(fromEnv && fromEnv.length > 0 ? fromEnv : process.cwd());
9212
+ return import_node_path45.default.resolve(fromEnv && fromEnv.length > 0 ? fromEnv : process.cwd());
8379
9213
  }
8380
9214
  async function readDaemonPorts(root) {
8381
9215
  try {
8382
- const raw = await import_node_fs26.promises.readFile(import_node_path43.default.join(root, "neat-out", "daemon.json"), "utf8");
9216
+ const raw = await import_node_fs26.promises.readFile(import_node_path45.default.join(root, "neat-out", "daemon.json"), "utf8");
8383
9217
  const parsed = JSON.parse(raw);
8384
9218
  const ports = parsed?.ports ?? {};
8385
9219
  return { web: asValidPort(ports.web), rest: asValidPort(ports.rest) };
@@ -8424,10 +9258,10 @@ function resolveWebPackageDir() {
8424
9258
  eval("require")
8425
9259
  );
8426
9260
  const pkgJsonPath = req.resolve("@neat.is/web/package.json");
8427
- return import_node_path43.default.dirname(pkgJsonPath);
9261
+ return import_node_path45.default.dirname(pkgJsonPath);
8428
9262
  }
8429
9263
  function resolveStandaloneServerEntry(webDir) {
8430
- return import_node_path43.default.join(webDir, ".next/standalone/packages/web/server.js");
9264
+ return import_node_path45.default.join(webDir, ".next/standalone/packages/web/server.js");
8431
9265
  }
8432
9266
  async function pickInternalPort() {
8433
9267
  return new Promise((resolve, reject) => {
@@ -8480,7 +9314,7 @@ async function spawnWebUI(restPort, opts = {}) {
8480
9314
  NEAT_API_URL: apiUrl
8481
9315
  };
8482
9316
  child = (0, import_node_child_process2.spawn)(process.execPath, [serverEntry], {
8483
- cwd: import_node_path43.default.dirname(serverEntry),
9317
+ cwd: import_node_path45.default.dirname(serverEntry),
8484
9318
  env,
8485
9319
  stdio: ["ignore", "inherit", "inherit"],
8486
9320
  detached: false
@@ -8656,14 +9490,14 @@ function localVersion() {
8656
9490
  }
8657
9491
  function neatHome2() {
8658
9492
  if (process.env.NEAT_HOME && process.env.NEAT_HOME.length > 0) {
8659
- return import_node_path44.default.resolve(process.env.NEAT_HOME);
9493
+ return import_node_path46.default.resolve(process.env.NEAT_HOME);
8660
9494
  }
8661
9495
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
8662
- return import_node_path44.default.join(home, ".neat");
9496
+ return import_node_path46.default.join(home, ".neat");
8663
9497
  }
8664
9498
  async function readPid() {
8665
9499
  try {
8666
- const raw = await import_node_fs27.promises.readFile(import_node_path44.default.join(neatHome2(), "neatd.pid"), "utf8");
9500
+ const raw = await import_node_fs27.promises.readFile(import_node_path46.default.join(neatHome2(), "neatd.pid"), "utf8");
8667
9501
  const n = Number.parseInt(raw.trim(), 10);
8668
9502
  return Number.isFinite(n) ? n : null;
8669
9503
  } catch {