@neat.is/core 0.6.0 → 0.6.2-dev.20260722

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
@@ -61,8 +61,8 @@ function mountBearerAuth(app, opts) {
61
61
  ]);
62
62
  const publicRead = opts.publicRead === true;
63
63
  app.addHook("preHandler", (req2, reply, done) => {
64
- const path54 = (req2.url.split("?")[0] ?? "").replace(/\/+$/, "");
65
- if (exactUnauthPaths.has(path54) || PROJECT_SCOPED_UNAUTH_PATTERN.test(path54)) {
64
+ const path55 = (req2.url.split("?")[0] ?? "").replace(/\/+$/, "");
65
+ if (exactUnauthPaths.has(path55) || PROJECT_SCOPED_UNAUTH_PATTERN.test(path55)) {
66
66
  done();
67
67
  return;
68
68
  }
@@ -194,8 +194,8 @@ function reshapeGrpcRequest(req2) {
194
194
  };
195
195
  }
196
196
  function resolveProtoRoot() {
197
- const here = import_node_path48.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
198
- return import_node_path48.default.resolve(here, "..", "proto");
197
+ const here = import_node_path49.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
198
+ return import_node_path49.default.resolve(here, "..", "proto");
199
199
  }
200
200
  function loadTraceService() {
201
201
  const protoRoot = resolveProtoRoot();
@@ -263,13 +263,13 @@ async function startOtelGrpcReceiver(opts) {
263
263
  })
264
264
  };
265
265
  }
266
- var import_node_url, import_node_path48, import_node_crypto2, grpc, protoLoader;
266
+ var import_node_url, import_node_path49, import_node_crypto2, grpc, protoLoader;
267
267
  var init_otel_grpc = __esm({
268
268
  "src/otel-grpc.ts"() {
269
269
  "use strict";
270
270
  init_cjs_shims();
271
271
  import_node_url = require("url");
272
- import_node_path48 = __toESM(require("path"), 1);
272
+ import_node_path49 = __toESM(require("path"), 1);
273
273
  import_node_crypto2 = require("crypto");
274
274
  grpc = __toESM(require("@grpc/grpc-js"), 1);
275
275
  protoLoader = __toESM(require("@grpc/proto-loader"), 1);
@@ -347,6 +347,14 @@ function normalizeDbSystem(attrs) {
347
347
  if (typeof raw !== "string") return void 0;
348
348
  return raw === "mongoose" ? "mongodb" : raw;
349
349
  }
350
+ function tableFromSqlStatement(sql) {
351
+ if (typeof sql !== "string" || sql.length === 0) return null;
352
+ if (/\bjoin\b/i.test(sql)) return null;
353
+ const froms = sql.match(/\bfrom\b/gi);
354
+ if (froms && froms.length > 1) return null;
355
+ const m = /\b(?:from|into|update)\s+(?:"?[\w$]+"?\s*\.\s*)?"?([a-zA-Z_][\w$]*)"?/i.exec(sql);
356
+ return m ? m[1] : null;
357
+ }
350
358
  function messagingDestinationOf(attrs) {
351
359
  for (const key of ["messaging.destination.name", "messaging.destination"]) {
352
360
  const v = attrs[key];
@@ -367,8 +375,8 @@ function websocketChannelPathOf(attrs) {
367
375
  const v = attrs[key];
368
376
  if (typeof v === "string" && v.length > 0) {
369
377
  const q = v.indexOf("?");
370
- const path54 = q === -1 ? v : v.slice(0, q);
371
- if (path54.length > 0) return path54;
378
+ const path55 = q === -1 ? v : v.slice(0, q);
379
+ if (path55.length > 0) return path55;
372
380
  }
373
381
  }
374
382
  return void 0;
@@ -404,6 +412,7 @@ function parseOtlpRequest(body) {
404
412
  dbSystem: normalizeDbSystem(attrs),
405
413
  dbName: typeof attrs["db.name"] === "string" ? attrs["db.name"] : void 0,
406
414
  dbCollection: typeof attrs["db.collection.name"] === "string" ? attrs["db.collection.name"] : typeof attrs["db.mongodb.collection"] === "string" ? attrs["db.mongodb.collection"] : void 0,
415
+ dbTable: typeof attrs["db.statement"] === "string" ? tableFromSqlStatement(attrs["db.statement"]) ?? void 0 : void 0,
407
416
  messagingSystem: typeof attrs["messaging.system"] === "string" ? attrs["messaging.system"] : void 0,
408
417
  messagingDestination: messagingDestinationOf(attrs),
409
418
  graphqlOperationName: typeof attrs["graphql.operation.name"] === "string" && attrs["graphql.operation.name"].length > 0 ? attrs["graphql.operation.name"] : void 0,
@@ -423,10 +432,10 @@ function parseOtlpRequest(body) {
423
432
  return out;
424
433
  }
425
434
  function loadProtoRoot() {
426
- const here = import_node_path49.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
427
- const protoRoot = import_node_path49.default.resolve(here, "..", "proto");
435
+ const here = import_node_path50.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
436
+ const protoRoot = import_node_path50.default.resolve(here, "..", "proto");
428
437
  const root = new import_protobufjs.default.Root();
429
- root.resolvePath = (_origin, target) => import_node_path49.default.resolve(protoRoot, target);
438
+ root.resolvePath = (_origin, target) => import_node_path50.default.resolve(protoRoot, target);
430
439
  root.loadSync(
431
440
  "opentelemetry/proto/collector/trace/v1/trace_service.proto",
432
441
  { keepCase: true }
@@ -654,12 +663,12 @@ async function listenSteppingOtlp(app, requestedPort, host) {
654
663
  }
655
664
  }
656
665
  }
657
- var import_node_path49, import_node_url2, import_fastify2, import_protobufjs, ENV_ATTR_CANONICAL, ENV_ATTR_COMPAT, ENV_FALLBACK, exportTraceServiceRequestType, exportTraceServiceResponseType, cachedProtobufResponseBody, OTLP_STEP_ATTEMPTS, OTLP_STEP_STRIDE;
666
+ var import_node_path50, import_node_url2, import_fastify2, import_protobufjs, ENV_ATTR_CANONICAL, ENV_ATTR_COMPAT, ENV_FALLBACK, exportTraceServiceRequestType, exportTraceServiceResponseType, cachedProtobufResponseBody, OTLP_STEP_ATTEMPTS, OTLP_STEP_STRIDE;
658
667
  var init_otel = __esm({
659
668
  "src/otel.ts"() {
660
669
  "use strict";
661
670
  init_cjs_shims();
662
- import_node_path49 = __toESM(require("path"), 1);
671
+ import_node_path50 = __toESM(require("path"), 1);
663
672
  import_node_url2 = require("url");
664
673
  import_fastify2 = __toESM(require("fastify"), 1);
665
674
  import_protobufjs = __toESM(require("protobufjs"), 1);
@@ -683,13 +692,13 @@ __export(neatd_exports, {
683
692
  module.exports = __toCommonJS(neatd_exports);
684
693
  init_cjs_shims();
685
694
  var import_node_fs33 = require("fs");
686
- var import_node_path53 = __toESM(require("path"), 1);
695
+ var import_node_path54 = __toESM(require("path"), 1);
687
696
  var import_node_module2 = require("module");
688
697
 
689
698
  // src/daemon.ts
690
699
  init_cjs_shims();
691
700
  var import_node_fs31 = require("fs");
692
- var import_node_path51 = __toESM(require("path"), 1);
701
+ var import_node_path52 = __toESM(require("path"), 1);
693
702
  var import_node_module = require("module");
694
703
 
695
704
  // src/graph.ts
@@ -1219,19 +1228,19 @@ function confidenceFromMix(edges, now = Date.now()) {
1219
1228
  function longestIncomingWalk(graph, start, maxDepth) {
1220
1229
  let best = { path: [start], edges: [] };
1221
1230
  const visited = /* @__PURE__ */ new Set([start]);
1222
- function step(node, path54, edges) {
1223
- if (path54.length > best.path.length) {
1224
- best = { path: [...path54], edges: [...edges] };
1231
+ function step(node, path55, edges) {
1232
+ if (path55.length > best.path.length) {
1233
+ best = { path: [...path55], edges: [...edges] };
1225
1234
  }
1226
- if (path54.length - 1 >= maxDepth) return;
1235
+ if (path55.length - 1 >= maxDepth) return;
1227
1236
  const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
1228
1237
  for (const [srcId, edge] of incoming) {
1229
1238
  if (visited.has(srcId)) continue;
1230
1239
  visited.add(srcId);
1231
- path54.push(srcId);
1240
+ path55.push(srcId);
1232
1241
  edges.push(edge);
1233
- step(srcId, path54, edges);
1234
- path54.pop();
1242
+ step(srcId, path55, edges);
1243
+ path55.pop();
1235
1244
  edges.pop();
1236
1245
  visited.delete(srcId);
1237
1246
  }
@@ -1239,11 +1248,11 @@ function longestIncomingWalk(graph, start, maxDepth) {
1239
1248
  step(start, [start], []);
1240
1249
  return best;
1241
1250
  }
1242
- function databaseRootCauseShape(graph, origin, walk3) {
1251
+ function databaseRootCauseShape(graph, origin, walk4) {
1243
1252
  const targetDb = origin;
1244
1253
  const candidatePairs = compatPairs().filter((p) => p.engine === targetDb.engine);
1245
1254
  if (candidatePairs.length === 0) return null;
1246
- for (const id of walk3.path) {
1255
+ for (const id of walk4.path) {
1247
1256
  const owner = resolveOwningService(graph, id);
1248
1257
  if (!owner) continue;
1249
1258
  const { id: serviceId6, svc } = owner;
@@ -1270,8 +1279,8 @@ function databaseRootCauseShape(graph, origin, walk3) {
1270
1279
  }
1271
1280
  return null;
1272
1281
  }
1273
- function serviceRootCauseShape(graph, _origin, walk3) {
1274
- for (const id of walk3.path) {
1282
+ function serviceRootCauseShape(graph, _origin, walk4) {
1283
+ for (const id of walk4.path) {
1275
1284
  const owner = resolveOwningService(graph, id);
1276
1285
  if (!owner) continue;
1277
1286
  const { id: serviceId6, svc } = owner;
@@ -1307,10 +1316,10 @@ function serviceRootCauseShape(graph, _origin, walk3) {
1307
1316
  }
1308
1317
  return null;
1309
1318
  }
1310
- function fileRootCauseShape(graph, origin, walk3) {
1319
+ function fileRootCauseShape(graph, origin, walk4) {
1311
1320
  const owner = resolveOwningService(graph, origin.id);
1312
1321
  if (!owner) return null;
1313
- return serviceRootCauseShape(graph, owner.svc, walk3);
1322
+ return serviceRootCauseShape(graph, owner.svc, walk4);
1314
1323
  }
1315
1324
  var rootCauseShapes = {
1316
1325
  [import_types.NodeType.DatabaseNode]: databaseRootCauseShape,
@@ -1322,16 +1331,16 @@ function getRootCause(graph, errorNodeId, errorEvent, incidents) {
1322
1331
  const origin = graph.getNodeAttributes(errorNodeId);
1323
1332
  const shape = rootCauseShapes[origin.type];
1324
1333
  if (shape) {
1325
- const walk3 = longestIncomingWalk(graph, errorNodeId, ROOT_CAUSE_MAX_DEPTH);
1326
- const match = shape(graph, origin, walk3);
1334
+ const walk4 = longestIncomingWalk(graph, errorNodeId, ROOT_CAUSE_MAX_DEPTH);
1335
+ const match = shape(graph, origin, walk4);
1327
1336
  if (match) {
1328
1337
  const reason = errorEvent ? `${match.rootCauseReason} (observed error: ${errorEvent.errorMessage})` : match.rootCauseReason;
1329
1338
  return import_types.RootCauseResultSchema.parse({
1330
1339
  rootCauseNode: match.rootCauseNode,
1331
1340
  rootCauseReason: reason,
1332
- traversalPath: walk3.path,
1333
- edgeProvenances: walk3.edges.map((e) => e.provenance),
1334
- confidence: confidenceFromMix(walk3.edges),
1341
+ traversalPath: walk4.path,
1342
+ edgeProvenances: walk4.edges.map((e) => e.provenance),
1343
+ confidence: confidenceFromMix(walk4.edges),
1335
1344
  fixRecommendation: match.fixRecommendation
1336
1345
  });
1337
1346
  }
@@ -1352,8 +1361,8 @@ function localizeFromIncidents(nodeId, incidents, errorEvent) {
1352
1361
  if (relevant.length === 0) return null;
1353
1362
  const latest = [...relevant].sort((a, b) => b.timestamp.localeCompare(a.timestamp))[0];
1354
1363
  const attrs = latest.attributes ?? {};
1355
- const filepath = typeof attrs["code.filepath"] === "string" ? attrs["code.filepath"] : void 0;
1356
- const lineno = typeof attrs["code.lineno"] === "number" ? attrs["code.lineno"] : void 0;
1364
+ const filepath = codeFilepathOf(attrs);
1365
+ const lineno = codeLinenoOf(attrs);
1357
1366
  const route = typeof attrs["http.route"] === "string" ? attrs["http.route"] : void 0;
1358
1367
  const location = filepath ? `${filepath}${lineno !== void 0 ? `:${lineno}` : ""}` : void 0;
1359
1368
  const sameMode = relevant.filter((ev) => ev.errorMessage === latest.errorMessage);
@@ -1425,26 +1434,26 @@ function dominantFailingCall(graph, serviceId6, visited) {
1425
1434
  return best;
1426
1435
  }
1427
1436
  function followFailingCallChain(graph, originServiceId, maxDepth) {
1428
- const path54 = [originServiceId];
1437
+ const path55 = [originServiceId];
1429
1438
  const edges = [];
1430
1439
  const visited = /* @__PURE__ */ new Set([originServiceId]);
1431
1440
  let current = originServiceId;
1432
1441
  for (let depth = 0; depth < maxDepth; depth++) {
1433
1442
  const hop = dominantFailingCall(graph, current, visited);
1434
1443
  if (!hop) break;
1435
- path54.push(hop.nextService);
1444
+ path55.push(hop.nextService);
1436
1445
  edges.push(hop.edge);
1437
1446
  visited.add(hop.nextService);
1438
1447
  current = hop.nextService;
1439
1448
  }
1440
1449
  if (edges.length === 0) return null;
1441
- return { path: path54, edges, culprit: current };
1450
+ return { path: path55, edges, culprit: current };
1442
1451
  }
1443
1452
  function crossServiceRootCause(graph, originId, incidents, errorEvent) {
1444
1453
  const chain = followFailingCallChain(graph, originId, ROOT_CAUSE_MAX_DEPTH);
1445
1454
  if (!chain) return null;
1446
1455
  const culprit = chain.culprit;
1447
- const path54 = [...chain.path];
1456
+ const path55 = [...chain.path];
1448
1457
  const edgeProvenances = chain.edges.map((e) => e.provenance);
1449
1458
  const baseConfidence = confidenceFromMix(chain.edges);
1450
1459
  const confidence = Math.max(0, Math.min(1, baseConfidence * INCIDENT_ROOT_CAUSE_CONFIDENCE));
@@ -1452,14 +1461,14 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
1452
1461
  if (loc) {
1453
1462
  let rootCauseNode = culprit;
1454
1463
  if (loc.fileNode) {
1455
- path54.push(loc.fileNode);
1464
+ path55.push(loc.fileNode);
1456
1465
  edgeProvenances.push(import_types.Provenance.OBSERVED);
1457
1466
  rootCauseNode = loc.fileNode;
1458
1467
  }
1459
1468
  return import_types.RootCauseResultSchema.parse({
1460
1469
  rootCauseNode,
1461
1470
  rootCauseReason: loc.rootCauseReason,
1462
- traversalPath: path54,
1471
+ traversalPath: path55,
1463
1472
  edgeProvenances,
1464
1473
  confidence,
1465
1474
  ...loc.fixRecommendation ? { fixRecommendation: loc.fixRecommendation } : {}
@@ -1471,7 +1480,7 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
1471
1480
  return import_types.RootCauseResultSchema.parse({
1472
1481
  rootCauseNode: culprit,
1473
1482
  rootCauseReason: `${culpritName} is failing downstream calls (${errs} observed error${errs === 1 ? "" : "s"})`,
1474
- traversalPath: path54,
1483
+ traversalPath: path55,
1475
1484
  edgeProvenances,
1476
1485
  confidence,
1477
1486
  fixRecommendation: `Inspect ${culpritName}'s failing handler`
@@ -2229,9 +2238,30 @@ function isLoopbackHost(host) {
2229
2238
  const h = host.toLowerCase();
2230
2239
  return h === "localhost" || h === "ip6-localhost" || h === "::1" || h === "[::1]" || /^127(?:\.\d{1,3}){3}$/.test(h);
2231
2240
  }
2241
+ var CODE_FILE_PATH_ATTR = "code.file.path";
2232
2242
  var CODE_FILEPATH_ATTR = "code.filepath";
2243
+ var CODE_LINE_NUMBER_ATTR = "code.line.number";
2233
2244
  var CODE_LINENO_ATTR = "code.lineno";
2245
+ var CODE_FUNCTION_NAME_ATTR = "code.function.name";
2234
2246
  var CODE_FUNCTION_ATTR = "code.function";
2247
+ function codeFilepathOf(attrs) {
2248
+ const stable = attrs[CODE_FILE_PATH_ATTR];
2249
+ if (typeof stable === "string" && stable.length > 0) return stable;
2250
+ const prior = attrs[CODE_FILEPATH_ATTR];
2251
+ return typeof prior === "string" && prior.length > 0 ? prior : void 0;
2252
+ }
2253
+ function codeLinenoOf(attrs) {
2254
+ const stable = attrs[CODE_LINE_NUMBER_ATTR];
2255
+ if (typeof stable === "number" && Number.isFinite(stable)) return stable;
2256
+ const prior = attrs[CODE_LINENO_ATTR];
2257
+ return typeof prior === "number" && Number.isFinite(prior) ? prior : void 0;
2258
+ }
2259
+ function codeFunctionOf(attrs) {
2260
+ const stable = attrs[CODE_FUNCTION_NAME_ATTR];
2261
+ if (typeof stable === "string" && stable.length > 0) return stable;
2262
+ const prior = attrs[CODE_FUNCTION_ATTR];
2263
+ return typeof prior === "string" && prior.length > 0 ? prior : void 0;
2264
+ }
2235
2265
  function toPosix(p) {
2236
2266
  return p.split("\\").join("/");
2237
2267
  }
@@ -2309,10 +2339,9 @@ function resolveDistToSrc(absFilepath, line) {
2309
2339
  }
2310
2340
  }
2311
2341
  function callSiteFromSpan(span, serviceNode, scanPath) {
2312
- const filepath = span.attributes[CODE_FILEPATH_ATTR];
2313
- if (typeof filepath !== "string" || filepath.length === 0) return null;
2314
- const linenoRaw = span.attributes[CODE_LINENO_ATTR];
2315
- let line = typeof linenoRaw === "number" && Number.isFinite(linenoRaw) ? linenoRaw : void 0;
2342
+ const filepath = codeFilepathOf(span.attributes);
2343
+ if (filepath === void 0) return null;
2344
+ let line = codeLinenoOf(span.attributes);
2316
2345
  const abs = toPosix(filepath).replace(/^file:\/\//, "");
2317
2346
  const resolved = resolveDistToSrc(abs, line);
2318
2347
  let effectivePath = filepath;
@@ -2327,8 +2356,7 @@ function callSiteFromSpan(span, serviceNode, scanPath) {
2327
2356
  if (!resolved && abs.endsWith(".js") && relPath.startsWith("dist/") && serviceNode?.name) {
2328
2357
  warnNoSourceMaps(serviceNode.name);
2329
2358
  }
2330
- const fnRaw = span.attributes[CODE_FUNCTION_ATTR];
2331
- const fn = typeof fnRaw === "string" && fnRaw.length > 0 ? fnRaw : void 0;
2359
+ const fn = codeFunctionOf(span.attributes);
2332
2360
  return {
2333
2361
  relPath,
2334
2362
  ...line !== void 0 ? { line } : {},
@@ -2910,6 +2938,18 @@ async function handleSpan(ctx, span) {
2910
2938
  callSiteEvidence
2911
2939
  );
2912
2940
  }
2941
+ if (span.dbTable) {
2942
+ const tableId = ensureInfraNode(ctx.graph, "sql-table", span.dbTable, "self");
2943
+ upsertObservedEdge(
2944
+ ctx.graph,
2945
+ import_types3.EdgeType.CALLS,
2946
+ observedSource(),
2947
+ tableId,
2948
+ ts,
2949
+ isError,
2950
+ callSiteEvidence
2951
+ );
2952
+ }
2913
2953
  }
2914
2954
  } else if (span.messagingSystem && span.messagingDestination && spanMintsMessagingEdge(span.kind)) {
2915
2955
  const targetId = ensureMessagingDestinationNode(
@@ -4074,14 +4114,14 @@ function buildServiceHostIndex(services) {
4074
4114
  }
4075
4115
  async function walkSourceFiles(dir) {
4076
4116
  const out = [];
4077
- async function walk3(current) {
4117
+ async function walk4(current) {
4078
4118
  const entries = await import_node_fs10.promises.readdir(current, { withFileTypes: true }).catch(() => []);
4079
4119
  for (const entry2 of entries) {
4080
4120
  const full = import_node_path10.default.join(current, entry2.name);
4081
4121
  if (entry2.isDirectory()) {
4082
4122
  if (IGNORED_DIRS.has(entry2.name)) continue;
4083
4123
  if (await isPythonVenvDir(full)) continue;
4084
- await walk3(full);
4124
+ await walk4(full);
4085
4125
  } 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
4086
4126
  // would attribute our instrumentation imports to the user's service.
4087
4127
  !isNeatAuthoredSourceFile(entry2.name)) {
@@ -4089,7 +4129,7 @@ async function walkSourceFiles(dir) {
4089
4129
  }
4090
4130
  }
4091
4131
  }
4092
- await walk3(dir);
4132
+ await walk4(dir);
4093
4133
  return out;
4094
4134
  }
4095
4135
  async function loadSourceFiles(dir) {
@@ -5239,20 +5279,20 @@ var import_node_path21 = __toESM(require("path"), 1);
5239
5279
  var import_types10 = require("@neat.is/types");
5240
5280
  async function walkConfigFiles(dir) {
5241
5281
  const out = [];
5242
- async function walk3(current) {
5282
+ async function walk4(current) {
5243
5283
  const entries = await import_node_fs14.promises.readdir(current, { withFileTypes: true });
5244
5284
  for (const entry2 of entries) {
5245
5285
  const full = import_node_path21.default.join(current, entry2.name);
5246
5286
  if (entry2.isDirectory()) {
5247
5287
  if (IGNORED_DIRS.has(entry2.name)) continue;
5248
5288
  if (await isPythonVenvDir(full)) continue;
5249
- await walk3(full);
5289
+ await walk4(full);
5250
5290
  } else if (entry2.isFile() && isConfigFile(entry2.name).match) {
5251
5291
  out.push(full);
5252
5292
  }
5253
5293
  }
5254
5294
  }
5255
- await walk3(dir);
5295
+ await walk4(dir);
5256
5296
  return out;
5257
5297
  }
5258
5298
  async function addConfigNodes(graph, services, scanPath) {
@@ -5305,6 +5345,7 @@ init_cjs_shims();
5305
5345
  var import_node_path22 = __toESM(require("path"), 1);
5306
5346
  var import_tree_sitter2 = __toESM(require("tree-sitter"), 1);
5307
5347
  var import_tree_sitter_javascript2 = __toESM(require("tree-sitter-javascript"), 1);
5348
+ var import_tree_sitter_python2 = __toESM(require("tree-sitter-python"), 1);
5308
5349
  var import_types11 = require("@neat.is/types");
5309
5350
  var PARSE_CHUNK2 = 16384;
5310
5351
  function parseSource2(parser, source) {
@@ -5317,6 +5358,11 @@ function makeJsParser2() {
5317
5358
  p.setLanguage(import_tree_sitter_javascript2.default);
5318
5359
  return p;
5319
5360
  }
5361
+ function makePyParser2() {
5362
+ const p = new import_tree_sitter2.default();
5363
+ p.setLanguage(import_tree_sitter_python2.default);
5364
+ return p;
5365
+ }
5320
5366
  var ROUTER_METHODS = /* @__PURE__ */ new Set([
5321
5367
  "get",
5322
5368
  "post",
@@ -5329,6 +5375,7 @@ var ROUTER_METHODS = /* @__PURE__ */ new Set([
5329
5375
  ]);
5330
5376
  var NEXT_APP_METHODS = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]);
5331
5377
  var JS_ROUTE_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx"]);
5378
+ var FASTAPI_METHODS = /* @__PURE__ */ new Set(["get", "post", "put", "patch", "delete", "options", "head", "trace"]);
5332
5379
  function canonicalizeTemplate(raw) {
5333
5380
  let p = raw.split("?")[0].split("#")[0];
5334
5381
  if (!p.startsWith("/")) p = "/" + p;
@@ -5556,8 +5603,102 @@ function nextRoutesFromFile(source, relFile, parser) {
5556
5603
  }
5557
5604
  return [];
5558
5605
  }
5606
+ function pyStaticStringText(node) {
5607
+ if (node.type !== "string") return null;
5608
+ for (let i = 0; i < node.namedChildCount; i++) {
5609
+ const child = node.namedChild(i);
5610
+ if (child?.type === "interpolation") return null;
5611
+ if (child?.type === "string_content") return child.text;
5612
+ }
5613
+ return "";
5614
+ }
5615
+ function keywordArrayStrings(argsNode, key) {
5616
+ for (let i = 0; i < argsNode.namedChildCount; i++) {
5617
+ const arg = argsNode.namedChild(i);
5618
+ if (arg?.type !== "keyword_argument") continue;
5619
+ if (arg.childForFieldName("name")?.text !== key) continue;
5620
+ const val = arg.childForFieldName("value");
5621
+ if (!val || val.type !== "list") return [];
5622
+ const out = [];
5623
+ for (let j = 0; j < val.namedChildCount; j++) {
5624
+ const el = val.namedChild(j);
5625
+ if (el?.type === "string") {
5626
+ const s = pyStaticStringText(el);
5627
+ if (s) out.push(s);
5628
+ }
5629
+ }
5630
+ return out;
5631
+ }
5632
+ return [];
5633
+ }
5634
+ function collectApiRouterPrefixes(root) {
5635
+ const prefixes = /* @__PURE__ */ new Map();
5636
+ walk(root, (node) => {
5637
+ if (node.type !== "assignment") return;
5638
+ const right = node.childForFieldName("right");
5639
+ if (!right || right.type !== "call") return;
5640
+ const fn = right.childForFieldName("function");
5641
+ if (!fn) return;
5642
+ const ctor = fn.type === "attribute" ? fn.childForFieldName("attribute")?.text : fn.text;
5643
+ if (ctor !== "APIRouter") return;
5644
+ const left = node.childForFieldName("left");
5645
+ if (!left || left.type !== "identifier") return;
5646
+ const args = right.childForFieldName("arguments");
5647
+ if (!args) return;
5648
+ for (let i = 0; i < args.namedChildCount; i++) {
5649
+ const arg = args.namedChild(i);
5650
+ if (arg?.type !== "keyword_argument") continue;
5651
+ if (arg.childForFieldName("name")?.text !== "prefix") continue;
5652
+ const val = arg.childForFieldName("value");
5653
+ const p = val ? pyStaticStringText(val) : null;
5654
+ if (p !== null) prefixes.set(left.text, p);
5655
+ }
5656
+ });
5657
+ return prefixes;
5658
+ }
5659
+ function fastapiRoutesFromSource(source, parser) {
5660
+ const tree = parseSource2(parser, source);
5661
+ const prefixes = collectApiRouterPrefixes(tree.rootNode);
5662
+ const out = [];
5663
+ walk(tree.rootNode, (node) => {
5664
+ if (node.type !== "decorator") return;
5665
+ const call = node.namedChild(0);
5666
+ if (!call || call.type !== "call") return;
5667
+ const fn = call.childForFieldName("function");
5668
+ if (!fn || fn.type !== "attribute") return;
5669
+ const method = fn.childForFieldName("attribute")?.text?.toLowerCase();
5670
+ if (!method) return;
5671
+ const isVerb = FASTAPI_METHODS.has(method);
5672
+ if (!isVerb && method !== "api_route") return;
5673
+ const args = call.childForFieldName("arguments");
5674
+ const first = args?.namedChild(0);
5675
+ if (!first || first.type !== "string") return;
5676
+ const rawPath = pyStaticStringText(first);
5677
+ if (rawPath === null || !rawPath.startsWith("/")) return;
5678
+ const obj = fn.childForFieldName("object")?.text;
5679
+ const prefix = obj ? prefixes.get(obj) ?? "" : "";
5680
+ const pathTemplate = canonicalizeTemplate(prefix + rawPath);
5681
+ const line = node.startPosition.row + 1;
5682
+ if (isVerb) {
5683
+ out.push({ method: method.toUpperCase(), pathTemplate, line, framework: "fastapi" });
5684
+ return;
5685
+ }
5686
+ const methods = keywordArrayStrings(args, "methods");
5687
+ const list = methods.length > 0 ? methods : ["ALL"];
5688
+ for (const m of list) {
5689
+ out.push({
5690
+ method: m === "ALL" ? "ALL" : m.toUpperCase(),
5691
+ pathTemplate,
5692
+ line,
5693
+ framework: "fastapi"
5694
+ });
5695
+ }
5696
+ });
5697
+ return out;
5698
+ }
5559
5699
  async function addRoutes(graph, services) {
5560
5700
  const jsParser = makeJsParser2();
5701
+ const pyParser = makePyParser2();
5561
5702
  let nodesAdded = 0;
5562
5703
  let edgesAdded = 0;
5563
5704
  for (const service of services) {
@@ -5569,15 +5710,20 @@ async function addRoutes(graph, services) {
5569
5710
  const hasFastify = deps["fastify"] !== void 0;
5570
5711
  const hasHono = deps["hono"] !== void 0;
5571
5712
  const hasNext = deps["next"] !== void 0;
5572
- if (!hasExpress && !hasFastify && !hasHono && !hasNext) continue;
5713
+ const hasFastapi = deps["fastapi"] !== void 0;
5714
+ if (!hasExpress && !hasFastify && !hasHono && !hasNext && !hasFastapi) continue;
5573
5715
  const files = await loadSourceFiles(service.dir);
5574
5716
  for (const file of files) {
5575
5717
  if (isTestPath(file.path)) continue;
5576
- if (!JS_ROUTE_EXTENSIONS.has(import_node_path22.default.extname(file.path))) continue;
5718
+ const ext = import_node_path22.default.extname(file.path);
5719
+ const isPy = ext === ".py";
5720
+ if (!JS_ROUTE_EXTENSIONS.has(ext) && !isPy) continue;
5577
5721
  const relFile = toPosix2(import_node_path22.default.relative(service.dir, file.path));
5578
5722
  let routes;
5579
5723
  try {
5580
- if (hasNext && (isNextAppRouteFile(relFile) || isNextPagesApiFile(relFile))) {
5724
+ if (isPy) {
5725
+ routes = hasFastapi ? fastapiRoutesFromSource(file.content, pyParser) : [];
5726
+ } else if (hasNext && (isNextAppRouteFile(relFile) || isNextPagesApiFile(relFile))) {
5581
5727
  routes = nextRoutesFromFile(file.content, relFile, jsParser);
5582
5728
  } else if (hasExpress || hasFastify || hasHono) {
5583
5729
  routes = serverRoutesFromSource(file.content, jsParser, hasExpress, hasFastify, hasHono);
@@ -5673,20 +5819,20 @@ function grpcMethodsFromProto(content, fqPackage) {
5673
5819
  }
5674
5820
  async function walkProtoFiles(dir) {
5675
5821
  const out = [];
5676
- async function walk3(current) {
5822
+ async function walk4(current) {
5677
5823
  const entries = await import_node_fs15.promises.readdir(current, { withFileTypes: true }).catch(() => []);
5678
5824
  for (const entry2 of entries) {
5679
5825
  const full = import_node_path23.default.join(current, entry2.name);
5680
5826
  if (entry2.isDirectory()) {
5681
5827
  if (IGNORED_DIRS.has(entry2.name)) continue;
5682
5828
  if (await isPythonVenvDir(full)) continue;
5683
- await walk3(full);
5829
+ await walk4(full);
5684
5830
  } else if (entry2.isFile() && import_node_path23.default.extname(entry2.name) === PROTO_EXTENSION) {
5685
5831
  out.push(full);
5686
5832
  }
5687
5833
  }
5688
5834
  }
5689
- await walk3(dir);
5835
+ await walk4(dir);
5690
5836
  return out;
5691
5837
  }
5692
5838
  async function addGrpcMethods(graph, services) {
@@ -5754,14 +5900,14 @@ async function addGrpcMethods(graph, services) {
5754
5900
 
5755
5901
  // src/extract/calls/index.ts
5756
5902
  init_cjs_shims();
5757
- var import_types21 = require("@neat.is/types");
5903
+ var import_types22 = require("@neat.is/types");
5758
5904
 
5759
5905
  // src/extract/calls/http.ts
5760
5906
  init_cjs_shims();
5761
5907
  var import_node_path24 = __toESM(require("path"), 1);
5762
5908
  var import_tree_sitter3 = __toESM(require("tree-sitter"), 1);
5763
5909
  var import_tree_sitter_javascript3 = __toESM(require("tree-sitter-javascript"), 1);
5764
- var import_tree_sitter_python2 = __toESM(require("tree-sitter-python"), 1);
5910
+ var import_tree_sitter_python3 = __toESM(require("tree-sitter-python"), 1);
5765
5911
  var import_types13 = require("@neat.is/types");
5766
5912
  var STRING_LITERAL_NODE_TYPES = /* @__PURE__ */ new Set(["string_fragment", "string_content"]);
5767
5913
  var JSX_EXTERNAL_LINK_TAGS = /* @__PURE__ */ new Set(["a", "Link", "NavLink", "ExternalLink", "Anchor"]);
@@ -5816,14 +5962,14 @@ function makeJsParser3() {
5816
5962
  p.setLanguage(import_tree_sitter_javascript3.default);
5817
5963
  return p;
5818
5964
  }
5819
- function makePyParser2() {
5965
+ function makePyParser3() {
5820
5966
  const p = new import_tree_sitter3.default();
5821
- p.setLanguage(import_tree_sitter_python2.default);
5967
+ p.setLanguage(import_tree_sitter_python3.default);
5822
5968
  return p;
5823
5969
  }
5824
5970
  async function addHttpCallEdges(graph, services) {
5825
5971
  const jsParser = makeJsParser3();
5826
- const pyParser = makePyParser2();
5972
+ const pyParser = makePyParser3();
5827
5973
  const { knownHosts, hostToNodeId } = buildServiceHostIndex(services);
5828
5974
  let nodesAdded = 0;
5829
5975
  let edgesAdded = 0;
@@ -6763,15 +6909,130 @@ async function mongooseCrossFileEndpoints(files, serviceDir) {
6763
6909
  return out;
6764
6910
  }
6765
6911
 
6912
+ // src/extract/calls/sqlalchemy.ts
6913
+ init_cjs_shims();
6914
+ var import_node_path32 = __toESM(require("path"), 1);
6915
+ var import_tree_sitter5 = __toESM(require("tree-sitter"), 1);
6916
+ var import_tree_sitter_python4 = __toESM(require("tree-sitter-python"), 1);
6917
+ var import_types21 = require("@neat.is/types");
6918
+ var SQLALCHEMY_IMPORT_RE = /(?:from|import)\s+(?:flask_sqlalchemy|sqlalchemy)\b/;
6919
+ var PARSE_CHUNK5 = 16384;
6920
+ function makePyParser4() {
6921
+ const p = new import_tree_sitter5.default();
6922
+ p.setLanguage(import_tree_sitter_python4.default);
6923
+ return p;
6924
+ }
6925
+ function parseSource5(parser, source) {
6926
+ return parser.parse(
6927
+ (index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK5)
6928
+ );
6929
+ }
6930
+ function flaskSqlalchemyTableName(className) {
6931
+ return className.replace(/((?<=[a-z0-9])[A-Z]|(?!^)[A-Z](?=[a-z]))/g, "_$1").toLowerCase().replace(/^_+/, "");
6932
+ }
6933
+ function namedChildren(node) {
6934
+ const out = [];
6935
+ for (let i = 0; i < node.namedChildCount; i++) {
6936
+ const c = node.namedChild(i);
6937
+ if (c) out.push(c);
6938
+ }
6939
+ return out;
6940
+ }
6941
+ function pyStaticStringText2(node) {
6942
+ if (node.type !== "string") return null;
6943
+ for (const child of namedChildren(node)) {
6944
+ if (child.type === "interpolation") return null;
6945
+ if (child.type === "string_content") return child.text;
6946
+ }
6947
+ return "";
6948
+ }
6949
+ function explicitTablename(body) {
6950
+ for (const stmt of namedChildren(body)) {
6951
+ if (stmt.type !== "expression_statement") continue;
6952
+ const assign = stmt.namedChild(0);
6953
+ if (assign?.type !== "assignment") continue;
6954
+ if (assign.childForFieldName("left")?.text !== "__tablename__") continue;
6955
+ const right = assign.childForFieldName("right");
6956
+ if (right?.type === "string") {
6957
+ const s = pyStaticStringText2(right);
6958
+ return s ? { name: s } : "computed";
6959
+ }
6960
+ return "computed";
6961
+ }
6962
+ return null;
6963
+ }
6964
+ function extendsFlaskModel(cls) {
6965
+ const supers = cls.childForFieldName("superclasses");
6966
+ if (!supers) return false;
6967
+ for (const a of namedChildren(supers)) {
6968
+ const t = a.text;
6969
+ if (t === "db.Model" || t === "Model" || t.endsWith(".Model")) return true;
6970
+ }
6971
+ return false;
6972
+ }
6973
+ function walk3(node, visit) {
6974
+ visit(node);
6975
+ for (const c of namedChildren(node)) walk3(c, visit);
6976
+ }
6977
+ function sqlalchemyEndpointsFromFile(file, serviceDir) {
6978
+ if (!SQLALCHEMY_IMPORT_RE.test(file.content)) return [];
6979
+ const tree = parseSource5(makePyParser4(), file.content);
6980
+ const out = [];
6981
+ const seen = /* @__PURE__ */ new Set();
6982
+ const push = (name, line) => {
6983
+ if (seen.has(name)) return;
6984
+ seen.add(name);
6985
+ out.push({
6986
+ infraId: (0, import_types21.infraId)("sql-table", name),
6987
+ name,
6988
+ kind: "sql-table",
6989
+ edgeType: "CALLS",
6990
+ confidenceKind: "verified-call-site",
6991
+ evidence: {
6992
+ file: import_node_path32.default.relative(serviceDir, file.path),
6993
+ line,
6994
+ snippet: snippet(file.content, line)
6995
+ }
6996
+ });
6997
+ };
6998
+ walk3(tree.rootNode, (node) => {
6999
+ if (node.type === "class_definition") {
7000
+ const body = node.childForFieldName("body");
7001
+ const nameNode = node.childForFieldName("name");
7002
+ if (!body || !nameNode) return;
7003
+ const line = node.startPosition.row + 1;
7004
+ const explicit = explicitTablename(body);
7005
+ if (explicit === "computed") return;
7006
+ if (explicit) {
7007
+ push(explicit.name, line);
7008
+ return;
7009
+ }
7010
+ if (extendsFlaskModel(node)) push(flaskSqlalchemyTableName(nameNode.text), line);
7011
+ return;
7012
+ }
7013
+ if (node.type === "call") {
7014
+ const fn = node.childForFieldName("function");
7015
+ const fnText = fn?.text;
7016
+ if (fnText !== "Table" && fnText !== "sa.Table" && fnText !== "sqlalchemy.Table") return;
7017
+ const first = node.childForFieldName("arguments")?.namedChild(0);
7018
+ if (first?.type === "string") {
7019
+ const s = pyStaticStringText2(first);
7020
+ if (s) push(s, node.startPosition.row + 1);
7021
+ }
7022
+ }
7023
+ });
7024
+ return out;
7025
+ }
7026
+
6766
7027
  // src/extract/calls/index.ts
6767
7028
  function edgeTypeFromEndpoint(ep) {
6768
7029
  switch (ep.edgeType) {
6769
7030
  case "PUBLISHES_TO":
6770
- return import_types21.EdgeType.PUBLISHES_TO;
7031
+ return import_types22.EdgeType.PUBLISHES_TO;
6771
7032
  case "CONSUMES_FROM":
6772
- return import_types21.EdgeType.CONSUMES_FROM;
7033
+ return import_types22.EdgeType.CONSUMES_FROM;
6773
7034
  default:
6774
- return import_types21.EdgeType.CALLS;
7035
+ return import_types22.EdgeType.CALLS;
6775
7036
  }
6776
7037
  }
6777
7038
  function isAwsKind(kind) {
@@ -6795,6 +7056,7 @@ async function addExternalEndpointEdges(graph, services) {
6795
7056
  endpoints.push(...grpcEndpointsFromFile(maskedFile, service.dir));
6796
7057
  endpoints.push(...supabaseEndpointsFromFile(maskedFile, service.dir));
6797
7058
  endpoints.push(...mongooseEndpointsFromFile(maskedFile, service.dir));
7059
+ endpoints.push(...sqlalchemyEndpointsFromFile(maskedFile, service.dir));
6798
7060
  }
6799
7061
  endpoints.push(...await mongooseCrossFileEndpoints(maskedFiles, service.dir));
6800
7062
  if (endpoints.length === 0) continue;
@@ -6803,7 +7065,7 @@ async function addExternalEndpointEdges(graph, services) {
6803
7065
  if (!graph.hasNode(ep.infraId)) {
6804
7066
  const node = {
6805
7067
  id: ep.infraId,
6806
- type: import_types21.NodeType.InfraNode,
7068
+ type: import_types22.NodeType.InfraNode,
6807
7069
  name: ep.name,
6808
7070
  // #238 — `aws-*` covers AWS-SDK client kinds (aws-s3, aws-dynamodb,
6809
7071
  // aws-cognito-identity-provider, …); `s3-` / `dynamodb-` cover the
@@ -6815,7 +7077,7 @@ async function addExternalEndpointEdges(graph, services) {
6815
7077
  nodesAdded++;
6816
7078
  }
6817
7079
  const edgeType = edgeTypeFromEndpoint(ep);
6818
- const confidence = (0, import_types21.confidenceForExtracted)(ep.confidenceKind);
7080
+ const confidence = (0, import_types22.confidenceForExtracted)(ep.confidenceKind);
6819
7081
  const relFile = toPosix2(ep.evidence.file);
6820
7082
  const { fileNodeId, nodesAdded: n, edgesAdded: e } = ensureFileNode(
6821
7083
  graph,
@@ -6825,7 +7087,7 @@ async function addExternalEndpointEdges(graph, services) {
6825
7087
  );
6826
7088
  nodesAdded += n;
6827
7089
  edgesAdded += e;
6828
- if (!(0, import_types21.passesExtractedFloor)(confidence)) {
7090
+ if (!(0, import_types22.passesExtractedFloor)(confidence)) {
6829
7091
  noteExtractedDropped({
6830
7092
  source: fileNodeId,
6831
7093
  target: ep.infraId,
@@ -6845,7 +7107,7 @@ async function addExternalEndpointEdges(graph, services) {
6845
7107
  source: fileNodeId,
6846
7108
  target: ep.infraId,
6847
7109
  type: edgeType,
6848
- provenance: import_types21.Provenance.EXTRACTED,
7110
+ provenance: import_types22.Provenance.EXTRACTED,
6849
7111
  confidence,
6850
7112
  evidence: ep.evidence
6851
7113
  };
@@ -6871,16 +7133,16 @@ init_cjs_shims();
6871
7133
 
6872
7134
  // src/extract/infra/docker-compose.ts
6873
7135
  init_cjs_shims();
6874
- var import_node_path32 = __toESM(require("path"), 1);
6875
- var import_types23 = require("@neat.is/types");
7136
+ var import_node_path33 = __toESM(require("path"), 1);
7137
+ var import_types24 = require("@neat.is/types");
6876
7138
 
6877
7139
  // src/extract/infra/shared.ts
6878
7140
  init_cjs_shims();
6879
- var import_types22 = require("@neat.is/types");
7141
+ var import_types23 = require("@neat.is/types");
6880
7142
  function makeInfraNode(kind, name, provider = "self", extras) {
6881
7143
  return {
6882
- id: (0, import_types22.infraId)(kind, name),
6883
- type: import_types22.NodeType.InfraNode,
7144
+ id: (0, import_types23.infraId)(kind, name),
7145
+ type: import_types23.NodeType.InfraNode,
6884
7146
  name,
6885
7147
  provider,
6886
7148
  kind,
@@ -6924,8 +7186,8 @@ function emitPlatformResourceEdge(graph, anchorId, edgeType, kind, name, provide
6924
7186
  source: anchorId,
6925
7187
  target: node.id,
6926
7188
  type: edgeType,
6927
- provenance: import_types22.Provenance.EXTRACTED,
6928
- confidence: (0, import_types22.confidenceForExtracted)("structural"),
7189
+ provenance: import_types23.Provenance.EXTRACTED,
7190
+ confidence: (0, import_types23.confidenceForExtracted)("structural"),
6929
7191
  evidence: { file: evidenceFile, ...line !== void 0 ? { line } : {} }
6930
7192
  };
6931
7193
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -6942,7 +7204,7 @@ function dependsOnList(value) {
6942
7204
  }
6943
7205
  function serviceNameToServiceNode(name, services) {
6944
7206
  for (const s of services) {
6945
- if (s.node.name === name || import_node_path32.default.basename(s.dir) === name) return s.node.id;
7207
+ if (s.node.name === name || import_node_path33.default.basename(s.dir) === name) return s.node.id;
6946
7208
  }
6947
7209
  return null;
6948
7210
  }
@@ -6951,7 +7213,7 @@ async function addComposeInfra(graph, scanPath, services) {
6951
7213
  let edgesAdded = 0;
6952
7214
  let composePath = null;
6953
7215
  for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
6954
- const abs = import_node_path32.default.join(scanPath, name);
7216
+ const abs = import_node_path33.default.join(scanPath, name);
6955
7217
  if (await exists(abs)) {
6956
7218
  composePath = abs;
6957
7219
  break;
@@ -6964,13 +7226,13 @@ async function addComposeInfra(graph, scanPath, services) {
6964
7226
  } catch (err) {
6965
7227
  recordExtractionError(
6966
7228
  "infra docker-compose",
6967
- import_node_path32.default.relative(scanPath, composePath),
7229
+ import_node_path33.default.relative(scanPath, composePath),
6968
7230
  err
6969
7231
  );
6970
7232
  return { nodesAdded, edgesAdded };
6971
7233
  }
6972
7234
  if (!compose?.services) return { nodesAdded, edgesAdded };
6973
- const evidenceFile = import_node_path32.default.relative(scanPath, composePath).split(import_node_path32.default.sep).join("/");
7235
+ const evidenceFile = import_node_path33.default.relative(scanPath, composePath).split(import_node_path33.default.sep).join("/");
6974
7236
  const composeNameToNodeId = /* @__PURE__ */ new Map();
6975
7237
  for (const [composeName, svc] of Object.entries(compose.services)) {
6976
7238
  const matchedServiceId = serviceNameToServiceNode(composeName, services);
@@ -6992,15 +7254,15 @@ async function addComposeInfra(graph, scanPath, services) {
6992
7254
  for (const dep of dependsOnList(svc.depends_on)) {
6993
7255
  const targetId = composeNameToNodeId.get(dep);
6994
7256
  if (!targetId) continue;
6995
- const edgeId = (0, import_types4.extractedEdgeId)(sourceId, targetId, import_types23.EdgeType.DEPENDS_ON);
7257
+ const edgeId = (0, import_types4.extractedEdgeId)(sourceId, targetId, import_types24.EdgeType.DEPENDS_ON);
6996
7258
  if (graph.hasEdge(edgeId)) continue;
6997
7259
  const edge = {
6998
7260
  id: edgeId,
6999
7261
  source: sourceId,
7000
7262
  target: targetId,
7001
- type: import_types23.EdgeType.DEPENDS_ON,
7002
- provenance: import_types23.Provenance.EXTRACTED,
7003
- confidence: (0, import_types23.confidenceForExtracted)("structural"),
7263
+ type: import_types24.EdgeType.DEPENDS_ON,
7264
+ provenance: import_types24.Provenance.EXTRACTED,
7265
+ confidence: (0, import_types24.confidenceForExtracted)("structural"),
7004
7266
  evidence: { file: evidenceFile }
7005
7267
  };
7006
7268
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -7012,9 +7274,9 @@ async function addComposeInfra(graph, scanPath, services) {
7012
7274
 
7013
7275
  // src/extract/infra/dockerfile.ts
7014
7276
  init_cjs_shims();
7015
- var import_node_path33 = __toESM(require("path"), 1);
7277
+ var import_node_path34 = __toESM(require("path"), 1);
7016
7278
  var import_node_fs16 = require("fs");
7017
- var import_types24 = require("@neat.is/types");
7279
+ var import_types25 = require("@neat.is/types");
7018
7280
  function readDockerfile(content) {
7019
7281
  let image = null;
7020
7282
  const ports = [];
@@ -7043,7 +7305,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
7043
7305
  let nodesAdded = 0;
7044
7306
  let edgesAdded = 0;
7045
7307
  for (const service of services) {
7046
- const dockerfilePath = import_node_path33.default.join(service.dir, "Dockerfile");
7308
+ const dockerfilePath = import_node_path34.default.join(service.dir, "Dockerfile");
7047
7309
  if (!await exists(dockerfilePath)) continue;
7048
7310
  let content;
7049
7311
  try {
@@ -7051,7 +7313,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
7051
7313
  } catch (err) {
7052
7314
  recordExtractionError(
7053
7315
  "infra dockerfile",
7054
- import_node_path33.default.relative(scanPath, dockerfilePath),
7316
+ import_node_path34.default.relative(scanPath, dockerfilePath),
7055
7317
  err
7056
7318
  );
7057
7319
  continue;
@@ -7063,8 +7325,8 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
7063
7325
  graph.addNode(node.id, node);
7064
7326
  nodesAdded++;
7065
7327
  }
7066
- const relDockerfile = toPosix2(import_node_path33.default.relative(service.dir, dockerfilePath));
7067
- const evidenceFile = toPosix2(import_node_path33.default.relative(scanPath, dockerfilePath));
7328
+ const relDockerfile = toPosix2(import_node_path34.default.relative(service.dir, dockerfilePath));
7329
+ const evidenceFile = toPosix2(import_node_path34.default.relative(scanPath, dockerfilePath));
7068
7330
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
7069
7331
  graph,
7070
7332
  service.pkg.name,
@@ -7073,15 +7335,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
7073
7335
  );
7074
7336
  nodesAdded += fn;
7075
7337
  edgesAdded += fe;
7076
- const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, node.id, import_types24.EdgeType.RUNS_ON);
7338
+ const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, node.id, import_types25.EdgeType.RUNS_ON);
7077
7339
  if (!graph.hasEdge(edgeId)) {
7078
7340
  const edge = {
7079
7341
  id: edgeId,
7080
7342
  source: fileNodeId,
7081
7343
  target: node.id,
7082
- type: import_types24.EdgeType.RUNS_ON,
7083
- provenance: import_types24.Provenance.EXTRACTED,
7084
- confidence: (0, import_types24.confidenceForExtracted)("structural"),
7344
+ type: import_types25.EdgeType.RUNS_ON,
7345
+ provenance: import_types25.Provenance.EXTRACTED,
7346
+ confidence: (0, import_types25.confidenceForExtracted)("structural"),
7085
7347
  evidence: {
7086
7348
  file: evidenceFile,
7087
7349
  ...facts.entrypoint ? { snippet: facts.entrypoint.slice(0, 120) } : {}
@@ -7096,15 +7358,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
7096
7358
  graph.addNode(portNode.id, portNode);
7097
7359
  nodesAdded++;
7098
7360
  }
7099
- const portEdgeId = (0, import_types4.extractedEdgeId)(fileNodeId, portNode.id, import_types24.EdgeType.CONNECTS_TO);
7361
+ const portEdgeId = (0, import_types4.extractedEdgeId)(fileNodeId, portNode.id, import_types25.EdgeType.CONNECTS_TO);
7100
7362
  if (graph.hasEdge(portEdgeId)) continue;
7101
7363
  const portEdge = {
7102
7364
  id: portEdgeId,
7103
7365
  source: fileNodeId,
7104
7366
  target: portNode.id,
7105
- type: import_types24.EdgeType.CONNECTS_TO,
7106
- provenance: import_types24.Provenance.EXTRACTED,
7107
- confidence: (0, import_types24.confidenceForExtracted)("structural"),
7367
+ type: import_types25.EdgeType.CONNECTS_TO,
7368
+ provenance: import_types25.Provenance.EXTRACTED,
7369
+ confidence: (0, import_types25.confidenceForExtracted)("structural"),
7108
7370
  evidence: { file: evidenceFile, snippet: `EXPOSE ${port}` }
7109
7371
  };
7110
7372
  graph.addEdgeWithKey(portEdgeId, portEdge.source, portEdge.target, portEdge);
@@ -7117,8 +7379,8 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
7117
7379
  // src/extract/infra/terraform.ts
7118
7380
  init_cjs_shims();
7119
7381
  var import_node_fs17 = require("fs");
7120
- var import_node_path34 = __toESM(require("path"), 1);
7121
- var import_types25 = require("@neat.is/types");
7382
+ var import_node_path35 = __toESM(require("path"), 1);
7383
+ var import_types26 = require("@neat.is/types");
7122
7384
  var RESOURCE_RE = /resource\s+"(aws_[A-Za-z0-9_]+)"\s+"([A-Za-z0-9_-]+)"/g;
7123
7385
  var REFERENCE_RE = /(?<![\w.])(aws_[A-Za-z0-9_]+)\.([A-Za-z0-9_-]+)/g;
7124
7386
  async function walkTfFiles(start, depth = 0, max = 5) {
@@ -7128,11 +7390,11 @@ async function walkTfFiles(start, depth = 0, max = 5) {
7128
7390
  for (const entry2 of entries) {
7129
7391
  if (entry2.isDirectory()) {
7130
7392
  if (IGNORED_DIRS.has(entry2.name) || entry2.name === ".terraform") continue;
7131
- const child = import_node_path34.default.join(start, entry2.name);
7393
+ const child = import_node_path35.default.join(start, entry2.name);
7132
7394
  if (await isPythonVenvDir(child)) continue;
7133
7395
  out.push(...await walkTfFiles(child, depth + 1, max));
7134
7396
  } else if (entry2.isFile() && entry2.name.endsWith(".tf")) {
7135
- out.push(import_node_path34.default.join(start, entry2.name));
7397
+ out.push(import_node_path35.default.join(start, entry2.name));
7136
7398
  }
7137
7399
  }
7138
7400
  return out;
@@ -7164,7 +7426,7 @@ async function addTerraformResources(graph, scanPath) {
7164
7426
  const files = await walkTfFiles(scanPath);
7165
7427
  for (const file of files) {
7166
7428
  const content = await import_node_fs17.promises.readFile(file, "utf8");
7167
- const evidenceFile = toPosix2(import_node_path34.default.relative(scanPath, file));
7429
+ const evidenceFile = toPosix2(import_node_path35.default.relative(scanPath, file));
7168
7430
  const resources = [];
7169
7431
  const byKey = /* @__PURE__ */ new Map();
7170
7432
  RESOURCE_RE.lastIndex = 0;
@@ -7199,16 +7461,16 @@ async function addTerraformResources(graph, scanPath) {
7199
7461
  if (!target) continue;
7200
7462
  if (seen.has(target.nodeId)) continue;
7201
7463
  seen.add(target.nodeId);
7202
- const edgeId = (0, import_types4.extractedEdgeId)(resource.nodeId, target.nodeId, import_types25.EdgeType.DEPENDS_ON);
7464
+ const edgeId = (0, import_types4.extractedEdgeId)(resource.nodeId, target.nodeId, import_types26.EdgeType.DEPENDS_ON);
7203
7465
  if (graph.hasEdge(edgeId)) continue;
7204
7466
  const line = lineAt2(content, resource.bodyOffset + ref.index);
7205
7467
  const edge = {
7206
7468
  id: edgeId,
7207
7469
  source: resource.nodeId,
7208
7470
  target: target.nodeId,
7209
- type: import_types25.EdgeType.DEPENDS_ON,
7210
- provenance: import_types25.Provenance.EXTRACTED,
7211
- confidence: (0, import_types25.confidenceForExtracted)("structural"),
7471
+ type: import_types26.EdgeType.DEPENDS_ON,
7472
+ provenance: import_types26.Provenance.EXTRACTED,
7473
+ confidence: (0, import_types26.confidenceForExtracted)("structural"),
7212
7474
  evidence: { file: evidenceFile, line, snippet: key }
7213
7475
  };
7214
7476
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -7222,7 +7484,7 @@ async function addTerraformResources(graph, scanPath) {
7222
7484
  // src/extract/infra/k8s.ts
7223
7485
  init_cjs_shims();
7224
7486
  var import_node_fs18 = require("fs");
7225
- var import_node_path35 = __toESM(require("path"), 1);
7487
+ var import_node_path36 = __toESM(require("path"), 1);
7226
7488
  var import_yaml3 = require("yaml");
7227
7489
  var K8S_KIND_TO_INFRA_KIND = {
7228
7490
  Service: "k8s-service",
@@ -7240,11 +7502,11 @@ async function walkYamlFiles2(start, depth = 0, max = 5) {
7240
7502
  for (const entry2 of entries) {
7241
7503
  if (entry2.isDirectory()) {
7242
7504
  if (IGNORED_DIRS.has(entry2.name)) continue;
7243
- const child = import_node_path35.default.join(start, entry2.name);
7505
+ const child = import_node_path36.default.join(start, entry2.name);
7244
7506
  if (await isPythonVenvDir(child)) continue;
7245
7507
  out.push(...await walkYamlFiles2(child, depth + 1, max));
7246
- } else if (entry2.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path35.default.extname(entry2.name))) {
7247
- out.push(import_node_path35.default.join(start, entry2.name));
7508
+ } else if (entry2.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path36.default.extname(entry2.name))) {
7509
+ out.push(import_node_path36.default.join(start, entry2.name));
7248
7510
  }
7249
7511
  }
7250
7512
  return out;
@@ -7278,13 +7540,13 @@ async function addK8sResources(graph, scanPath) {
7278
7540
  // src/extract/infra/cloudflare.ts
7279
7541
  init_cjs_shims();
7280
7542
  var import_node_fs19 = require("fs");
7281
- var import_node_path36 = __toESM(require("path"), 1);
7543
+ var import_node_path37 = __toESM(require("path"), 1);
7282
7544
  var import_smol_toml2 = require("smol-toml");
7283
- var import_types26 = require("@neat.is/types");
7545
+ var import_types27 = require("@neat.is/types");
7284
7546
  var WRANGLER_FILENAMES = ["wrangler.toml", "wrangler.jsonc", "wrangler.json"];
7285
7547
  async function readWranglerConfig(dir) {
7286
7548
  for (const filename of WRANGLER_FILENAMES) {
7287
- const abs = import_node_path36.default.join(dir, filename);
7549
+ const abs = import_node_path37.default.join(dir, filename);
7288
7550
  if (!await exists(abs)) continue;
7289
7551
  const raw = await import_node_fs19.promises.readFile(abs, "utf8");
7290
7552
  const config = filename === "wrangler.toml" ? (0, import_smol_toml2.parse)(raw) : JSON.parse(maskCommentsInSource(raw));
@@ -7328,8 +7590,8 @@ function addResourceEdge(graph, anchorId, edgeType, kind, name, evidenceFile, li
7328
7590
  source: anchorId,
7329
7591
  target: node.id,
7330
7592
  type: edgeType,
7331
- provenance: import_types26.Provenance.EXTRACTED,
7332
- confidence: (0, import_types26.confidenceForExtracted)("structural"),
7593
+ provenance: import_types27.Provenance.EXTRACTED,
7594
+ confidence: (0, import_types27.confidenceForExtracted)("structural"),
7333
7595
  evidence: { file: evidenceFile, ...line !== void 0 ? { line } : {} }
7334
7596
  };
7335
7597
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -7347,11 +7609,11 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7347
7609
  try {
7348
7610
  read = await readWranglerConfig(service.dir);
7349
7611
  } catch (err) {
7350
- recordExtractionError("infra cloudflare", import_node_path36.default.relative(scanPath, service.dir), err);
7612
+ recordExtractionError("infra cloudflare", import_node_path37.default.relative(scanPath, service.dir), err);
7351
7613
  continue;
7352
7614
  }
7353
7615
  if (!read || !read.config.name) continue;
7354
- const evidenceFile = toPosix2(import_node_path36.default.relative(scanPath, import_node_path36.default.join(service.dir, read.relFile)));
7616
+ const evidenceFile = toPosix2(import_node_path37.default.relative(scanPath, import_node_path37.default.join(service.dir, read.relFile)));
7355
7617
  discovered.push({ service, config: read.config, relFile: read.relFile, raw: read.raw, evidenceFile });
7356
7618
  }
7357
7619
  for (const worker of discovered) {
@@ -7363,7 +7625,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7363
7625
  }
7364
7626
  let anchorId = service.node.id;
7365
7627
  if (config.main) {
7366
- const entryRelPath = toPosix2(import_node_path36.default.normalize(config.main));
7628
+ const entryRelPath = toPosix2(import_node_path37.default.normalize(config.main));
7367
7629
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
7368
7630
  graph,
7369
7631
  service.pkg.name,
@@ -7390,15 +7652,15 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7390
7652
  nodesAdded++;
7391
7653
  }
7392
7654
  if (runtimeNode.id !== anchorId) {
7393
- const runsOnId = (0, import_types4.extractedEdgeId)(anchorId, runtimeNode.id, import_types26.EdgeType.RUNS_ON);
7655
+ const runsOnId = (0, import_types4.extractedEdgeId)(anchorId, runtimeNode.id, import_types27.EdgeType.RUNS_ON);
7394
7656
  if (!graph.hasEdge(runsOnId)) {
7395
7657
  const edge = {
7396
7658
  id: runsOnId,
7397
7659
  source: anchorId,
7398
7660
  target: runtimeNode.id,
7399
- type: import_types26.EdgeType.RUNS_ON,
7400
- provenance: import_types26.Provenance.EXTRACTED,
7401
- confidence: (0, import_types26.confidenceForExtracted)("structural"),
7661
+ type: import_types27.EdgeType.RUNS_ON,
7662
+ provenance: import_types27.Provenance.EXTRACTED,
7663
+ confidence: (0, import_types27.confidenceForExtracted)("structural"),
7402
7664
  evidence: {
7403
7665
  file: evidenceFile,
7404
7666
  ...config.compatibility_date ? { snippet: `compatibility_date = ${config.compatibility_date}`.slice(0, 120) } : {}
@@ -7412,7 +7674,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7412
7674
  const result = addResourceEdge(
7413
7675
  graph,
7414
7676
  anchorId,
7415
- import_types26.EdgeType.CONNECTS_TO,
7677
+ import_types27.EdgeType.CONNECTS_TO,
7416
7678
  "cloudflare-route",
7417
7679
  route,
7418
7680
  evidenceFile,
@@ -7436,7 +7698,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7436
7698
  const result = addResourceEdge(
7437
7699
  graph,
7438
7700
  anchorId,
7439
- import_types26.EdgeType.DEPENDS_ON,
7701
+ import_types27.EdgeType.DEPENDS_ON,
7440
7702
  group.kind,
7441
7703
  name,
7442
7704
  evidenceFile,
@@ -7450,7 +7712,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7450
7712
  const result = addResourceEdge(
7451
7713
  graph,
7452
7714
  anchorId,
7453
- import_types26.EdgeType.DEPENDS_ON,
7715
+ import_types27.EdgeType.DEPENDS_ON,
7454
7716
  "cloudflare-cron",
7455
7717
  cron,
7456
7718
  evidenceFile,
@@ -7463,7 +7725,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7463
7725
  const result = addResourceEdge(
7464
7726
  graph,
7465
7727
  anchorId,
7466
- import_types26.EdgeType.DEPENDS_ON,
7728
+ import_types27.EdgeType.DEPENDS_ON,
7467
7729
  "cloudflare-env-var",
7468
7730
  varName,
7469
7731
  evidenceFile,
@@ -7476,15 +7738,15 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7476
7738
  if (!svc.service) continue;
7477
7739
  const target = workerIndex.get(svc.service);
7478
7740
  if (target && target.anchorId !== anchorId) {
7479
- const edgeId = (0, import_types4.extractedEdgeId)(anchorId, target.anchorId, import_types26.EdgeType.CALLS);
7741
+ const edgeId = (0, import_types4.extractedEdgeId)(anchorId, target.anchorId, import_types27.EdgeType.CALLS);
7480
7742
  if (!graph.hasEdge(edgeId)) {
7481
7743
  const edge = {
7482
7744
  id: edgeId,
7483
7745
  source: anchorId,
7484
7746
  target: target.anchorId,
7485
- type: import_types26.EdgeType.CALLS,
7486
- provenance: import_types26.Provenance.EXTRACTED,
7487
- confidence: (0, import_types26.confidenceForExtracted)("structural"),
7747
+ type: import_types27.EdgeType.CALLS,
7748
+ provenance: import_types27.Provenance.EXTRACTED,
7749
+ confidence: (0, import_types27.confidenceForExtracted)("structural"),
7488
7750
  evidence: { file: evidenceFile, line: lineContaining2(raw, svc.service) }
7489
7751
  };
7490
7752
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -7495,7 +7757,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7495
7757
  const result = addResourceEdge(
7496
7758
  graph,
7497
7759
  anchorId,
7498
- import_types26.EdgeType.DEPENDS_ON,
7760
+ import_types27.EdgeType.DEPENDS_ON,
7499
7761
  "cloudflare-service-binding",
7500
7762
  svc.service,
7501
7763
  evidenceFile,
@@ -7511,12 +7773,12 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7511
7773
  // src/extract/infra/vercel.ts
7512
7774
  init_cjs_shims();
7513
7775
  var import_node_fs20 = require("fs");
7514
- var import_node_path37 = __toESM(require("path"), 1);
7515
- var import_types27 = require("@neat.is/types");
7776
+ var import_node_path38 = __toESM(require("path"), 1);
7777
+ var import_types28 = require("@neat.is/types");
7516
7778
  var VERCEL_CONFIG_FILENAMES = ["vercel.json", "vercel.jsonc"];
7517
7779
  async function readVercelConfig(dir) {
7518
7780
  for (const filename of VERCEL_CONFIG_FILENAMES) {
7519
- const abs = import_node_path37.default.join(dir, filename);
7781
+ const abs = import_node_path38.default.join(dir, filename);
7520
7782
  if (!await exists(abs)) continue;
7521
7783
  const raw = await import_node_fs20.promises.readFile(abs, "utf8");
7522
7784
  const config = JSON.parse(maskCommentsInSource(raw));
@@ -7525,7 +7787,7 @@ async function readVercelConfig(dir) {
7525
7787
  return null;
7526
7788
  }
7527
7789
  async function readLinkedProjectName(dir) {
7528
- const abs = import_node_path37.default.join(dir, ".vercel", "project.json");
7790
+ const abs = import_node_path38.default.join(dir, ".vercel", "project.json");
7529
7791
  if (!await exists(abs)) return void 0;
7530
7792
  const parsed = JSON.parse(await import_node_fs20.promises.readFile(abs, "utf8"));
7531
7793
  return typeof parsed.projectName === "string" ? parsed.projectName : void 0;
@@ -7543,7 +7805,7 @@ async function addVercelServices(graph, services, scanPath) {
7543
7805
  read = await readVercelConfig(service.dir);
7544
7806
  projectName = await readLinkedProjectName(service.dir);
7545
7807
  } catch (err) {
7546
- recordExtractionError("infra vercel", import_node_path37.default.relative(scanPath, service.dir), err);
7808
+ recordExtractionError("infra vercel", import_node_path38.default.relative(scanPath, service.dir), err);
7547
7809
  continue;
7548
7810
  }
7549
7811
  if (!read && !projectName) continue;
@@ -7559,7 +7821,7 @@ async function addVercelServices(graph, services, scanPath) {
7559
7821
  const anchorId = service.node.id;
7560
7822
  if (!read) continue;
7561
7823
  const { config, relFile, raw } = read;
7562
- const evidenceFile = toPosix2(import_node_path37.default.relative(scanPath, import_node_path37.default.join(service.dir, relFile)));
7824
+ const evidenceFile = toPosix2(import_node_path38.default.relative(scanPath, import_node_path38.default.join(service.dir, relFile)));
7563
7825
  const add = (edgeType, kind, name) => {
7564
7826
  if (!name) return;
7565
7827
  const result = emitPlatformResourceEdge(
@@ -7575,12 +7837,12 @@ async function addVercelServices(graph, services, scanPath) {
7575
7837
  nodesAdded += result.nodesAdded;
7576
7838
  edgesAdded += result.edgesAdded;
7577
7839
  };
7578
- add(import_types27.EdgeType.RUNS_ON, "vercel", "vercel");
7579
- for (const cron of config.crons ?? []) add(import_types27.EdgeType.DEPENDS_ON, "vercel-cron", cron.path ?? cron.schedule);
7580
- for (const varName of Object.keys(config.env ?? {})) add(import_types27.EdgeType.DEPENDS_ON, "vercel-env-var", varName);
7581
- for (const varName of Object.keys(config.build?.env ?? {})) add(import_types27.EdgeType.DEPENDS_ON, "vercel-env-var", varName);
7840
+ add(import_types28.EdgeType.RUNS_ON, "vercel", "vercel");
7841
+ for (const cron of config.crons ?? []) add(import_types28.EdgeType.DEPENDS_ON, "vercel-cron", cron.path ?? cron.schedule);
7842
+ for (const varName of Object.keys(config.env ?? {})) add(import_types28.EdgeType.DEPENDS_ON, "vercel-env-var", varName);
7843
+ for (const varName of Object.keys(config.build?.env ?? {})) add(import_types28.EdgeType.DEPENDS_ON, "vercel-env-var", varName);
7582
7844
  for (const route of [...config.rewrites ?? [], ...config.redirects ?? [], ...config.routes ?? []]) {
7583
- add(import_types27.EdgeType.CONNECTS_TO, "vercel-route", routeSource(route));
7845
+ add(import_types28.EdgeType.CONNECTS_TO, "vercel-route", routeSource(route));
7584
7846
  }
7585
7847
  }
7586
7848
  return { nodesAdded, edgesAdded };
@@ -7589,13 +7851,13 @@ async function addVercelServices(graph, services, scanPath) {
7589
7851
  // src/extract/infra/railway.ts
7590
7852
  init_cjs_shims();
7591
7853
  var import_node_fs21 = require("fs");
7592
- var import_node_path38 = __toESM(require("path"), 1);
7854
+ var import_node_path39 = __toESM(require("path"), 1);
7593
7855
  var import_smol_toml3 = require("smol-toml");
7594
- var import_types28 = require("@neat.is/types");
7856
+ var import_types29 = require("@neat.is/types");
7595
7857
  var RAILWAY_FILENAMES = ["railway.toml", "railway.json", "railway.jsonc"];
7596
7858
  async function readRailwayConfig(dir) {
7597
7859
  for (const filename of RAILWAY_FILENAMES) {
7598
- const abs = import_node_path38.default.join(dir, filename);
7860
+ const abs = import_node_path39.default.join(dir, filename);
7599
7861
  if (!await exists(abs)) continue;
7600
7862
  const raw = await import_node_fs21.promises.readFile(abs, "utf8");
7601
7863
  const config = filename === "railway.toml" ? (0, import_smol_toml3.parse)(raw) : JSON.parse(maskCommentsInSource(raw));
@@ -7611,7 +7873,7 @@ async function addRailwayServices(graph, services, scanPath) {
7611
7873
  try {
7612
7874
  read = await readRailwayConfig(service.dir);
7613
7875
  } catch (err) {
7614
- recordExtractionError("infra railway", import_node_path38.default.relative(scanPath, service.dir), err);
7876
+ recordExtractionError("infra railway", import_node_path39.default.relative(scanPath, service.dir), err);
7615
7877
  continue;
7616
7878
  }
7617
7879
  if (!read) continue;
@@ -7621,7 +7883,7 @@ async function addRailwayServices(graph, services, scanPath) {
7621
7883
  }
7622
7884
  const anchorId = service.node.id;
7623
7885
  const { config, relFile, raw } = read;
7624
- const evidenceFile = toPosix2(import_node_path38.default.relative(scanPath, import_node_path38.default.join(service.dir, relFile)));
7886
+ const evidenceFile = toPosix2(import_node_path39.default.relative(scanPath, import_node_path39.default.join(service.dir, relFile)));
7625
7887
  const add = (edgeType, kind, name) => {
7626
7888
  if (!name) return;
7627
7889
  const result = emitPlatformResourceEdge(
@@ -7637,9 +7899,9 @@ async function addRailwayServices(graph, services, scanPath) {
7637
7899
  nodesAdded += result.nodesAdded;
7638
7900
  edgesAdded += result.edgesAdded;
7639
7901
  };
7640
- add(import_types28.EdgeType.RUNS_ON, "railway", "railway");
7641
- add(import_types28.EdgeType.CONNECTS_TO, "railway-route", config.deploy?.healthcheckPath);
7642
- add(import_types28.EdgeType.DEPENDS_ON, "railway-cron", config.deploy?.cronSchedule);
7902
+ add(import_types29.EdgeType.RUNS_ON, "railway", "railway");
7903
+ add(import_types29.EdgeType.CONNECTS_TO, "railway-route", config.deploy?.healthcheckPath);
7904
+ add(import_types29.EdgeType.DEPENDS_ON, "railway-cron", config.deploy?.cronSchedule);
7643
7905
  }
7644
7906
  return { nodesAdded, edgesAdded };
7645
7907
  }
@@ -7647,12 +7909,12 @@ async function addRailwayServices(graph, services, scanPath) {
7647
7909
  // src/extract/infra/supabase.ts
7648
7910
  init_cjs_shims();
7649
7911
  var import_node_fs22 = require("fs");
7650
- var import_node_path39 = __toESM(require("path"), 1);
7912
+ var import_node_path40 = __toESM(require("path"), 1);
7651
7913
  var import_smol_toml4 = require("smol-toml");
7652
- var import_types29 = require("@neat.is/types");
7914
+ var import_types30 = require("@neat.is/types");
7653
7915
  async function readSupabaseConfig(dir) {
7654
- const relFile = import_node_path39.default.join("supabase", "config.toml");
7655
- const abs = import_node_path39.default.join(dir, relFile);
7916
+ const relFile = import_node_path40.default.join("supabase", "config.toml");
7917
+ const abs = import_node_path40.default.join(dir, relFile);
7656
7918
  if (!await exists(abs)) return null;
7657
7919
  const raw = await import_node_fs22.promises.readFile(abs, "utf8");
7658
7920
  const config = (0, import_smol_toml4.parse)(raw);
@@ -7666,7 +7928,7 @@ async function addSupabaseProjects(graph, services, scanPath) {
7666
7928
  try {
7667
7929
  read = await readSupabaseConfig(service.dir);
7668
7930
  } catch (err) {
7669
- recordExtractionError("infra supabase", import_node_path39.default.relative(scanPath, service.dir), err);
7931
+ recordExtractionError("infra supabase", import_node_path40.default.relative(scanPath, service.dir), err);
7670
7932
  continue;
7671
7933
  }
7672
7934
  if (!read) continue;
@@ -7681,7 +7943,7 @@ async function addSupabaseProjects(graph, services, scanPath) {
7681
7943
  });
7682
7944
  }
7683
7945
  const anchorId = service.node.id;
7684
- const evidenceFile = toPosix2(import_node_path39.default.relative(scanPath, import_node_path39.default.join(service.dir, relFile)));
7946
+ const evidenceFile = toPosix2(import_node_path40.default.relative(scanPath, import_node_path40.default.join(service.dir, relFile)));
7685
7947
  const add = (edgeType, kind, name) => {
7686
7948
  if (!name) return;
7687
7949
  const result = emitPlatformResourceEdge(
@@ -7697,10 +7959,10 @@ async function addSupabaseProjects(graph, services, scanPath) {
7697
7959
  nodesAdded += result.nodesAdded;
7698
7960
  edgesAdded += result.edgesAdded;
7699
7961
  };
7700
- add(import_types29.EdgeType.RUNS_ON, "supabase", "supabase");
7701
- for (const fn of Object.keys(config.functions ?? {})) add(import_types29.EdgeType.DEPENDS_ON, "supabase-function", fn);
7702
- if (config.storage) add(import_types29.EdgeType.DEPENDS_ON, "supabase-storage", "storage");
7703
- if (config.auth) add(import_types29.EdgeType.DEPENDS_ON, "supabase-auth", "auth");
7962
+ add(import_types30.EdgeType.RUNS_ON, "supabase", "supabase");
7963
+ for (const fn of Object.keys(config.functions ?? {})) add(import_types30.EdgeType.DEPENDS_ON, "supabase-function", fn);
7964
+ if (config.storage) add(import_types30.EdgeType.DEPENDS_ON, "supabase-storage", "storage");
7965
+ if (config.auth) add(import_types30.EdgeType.DEPENDS_ON, "supabase-auth", "auth");
7704
7966
  }
7705
7967
  return { nodesAdded, edgesAdded };
7706
7968
  }
@@ -7722,17 +7984,17 @@ async function addInfra(graph, scanPath, services) {
7722
7984
  }
7723
7985
 
7724
7986
  // src/extract/index.ts
7725
- var import_node_path41 = __toESM(require("path"), 1);
7987
+ var import_node_path42 = __toESM(require("path"), 1);
7726
7988
 
7727
7989
  // src/extract/retire.ts
7728
7990
  init_cjs_shims();
7729
7991
  var import_node_fs23 = require("fs");
7730
- var import_node_path40 = __toESM(require("path"), 1);
7731
- var import_types30 = require("@neat.is/types");
7992
+ var import_node_path41 = __toESM(require("path"), 1);
7993
+ var import_types31 = require("@neat.is/types");
7732
7994
  function dropOrphanedFileNodes(graph) {
7733
7995
  const orphans = [];
7734
7996
  graph.forEachNode((id, attrs) => {
7735
- if (attrs.type !== import_types30.NodeType.FileNode) return;
7997
+ if (attrs.type !== import_types31.NodeType.FileNode) return;
7736
7998
  if (graph.inboundEdges(id).length === 0 && graph.outboundEdges(id).length === 0) {
7737
7999
  orphans.push(id);
7738
8000
  }
@@ -7745,14 +8007,14 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
7745
8007
  const bases = [scanPath, ...serviceDirs];
7746
8008
  graph.forEachEdge((id, attrs) => {
7747
8009
  const edge = attrs;
7748
- if (edge.provenance !== import_types30.Provenance.EXTRACTED) return;
8010
+ if (edge.provenance !== import_types31.Provenance.EXTRACTED) return;
7749
8011
  const evidenceFile = edge.evidence?.file;
7750
8012
  if (!evidenceFile) return;
7751
- if (import_node_path40.default.isAbsolute(evidenceFile)) {
8013
+ if (import_node_path41.default.isAbsolute(evidenceFile)) {
7752
8014
  if (!(0, import_node_fs23.existsSync)(evidenceFile)) toDrop.push(id);
7753
8015
  return;
7754
8016
  }
7755
- const found = bases.some((base) => (0, import_node_fs23.existsSync)(import_node_path40.default.join(base, evidenceFile)));
8017
+ const found = bases.some((base) => (0, import_node_fs23.existsSync)(import_node_path41.default.join(base, evidenceFile)));
7756
8018
  if (!found) toDrop.push(id);
7757
8019
  });
7758
8020
  for (const id of toDrop) graph.dropEdge(id);
@@ -7794,7 +8056,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
7794
8056
  }
7795
8057
  const droppedEntries = drainDroppedExtracted();
7796
8058
  if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
7797
- const rejectedPath = import_node_path41.default.join(import_node_path41.default.dirname(opts.errorsPath), "rejected.ndjson");
8059
+ const rejectedPath = import_node_path42.default.join(import_node_path42.default.dirname(opts.errorsPath), "rejected.ndjson");
7798
8060
  try {
7799
8061
  await writeRejectedExtracted(droppedEntries, rejectedPath);
7800
8062
  } catch (err) {
@@ -7829,8 +8091,8 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
7829
8091
  // src/persist.ts
7830
8092
  init_cjs_shims();
7831
8093
  var import_node_fs24 = require("fs");
7832
- var import_node_path42 = __toESM(require("path"), 1);
7833
- var import_types31 = require("@neat.is/types");
8094
+ var import_node_path43 = __toESM(require("path"), 1);
8095
+ var import_types32 = require("@neat.is/types");
7834
8096
  var SCHEMA_VERSION = 4;
7835
8097
  function migrateV1ToV2(payload) {
7836
8098
  const nodes = payload.graph.nodes;
@@ -7852,12 +8114,12 @@ function migrateV2ToV3(payload) {
7852
8114
  for (const edge of edges) {
7853
8115
  const attrs = edge.attributes;
7854
8116
  if (!attrs || attrs.provenance !== "FRONTIER") continue;
7855
- attrs.provenance = import_types31.Provenance.OBSERVED;
8117
+ attrs.provenance = import_types32.Provenance.OBSERVED;
7856
8118
  const type = typeof attrs.type === "string" ? attrs.type : void 0;
7857
8119
  const source = typeof attrs.source === "string" ? attrs.source : void 0;
7858
8120
  const target = typeof attrs.target === "string" ? attrs.target : void 0;
7859
8121
  if (type && source && target) {
7860
- const newId = (0, import_types31.observedEdgeId)(source, target, type);
8122
+ const newId = (0, import_types32.observedEdgeId)(source, target, type);
7861
8123
  attrs.id = newId;
7862
8124
  if (edge.key) edge.key = newId;
7863
8125
  }
@@ -7866,7 +8128,7 @@ function migrateV2ToV3(payload) {
7866
8128
  return { ...payload, schemaVersion: 3 };
7867
8129
  }
7868
8130
  async function ensureDir(filePath) {
7869
- await import_node_fs24.promises.mkdir(import_node_path42.default.dirname(filePath), { recursive: true });
8131
+ await import_node_fs24.promises.mkdir(import_node_path43.default.dirname(filePath), { recursive: true });
7870
8132
  }
7871
8133
  async function saveGraphToDisk(graph, outPath) {
7872
8134
  await ensureDir(outPath);
@@ -7947,23 +8209,23 @@ function startPersistLoop(graph, outPath, opts = {}) {
7947
8209
 
7948
8210
  // src/projects.ts
7949
8211
  init_cjs_shims();
7950
- var import_node_path43 = __toESM(require("path"), 1);
8212
+ var import_node_path44 = __toESM(require("path"), 1);
7951
8213
  function pathsForProject(project, baseDir) {
7952
8214
  if (project === DEFAULT_PROJECT) {
7953
8215
  return {
7954
- snapshotPath: import_node_path43.default.join(baseDir, "graph.json"),
7955
- errorsPath: import_node_path43.default.join(baseDir, "errors.ndjson"),
7956
- staleEventsPath: import_node_path43.default.join(baseDir, "stale-events.ndjson"),
7957
- embeddingsCachePath: import_node_path43.default.join(baseDir, "embeddings.json"),
7958
- policyViolationsPath: import_node_path43.default.join(baseDir, "policy-violations.ndjson")
8216
+ snapshotPath: import_node_path44.default.join(baseDir, "graph.json"),
8217
+ errorsPath: import_node_path44.default.join(baseDir, "errors.ndjson"),
8218
+ staleEventsPath: import_node_path44.default.join(baseDir, "stale-events.ndjson"),
8219
+ embeddingsCachePath: import_node_path44.default.join(baseDir, "embeddings.json"),
8220
+ policyViolationsPath: import_node_path44.default.join(baseDir, "policy-violations.ndjson")
7959
8221
  };
7960
8222
  }
7961
8223
  return {
7962
- snapshotPath: import_node_path43.default.join(baseDir, `${project}.json`),
7963
- errorsPath: import_node_path43.default.join(baseDir, `errors.${project}.ndjson`),
7964
- staleEventsPath: import_node_path43.default.join(baseDir, `stale-events.${project}.ndjson`),
7965
- embeddingsCachePath: import_node_path43.default.join(baseDir, `embeddings.${project}.json`),
7966
- policyViolationsPath: import_node_path43.default.join(baseDir, `policy-violations.${project}.ndjson`)
8224
+ snapshotPath: import_node_path44.default.join(baseDir, `${project}.json`),
8225
+ errorsPath: import_node_path44.default.join(baseDir, `errors.${project}.ndjson`),
8226
+ staleEventsPath: import_node_path44.default.join(baseDir, `stale-events.${project}.ndjson`),
8227
+ embeddingsCachePath: import_node_path44.default.join(baseDir, `embeddings.${project}.json`),
8228
+ policyViolationsPath: import_node_path44.default.join(baseDir, `policy-violations.${project}.ndjson`)
7967
8229
  };
7968
8230
  }
7969
8231
  var Projects = class {
@@ -8001,19 +8263,19 @@ var Projects = class {
8001
8263
  init_cjs_shims();
8002
8264
  var import_fastify = __toESM(require("fastify"), 1);
8003
8265
  var import_cors = __toESM(require("@fastify/cors"), 1);
8004
- var import_types34 = require("@neat.is/types");
8266
+ var import_types35 = require("@neat.is/types");
8005
8267
 
8006
8268
  // src/extend/index.ts
8007
8269
  init_cjs_shims();
8008
8270
  var import_node_fs26 = require("fs");
8009
- var import_node_path45 = __toESM(require("path"), 1);
8271
+ var import_node_path46 = __toESM(require("path"), 1);
8010
8272
  var import_node_os2 = __toESM(require("os"), 1);
8011
8273
  var import_instrumentation_registry = require("@neat.is/instrumentation-registry");
8012
8274
 
8013
8275
  // src/installers/package-manager.ts
8014
8276
  init_cjs_shims();
8015
8277
  var import_node_fs25 = require("fs");
8016
- var import_node_path44 = __toESM(require("path"), 1);
8278
+ var import_node_path45 = __toESM(require("path"), 1);
8017
8279
  var import_node_child_process = require("child_process");
8018
8280
  var LOCKFILE_PRIORITY = [
8019
8281
  { lockfile: "bun.lockb", pm: "bun", args: ["install", "--no-summary"] },
@@ -8035,22 +8297,22 @@ async function exists2(p) {
8035
8297
  }
8036
8298
  }
8037
8299
  async function detectPackageManager(serviceDir) {
8038
- let dir = import_node_path44.default.resolve(serviceDir);
8300
+ let dir = import_node_path45.default.resolve(serviceDir);
8039
8301
  const stops = /* @__PURE__ */ new Set();
8040
8302
  for (let i = 0; i < 64; i++) {
8041
8303
  if (stops.has(dir)) break;
8042
8304
  stops.add(dir);
8043
8305
  for (const candidate of LOCKFILE_PRIORITY) {
8044
- const lockPath = import_node_path44.default.join(dir, candidate.lockfile);
8306
+ const lockPath = import_node_path45.default.join(dir, candidate.lockfile);
8045
8307
  if (await exists2(lockPath)) {
8046
8308
  return { pm: candidate.pm, cwd: dir, args: [...candidate.args] };
8047
8309
  }
8048
8310
  }
8049
- const parent = import_node_path44.default.dirname(dir);
8311
+ const parent = import_node_path45.default.dirname(dir);
8050
8312
  if (parent === dir) break;
8051
8313
  dir = parent;
8052
8314
  }
8053
- return { pm: "npm", cwd: import_node_path44.default.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
8315
+ return { pm: "npm", cwd: import_node_path45.default.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
8054
8316
  }
8055
8317
  async function runPackageManagerInstall(cmd) {
8056
8318
  return new Promise((resolve) => {
@@ -8099,7 +8361,7 @@ async function fileExists2(p) {
8099
8361
  }
8100
8362
  }
8101
8363
  async function readPackageJson(scanPath) {
8102
- const pkgPath = import_node_path45.default.join(scanPath, "package.json");
8364
+ const pkgPath = import_node_path46.default.join(scanPath, "package.json");
8103
8365
  const raw = await import_node_fs26.promises.readFile(pkgPath, "utf8");
8104
8366
  return JSON.parse(raw);
8105
8367
  }
@@ -8113,27 +8375,27 @@ var HOOK_WALK_SKIP_DIRS = /* @__PURE__ */ new Set([
8113
8375
  ]);
8114
8376
  async function findHookFiles(scanPath) {
8115
8377
  const found = [];
8116
- const walk3 = async (dir) => {
8378
+ const walk4 = async (dir) => {
8117
8379
  const entries = await import_node_fs26.promises.readdir(dir, { withFileTypes: true }).catch(() => []);
8118
8380
  for (const entry2 of entries) {
8119
8381
  if (entry2.isDirectory()) {
8120
8382
  if (entry2.name.startsWith(".") || HOOK_WALK_SKIP_DIRS.has(entry2.name)) continue;
8121
- await walk3(import_node_path45.default.join(dir, entry2.name));
8383
+ await walk4(import_node_path46.default.join(dir, entry2.name));
8122
8384
  } else if (entry2.isFile()) {
8123
8385
  if ((entry2.name.startsWith("instrumentation") || entry2.name.startsWith("otel-init")) && /\.(ts|js|cjs|mjs)$/.test(entry2.name)) {
8124
- const rel = import_node_path45.default.relative(scanPath, import_node_path45.default.join(dir, entry2.name));
8125
- found.push(rel.split(import_node_path45.default.sep).join("/"));
8386
+ const rel = import_node_path46.default.relative(scanPath, import_node_path46.default.join(dir, entry2.name));
8387
+ found.push(rel.split(import_node_path46.default.sep).join("/"));
8126
8388
  }
8127
8389
  }
8128
8390
  }
8129
8391
  };
8130
- await walk3(scanPath);
8392
+ await walk4(scanPath);
8131
8393
  return found.sort();
8132
8394
  }
8133
8395
  async function pickPrimaryHookFile(scanPath, hookFiles, snippet2) {
8134
8396
  let fallback = null;
8135
8397
  for (const file of hookFiles) {
8136
- const content = await import_node_fs26.promises.readFile(import_node_path45.default.join(scanPath, file), "utf8");
8398
+ const content = await import_node_fs26.promises.readFile(import_node_path46.default.join(scanPath, file), "utf8");
8137
8399
  const patched = splicedContent(content, snippet2);
8138
8400
  if (patched !== null) return { file, content, patched };
8139
8401
  if (fallback === null) fallback = { file, content };
@@ -8141,11 +8403,11 @@ async function pickPrimaryHookFile(scanPath, hookFiles, snippet2) {
8141
8403
  return { file: fallback.file, content: fallback.content, patched: null };
8142
8404
  }
8143
8405
  function extendLogPath() {
8144
- return process.env.NEAT_EXTEND_LOG ?? import_node_path45.default.join(import_node_os2.default.homedir(), ".neat", "extend-log.ndjson");
8406
+ return process.env.NEAT_EXTEND_LOG ?? import_node_path46.default.join(import_node_os2.default.homedir(), ".neat", "extend-log.ndjson");
8145
8407
  }
8146
8408
  async function appendExtendLog(entry2) {
8147
8409
  const logPath = extendLogPath();
8148
- await import_node_fs26.promises.mkdir(import_node_path45.default.dirname(logPath), { recursive: true });
8410
+ await import_node_fs26.promises.mkdir(import_node_path46.default.dirname(logPath), { recursive: true });
8149
8411
  await import_node_fs26.promises.appendFile(logPath, JSON.stringify(entry2) + "\n", "utf8");
8150
8412
  }
8151
8413
  function splicedContent(fileContent, snippet2) {
@@ -8204,7 +8466,7 @@ function lookupInstrumentation(library, installedVersion) {
8204
8466
  }
8205
8467
  async function describeProjectInstrumentation(ctx) {
8206
8468
  const hookFiles = await findHookFiles(ctx.scanPath);
8207
- const envNeat = await fileExists2(import_node_path45.default.join(ctx.scanPath, ".env.neat"));
8469
+ const envNeat = await fileExists2(import_node_path46.default.join(ctx.scanPath, ".env.neat"));
8208
8470
  const registryInstrPackages = new Set(
8209
8471
  (0, import_instrumentation_registry.list)().map((e) => e.instrumentation_package).filter((p) => !!p)
8210
8472
  );
@@ -8226,7 +8488,7 @@ async function applyExtension(ctx, args, options) {
8226
8488
  );
8227
8489
  }
8228
8490
  for (const file of hookFiles) {
8229
- const content = await import_node_fs26.promises.readFile(import_node_path45.default.join(ctx.scanPath, file), "utf8");
8491
+ const content = await import_node_fs26.promises.readFile(import_node_path46.default.join(ctx.scanPath, file), "utf8");
8230
8492
  if (content.includes(args.registration_snippet)) {
8231
8493
  return { library: args.library, filesTouched: [], depsAdded: [], installOutput: "", alreadyApplied: true };
8232
8494
  }
@@ -8238,10 +8500,10 @@ async function applyExtension(ctx, args, options) {
8238
8500
  );
8239
8501
  }
8240
8502
  const primaryFile = primary.file;
8241
- const primaryPath = import_node_path45.default.join(ctx.scanPath, primaryFile);
8503
+ const primaryPath = import_node_path46.default.join(ctx.scanPath, primaryFile);
8242
8504
  const filesTouched = [];
8243
8505
  const depsAdded = [];
8244
- const pkgPath = import_node_path45.default.join(ctx.scanPath, "package.json");
8506
+ const pkgPath = import_node_path46.default.join(ctx.scanPath, "package.json");
8245
8507
  const pkg = await readPackageJson(ctx.scanPath);
8246
8508
  if (!(pkg.dependencies ?? {})[args.instrumentation_package]) {
8247
8509
  pkg.dependencies = { ...pkg.dependencies ?? {}, [args.instrumentation_package]: args.version };
@@ -8280,7 +8542,7 @@ async function dryRunExtension(ctx, args) {
8280
8542
  };
8281
8543
  }
8282
8544
  for (const file of hookFiles) {
8283
- const content = await import_node_fs26.promises.readFile(import_node_path45.default.join(ctx.scanPath, file), "utf8");
8545
+ const content = await import_node_fs26.promises.readFile(import_node_path46.default.join(ctx.scanPath, file), "utf8");
8284
8546
  if (content.includes(args.registration_snippet)) {
8285
8547
  return {
8286
8548
  library: args.library,
@@ -8321,7 +8583,7 @@ async function rollbackExtension(ctx, args) {
8321
8583
  if (!match) {
8322
8584
  return { undone: false, message: "no apply found for library" };
8323
8585
  }
8324
- const pkgPath = import_node_path45.default.join(ctx.scanPath, "package.json");
8586
+ const pkgPath = import_node_path46.default.join(ctx.scanPath, "package.json");
8325
8587
  if (await fileExists2(pkgPath)) {
8326
8588
  const pkg = await readPackageJson(ctx.scanPath);
8327
8589
  if (pkg.dependencies?.[match.instrumentation_package]) {
@@ -8332,7 +8594,7 @@ async function rollbackExtension(ctx, args) {
8332
8594
  }
8333
8595
  const hookFiles = await findHookFiles(ctx.scanPath);
8334
8596
  for (const file of hookFiles) {
8335
- const filePath = import_node_path45.default.join(ctx.scanPath, file);
8597
+ const filePath = import_node_path46.default.join(ctx.scanPath, file);
8336
8598
  const content = await import_node_fs26.promises.readFile(filePath, "utf8");
8337
8599
  if (content.includes(match.registration_snippet)) {
8338
8600
  const filtered = content.split("\n").filter((line) => !line.includes(match.registration_snippet)).join("\n");
@@ -8348,39 +8610,39 @@ async function rollbackExtension(ctx, args) {
8348
8610
 
8349
8611
  // src/divergences.ts
8350
8612
  init_cjs_shims();
8351
- var import_types32 = require("@neat.is/types");
8613
+ var import_types33 = require("@neat.is/types");
8352
8614
  function bucketKey(source, target, type) {
8353
8615
  return `${type}|${source}|${target}`;
8354
8616
  }
8355
8617
  function bucketSourceFor(graph, edge) {
8356
- if (edge.type !== import_types32.EdgeType.CONNECTS_TO) return edge.source;
8357
- const parsed = (0, import_types32.parseFileId)(edge.source);
8618
+ if (edge.type !== import_types33.EdgeType.CONNECTS_TO) return edge.source;
8619
+ const parsed = (0, import_types33.parseFileId)(edge.source);
8358
8620
  if (!parsed || !graph.hasNode(edge.target)) return edge.source;
8359
8621
  const target = graph.getNodeAttributes(edge.target);
8360
- if (target.type !== import_types32.NodeType.DatabaseNode) return edge.source;
8361
- return (0, import_types32.serviceId)(parsed.service);
8622
+ if (target.type !== import_types33.NodeType.DatabaseNode) return edge.source;
8623
+ return (0, import_types33.serviceId)(parsed.service);
8362
8624
  }
8363
8625
  function bucketEdges(graph) {
8364
8626
  const buckets2 = /* @__PURE__ */ new Map();
8365
8627
  graph.forEachEdge((id, attrs) => {
8366
8628
  const e = attrs;
8367
- const parsed = (0, import_types32.parseEdgeId)(id);
8629
+ const parsed = (0, import_types33.parseEdgeId)(id);
8368
8630
  const provenance = parsed?.provenance ?? e.provenance;
8369
8631
  const source = bucketSourceFor(graph, e);
8370
8632
  const key = bucketKey(source, e.target, e.type);
8371
8633
  const cur = buckets2.get(key) ?? { source, target: e.target, type: e.type };
8372
8634
  switch (provenance) {
8373
- case import_types32.Provenance.EXTRACTED:
8635
+ case import_types33.Provenance.EXTRACTED:
8374
8636
  cur.extracted = e;
8375
8637
  break;
8376
- case import_types32.Provenance.OBSERVED:
8638
+ case import_types33.Provenance.OBSERVED:
8377
8639
  cur.observed = e;
8378
8640
  break;
8379
- case import_types32.Provenance.INFERRED:
8641
+ case import_types33.Provenance.INFERRED:
8380
8642
  cur.inferred = e;
8381
8643
  break;
8382
8644
  default:
8383
- if (e.provenance === import_types32.Provenance.STALE) cur.stale = e;
8645
+ if (e.provenance === import_types33.Provenance.STALE) cur.stale = e;
8384
8646
  }
8385
8647
  buckets2.set(key, cur);
8386
8648
  });
@@ -8389,12 +8651,12 @@ function bucketEdges(graph) {
8389
8651
  function nodeIsFrontier(graph, nodeId) {
8390
8652
  if (!graph.hasNode(nodeId)) return false;
8391
8653
  const attrs = graph.getNodeAttributes(nodeId);
8392
- return attrs.type === import_types32.NodeType.FrontierNode;
8654
+ return attrs.type === import_types33.NodeType.FrontierNode;
8393
8655
  }
8394
8656
  function nodeIsWebsocketChannel(graph, nodeId) {
8395
8657
  if (!graph.hasNode(nodeId)) return false;
8396
8658
  const attrs = graph.getNodeAttributes(nodeId);
8397
- return attrs.type === import_types32.NodeType.WebSocketChannelNode;
8659
+ return attrs.type === import_types33.NodeType.WebSocketChannelNode;
8398
8660
  }
8399
8661
  function clampConfidence(n) {
8400
8662
  if (!Number.isFinite(n)) return 0;
@@ -8414,14 +8676,14 @@ function gradedConfidence(edge) {
8414
8676
  return clampConfidence(confidenceForEdge(edge));
8415
8677
  }
8416
8678
  var OBSERVABLE_EDGE_TYPES = /* @__PURE__ */ new Set([
8417
- import_types32.EdgeType.CALLS,
8418
- import_types32.EdgeType.CONNECTS_TO,
8419
- import_types32.EdgeType.PUBLISHES_TO,
8420
- import_types32.EdgeType.CONSUMES_FROM
8679
+ import_types33.EdgeType.CALLS,
8680
+ import_types33.EdgeType.CONNECTS_TO,
8681
+ import_types33.EdgeType.PUBLISHES_TO,
8682
+ import_types33.EdgeType.CONSUMES_FROM
8421
8683
  ]);
8422
8684
  function detectMissingDivergences(graph, bucket) {
8423
8685
  const out = [];
8424
- if (bucket.type === import_types32.EdgeType.CONTAINS) return out;
8686
+ if (bucket.type === import_types33.EdgeType.CONTAINS) return out;
8425
8687
  if (bucket.extracted && !bucket.observed && OBSERVABLE_EDGE_TYPES.has(bucket.type)) {
8426
8688
  if (!nodeIsFrontier(graph, bucket.target)) {
8427
8689
  out.push({
@@ -8462,7 +8724,7 @@ function declaredHostFor(svc) {
8462
8724
  function hasExtractedConfiguredBy(graph, svcId) {
8463
8725
  for (const edgeId of graph.outboundEdges(svcId)) {
8464
8726
  const e = graph.getEdgeAttributes(edgeId);
8465
- if (e.type === import_types32.EdgeType.CONFIGURED_BY && e.provenance === import_types32.Provenance.EXTRACTED) {
8727
+ if (e.type === import_types33.EdgeType.CONFIGURED_BY && e.provenance === import_types33.Provenance.EXTRACTED) {
8466
8728
  return true;
8467
8729
  }
8468
8730
  }
@@ -8475,10 +8737,10 @@ function detectHostMismatch(graph, svcId, svc) {
8475
8737
  const out = [];
8476
8738
  for (const edgeId of graph.outboundEdges(svcId)) {
8477
8739
  const edge = graph.getEdgeAttributes(edgeId);
8478
- if (edge.type !== import_types32.EdgeType.CONNECTS_TO) continue;
8479
- if (edge.provenance !== import_types32.Provenance.OBSERVED) continue;
8740
+ if (edge.type !== import_types33.EdgeType.CONNECTS_TO) continue;
8741
+ if (edge.provenance !== import_types33.Provenance.OBSERVED) continue;
8480
8742
  const target = graph.getNodeAttributes(edge.target);
8481
- if (target.type !== import_types32.NodeType.DatabaseNode) continue;
8743
+ if (target.type !== import_types33.NodeType.DatabaseNode) continue;
8482
8744
  const observedHost = target.host?.trim();
8483
8745
  if (!observedHost) continue;
8484
8746
  if (observedHost === declaredHost) continue;
@@ -8500,10 +8762,10 @@ function detectCompatDivergences(graph, svcId, svc) {
8500
8762
  const deps = svc.dependencies ?? {};
8501
8763
  for (const edgeId of graph.outboundEdges(svcId)) {
8502
8764
  const edge = graph.getEdgeAttributes(edgeId);
8503
- if (edge.type !== import_types32.EdgeType.CONNECTS_TO) continue;
8504
- if (edge.provenance !== import_types32.Provenance.OBSERVED) continue;
8765
+ if (edge.type !== import_types33.EdgeType.CONNECTS_TO) continue;
8766
+ if (edge.provenance !== import_types33.Provenance.OBSERVED) continue;
8505
8767
  const target = graph.getNodeAttributes(edge.target);
8506
- if (target.type !== import_types32.NodeType.DatabaseNode) continue;
8768
+ if (target.type !== import_types33.NodeType.DatabaseNode) continue;
8507
8769
  for (const pair of compatPairs()) {
8508
8770
  if (pair.engine !== target.engine) continue;
8509
8771
  const declared = deps[pair.driver];
@@ -8562,7 +8824,7 @@ function suppressHostMismatchHalves(all) {
8562
8824
  for (const d of all) {
8563
8825
  if (d.type !== "host-mismatch") continue;
8564
8826
  observedHalf.add(`${d.source}->${d.target}`);
8565
- declaredHalf.add((0, import_types32.databaseId)(d.extractedHost));
8827
+ declaredHalf.add((0, import_types33.databaseId)(d.extractedHost));
8566
8828
  }
8567
8829
  if (observedHalf.size === 0) return all;
8568
8830
  return all.filter((d) => {
@@ -8581,7 +8843,7 @@ function computeDivergences(graph, opts = {}) {
8581
8843
  }
8582
8844
  graph.forEachNode((nodeId, attrs) => {
8583
8845
  const n = attrs;
8584
- if (n.type !== import_types32.NodeType.ServiceNode) return;
8846
+ if (n.type !== import_types33.NodeType.ServiceNode) return;
8585
8847
  const svc = n;
8586
8848
  for (const d of detectHostMismatch(graph, nodeId, svc)) all.push(d);
8587
8849
  for (const d of detectCompatDivergences(graph, nodeId, svc)) all.push(d);
@@ -8615,7 +8877,7 @@ function computeDivergences(graph, opts = {}) {
8615
8877
  if (a.source !== b.source) return a.source.localeCompare(b.source);
8616
8878
  return a.target.localeCompare(b.target);
8617
8879
  });
8618
- return import_types32.DivergenceResultSchema.parse({
8880
+ return import_types33.DivergenceResultSchema.parse({
8619
8881
  divergences: filtered,
8620
8882
  totalAffected: filtered.length,
8621
8883
  computedAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -8748,23 +9010,23 @@ function canonicalJson(value) {
8748
9010
  init_cjs_shims();
8749
9011
  var import_node_fs28 = require("fs");
8750
9012
  var import_node_os3 = __toESM(require("os"), 1);
8751
- var import_node_path46 = __toESM(require("path"), 1);
8752
- var import_types33 = require("@neat.is/types");
9013
+ var import_node_path47 = __toESM(require("path"), 1);
9014
+ var import_types34 = require("@neat.is/types");
8753
9015
  var LOCK_TIMEOUT_MS = 5e3;
8754
9016
  var LOCK_RETRY_MS = 50;
8755
9017
  function neatHome() {
8756
9018
  const override = process.env.NEAT_HOME;
8757
- if (override && override.length > 0) return import_node_path46.default.resolve(override);
8758
- return import_node_path46.default.join(import_node_os3.default.homedir(), ".neat");
9019
+ if (override && override.length > 0) return import_node_path47.default.resolve(override);
9020
+ return import_node_path47.default.join(import_node_os3.default.homedir(), ".neat");
8759
9021
  }
8760
9022
  function registryPath() {
8761
- return import_node_path46.default.join(neatHome(), "projects.json");
9023
+ return import_node_path47.default.join(neatHome(), "projects.json");
8762
9024
  }
8763
9025
  function registryLockPath() {
8764
- return import_node_path46.default.join(neatHome(), "projects.json.lock");
9026
+ return import_node_path47.default.join(neatHome(), "projects.json.lock");
8765
9027
  }
8766
9028
  function daemonPidPath() {
8767
- return import_node_path46.default.join(neatHome(), "neatd.pid");
9029
+ return import_node_path47.default.join(neatHome(), "neatd.pid");
8768
9030
  }
8769
9031
  function isPidAliveDefault(pid) {
8770
9032
  try {
@@ -8824,7 +9086,7 @@ function lockHolderMessage(holder, lockPath, timeoutMs) {
8824
9086
  }
8825
9087
  }
8826
9088
  async function writeAtomically(target, contents) {
8827
- await import_node_fs28.promises.mkdir(import_node_path46.default.dirname(target), { recursive: true });
9089
+ await import_node_fs28.promises.mkdir(import_node_path47.default.dirname(target), { recursive: true });
8828
9090
  const tmp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
8829
9091
  const fd = await import_node_fs28.promises.open(tmp, "w");
8830
9092
  try {
@@ -8837,7 +9099,7 @@ async function writeAtomically(target, contents) {
8837
9099
  }
8838
9100
  async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaultLockHolderProbe) {
8839
9101
  const deadline = Date.now() + timeoutMs;
8840
- await import_node_fs28.promises.mkdir(import_node_path46.default.dirname(lockPath), { recursive: true });
9102
+ await import_node_fs28.promises.mkdir(import_node_path47.default.dirname(lockPath), { recursive: true });
8841
9103
  let probedHolder = false;
8842
9104
  while (true) {
8843
9105
  try {
@@ -8890,10 +9152,10 @@ async function readRegistry() {
8890
9152
  throw err;
8891
9153
  }
8892
9154
  const parsed = JSON.parse(raw);
8893
- return import_types33.RegistryFileSchema.parse(parsed);
9155
+ return import_types34.RegistryFileSchema.parse(parsed);
8894
9156
  }
8895
9157
  async function writeRegistry(reg) {
8896
- const validated = import_types33.RegistryFileSchema.parse(reg);
9158
+ const validated = import_types34.RegistryFileSchema.parse(reg);
8897
9159
  await writeAtomically(registryPath(), JSON.stringify(validated, null, 2) + "\n");
8898
9160
  }
8899
9161
  async function getProject(name) {
@@ -9038,7 +9300,7 @@ init_auth();
9038
9300
  // src/connectors-config.ts
9039
9301
  init_cjs_shims();
9040
9302
  var import_node_os4 = __toESM(require("os"), 1);
9041
- var import_node_path47 = __toESM(require("path"), 1);
9303
+ var import_node_path48 = __toESM(require("path"), 1);
9042
9304
  var import_node_fs29 = require("fs");
9043
9305
  var CONNECTORS_CONFIG_VERSION = 1;
9044
9306
  var EnvRefUnsetError = class extends Error {
@@ -9053,11 +9315,11 @@ var EnvRefUnsetError = class extends Error {
9053
9315
  };
9054
9316
  function neatHome2() {
9055
9317
  const override = process.env.NEAT_HOME;
9056
- if (override && override.length > 0) return import_node_path47.default.resolve(override);
9057
- return import_node_path47.default.join(import_node_os4.default.homedir(), ".neat");
9318
+ if (override && override.length > 0) return import_node_path48.default.resolve(override);
9319
+ return import_node_path48.default.join(import_node_os4.default.homedir(), ".neat");
9058
9320
  }
9059
9321
  function connectorsConfigPath(home = neatHome2()) {
9060
- return import_node_path47.default.join(home, "connectors.json");
9322
+ return import_node_path48.default.join(home, "connectors.json");
9061
9323
  }
9062
9324
  var MODE_MASK_LOOSER_THAN_0600 = 63;
9063
9325
  async function warnIfModeLooserThan0600(file) {
@@ -9393,11 +9655,11 @@ function registerRoutes(scope, ctx) {
9393
9655
  const candidates = req2.query.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
9394
9656
  const parsed = [];
9395
9657
  for (const c of candidates) {
9396
- const r = import_types34.DivergenceTypeSchema.safeParse(c);
9658
+ const r = import_types35.DivergenceTypeSchema.safeParse(c);
9397
9659
  if (!r.success) {
9398
9660
  return reply.code(400).send({
9399
9661
  error: `unknown divergence type "${c}"`,
9400
- allowed: import_types34.DivergenceTypeSchema.options
9662
+ allowed: import_types35.DivergenceTypeSchema.options
9401
9663
  });
9402
9664
  }
9403
9665
  parsed.push(r.data);
@@ -9668,7 +9930,7 @@ function registerRoutes(scope, ctx) {
9668
9930
  const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
9669
9931
  let violations = await log.readAll();
9670
9932
  if (req2.query.severity) {
9671
- const sev = import_types34.PolicySeveritySchema.safeParse(req2.query.severity);
9933
+ const sev = import_types35.PolicySeveritySchema.safeParse(req2.query.severity);
9672
9934
  if (!sev.success) {
9673
9935
  return reply.code(400).send({
9674
9936
  error: "invalid severity",
@@ -9707,7 +9969,7 @@ function registerRoutes(scope, ctx) {
9707
9969
  scope.post("/policies/check", async (req2, reply) => {
9708
9970
  const proj = resolveProject(registry, req2, reply, ctx.bootstrap, ctx.singleProject);
9709
9971
  if (!proj) return;
9710
- const parsed = import_types34.PoliciesCheckBodySchema.safeParse(req2.body ?? {});
9972
+ const parsed = import_types35.PoliciesCheckBodySchema.safeParse(req2.body ?? {});
9711
9973
  if (!parsed.success) {
9712
9974
  return reply.code(400).send({
9713
9975
  error: "invalid /policies/check body",
@@ -10007,15 +10269,15 @@ init_otel();
10007
10269
 
10008
10270
  // src/connectors/index.ts
10009
10271
  init_cjs_shims();
10010
- var import_types35 = require("@neat.is/types");
10272
+ var import_types36 = require("@neat.is/types");
10011
10273
  var NO_ENV = "unknown";
10012
10274
  function staticCallSiteFor(graph, serviceName, targetNodeId) {
10013
10275
  if (!graph.hasNode(targetNodeId)) return void 0;
10014
10276
  const sites = [];
10015
10277
  for (const edgeId of graph.inboundEdges(targetNodeId)) {
10016
10278
  const edge = graph.getEdgeAttributes(edgeId);
10017
- if (edge.provenance !== import_types35.Provenance.EXTRACTED) continue;
10018
- const parsed = (0, import_types35.parseFileId)(edge.source);
10279
+ if (edge.provenance !== import_types36.Provenance.EXTRACTED) continue;
10280
+ const parsed = (0, import_types36.parseFileId)(edge.source);
10019
10281
  if (!parsed || parsed.service !== serviceName || !edge.evidence) continue;
10020
10282
  const site = { relPath: edge.evidence.file };
10021
10283
  if (edge.evidence.line !== void 0) site.line = edge.evidence.line;
@@ -10026,7 +10288,7 @@ function staticCallSiteFor(graph, serviceName, targetNodeId) {
10026
10288
  function routeCallSiteFor(graph, targetNodeId) {
10027
10289
  if (!graph.hasNode(targetNodeId)) return void 0;
10028
10290
  const attrs = graph.getNodeAttributes(targetNodeId);
10029
- if (attrs.type !== import_types35.NodeType.RouteNode || !attrs.path) return void 0;
10291
+ if (attrs.type !== import_types36.NodeType.RouteNode || !attrs.path) return void 0;
10030
10292
  const site = { relPath: attrs.path };
10031
10293
  if (attrs.line !== void 0) site.line = attrs.line;
10032
10294
  return site;
@@ -10497,10 +10759,10 @@ var SUPABASE_RPC_TARGET_KIND = "supabase-rpc";
10497
10759
  // src/connectors/supabase/map.ts
10498
10760
  var REST_RPC_PATH_RE = /^\/rest\/v1\/rpc\/([^/?]+)/;
10499
10761
  var REST_TABLE_PATH_RE = /^\/rest\/v1\/([^/?]+)/;
10500
- function targetFromRestPath(path54) {
10501
- const rpcMatch = REST_RPC_PATH_RE.exec(path54);
10762
+ function targetFromRestPath(path55) {
10763
+ const rpcMatch = REST_RPC_PATH_RE.exec(path55);
10502
10764
  if (rpcMatch) return { targetKind: SUPABASE_RPC_TARGET_KIND, name: rpcMatch[1] };
10503
- const tableMatch = REST_TABLE_PATH_RE.exec(path54);
10765
+ const tableMatch = REST_TABLE_PATH_RE.exec(path55);
10504
10766
  if (tableMatch) return { targetKind: SUPABASE_TABLE_TARGET_KIND, name: tableMatch[1] };
10505
10767
  return null;
10506
10768
  }
@@ -10609,23 +10871,23 @@ async function fetchPgStatStatements(connectionString, limit = DEFAULT_STATEMENT
10609
10871
 
10610
10872
  // src/connectors/supabase/resolve.ts
10611
10873
  init_cjs_shims();
10612
- var import_types37 = require("@neat.is/types");
10874
+ var import_types38 = require("@neat.is/types");
10613
10875
  function createSupabaseResolveTarget(graph, config) {
10614
10876
  return (signal, _ctx) => {
10615
10877
  if (signal.targetKind !== SUPABASE_TABLE_TARGET_KIND && signal.targetKind !== SUPABASE_RPC_TARGET_KIND) {
10616
10878
  return null;
10617
10879
  }
10618
- const subResourceId = (0, import_types37.infraId)(signal.targetKind, `${config.nodeRef}/${signal.targetName}`);
10880
+ const subResourceId = (0, import_types38.infraId)(signal.targetKind, `${config.nodeRef}/${signal.targetName}`);
10619
10881
  if (graph.hasNode(subResourceId)) {
10620
- return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType: import_types37.EdgeType.CALLS };
10882
+ return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType: import_types38.EdgeType.CALLS };
10621
10883
  }
10622
- const bareResourceId = (0, import_types37.infraId)(signal.targetKind, signal.targetName);
10884
+ const bareResourceId = (0, import_types38.infraId)(signal.targetKind, signal.targetName);
10623
10885
  if (graph.hasNode(bareResourceId)) {
10624
- return { targetNodeId: bareResourceId, serviceName: config.serviceName, edgeType: import_types37.EdgeType.CALLS };
10886
+ return { targetNodeId: bareResourceId, serviceName: config.serviceName, edgeType: import_types38.EdgeType.CALLS };
10625
10887
  }
10626
- const projectLevelId = (0, import_types37.infraId)("supabase", config.nodeRef);
10888
+ const projectLevelId = (0, import_types38.infraId)("supabase", config.nodeRef);
10627
10889
  if (graph.hasNode(projectLevelId)) {
10628
- return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType: import_types37.EdgeType.CALLS };
10890
+ return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType: import_types38.EdgeType.CALLS };
10629
10891
  }
10630
10892
  return null;
10631
10893
  };
@@ -10718,7 +10980,7 @@ function createSupabaseConnector(graph, config, deps = {}) {
10718
10980
 
10719
10981
  // src/connectors/railway/index.ts
10720
10982
  init_cjs_shims();
10721
- var import_types41 = require("@neat.is/types");
10983
+ var import_types42 = require("@neat.is/types");
10722
10984
 
10723
10985
  // src/connectors/railway/client.ts
10724
10986
  init_cjs_shims();
@@ -10844,7 +11106,7 @@ function buildRailwayRouteIndex(graph, serviceName) {
10844
11106
  const out = [];
10845
11107
  graph.forEachNode((_id, attrs) => {
10846
11108
  const node = attrs;
10847
- if (node.type !== import_types41.NodeType.RouteNode) return;
11109
+ if (node.type !== import_types42.NodeType.RouteNode) return;
10848
11110
  const route = attrs;
10849
11111
  if (route.service !== serviceName) return;
10850
11112
  out.push({
@@ -10948,12 +11210,12 @@ function createRailwayResolveTarget(config) {
10948
11210
  const serviceName = config.serviceNameById[config.serviceId];
10949
11211
  if (!serviceName) return null;
10950
11212
  if (signal.targetKind === ROUTE_TARGET_KIND) {
10951
- return { targetNodeId: signal.targetName, serviceName, edgeType: import_types41.EdgeType.CALLS };
11213
+ return { targetNodeId: signal.targetName, serviceName, edgeType: import_types42.EdgeType.CALLS };
10952
11214
  }
10953
11215
  if (signal.targetKind === PEER_SERVICE_TARGET_KIND) {
10954
11216
  const peerName = config.serviceNameById[signal.targetName];
10955
11217
  if (!peerName) return null;
10956
- return { targetNodeId: (0, import_types41.serviceId)(peerName), serviceName, edgeType: import_types41.EdgeType.CONNECTS_TO };
11218
+ return { targetNodeId: (0, import_types42.serviceId)(peerName), serviceName, edgeType: import_types42.EdgeType.CONNECTS_TO };
10957
11219
  }
10958
11220
  return null;
10959
11221
  };
@@ -11077,9 +11339,9 @@ function parseFirebaseTargetName(targetName) {
11077
11339
  const secondSep = rest.indexOf(FIELD_SEP);
11078
11340
  if (secondSep === -1) return null;
11079
11341
  const method = rest.slice(0, secondSep);
11080
- const path54 = rest.slice(secondSep + 1);
11081
- if (!resourceName || !method || !path54) return null;
11082
- return { resourceName, method, path: path54 };
11342
+ const path55 = rest.slice(secondSep + 1);
11343
+ if (!resourceName || !method || !path55) return null;
11344
+ return { resourceName, method, path: path55 };
11083
11345
  }
11084
11346
  function resourceNameFor(type, labels) {
11085
11347
  if (!labels) return null;
@@ -11117,14 +11379,14 @@ function mapLogEntryToSignal(entry2) {
11117
11379
  if (!req2) return null;
11118
11380
  if (typeof req2.requestMethod !== "string" || req2.requestMethod.length === 0) return null;
11119
11381
  const method = req2.requestMethod.toUpperCase();
11120
- const path54 = pathFromRequestUrl(req2.requestUrl);
11121
- if (path54 === null) return null;
11382
+ const path55 = pathFromRequestUrl(req2.requestUrl);
11383
+ if (path55 === null) return null;
11122
11384
  const timestamp = entry2.timestamp;
11123
11385
  if (typeof timestamp !== "string" || timestamp.length === 0) return null;
11124
11386
  const isError = typeof req2.status === "number" && req2.status >= ERROR_STATUS_THRESHOLD2;
11125
11387
  return {
11126
11388
  targetKind: resourceType,
11127
- targetName: packFirebaseTargetName({ resourceName, method, path: path54 }),
11389
+ targetName: packFirebaseTargetName({ resourceName, method, path: path55 }),
11128
11390
  callCount: 1,
11129
11391
  errorCount: isError ? 1 : 0,
11130
11392
  lastObservedIso: timestamp
@@ -11141,7 +11403,7 @@ function mapLogEntriesToSignals(entries) {
11141
11403
 
11142
11404
  // src/connectors/firebase/resolve.ts
11143
11405
  init_cjs_shims();
11144
- var import_types42 = require("@neat.is/types");
11406
+ var import_types43 = require("@neat.is/types");
11145
11407
  function neatServiceNameFor(resourceType, resourceName, serviceMap) {
11146
11408
  switch (resourceType) {
11147
11409
  case "cloud_function":
@@ -11156,7 +11418,7 @@ function routeEntriesFor(graph, serviceName) {
11156
11418
  const entries = [];
11157
11419
  graph.forEachNode((_id, attrs) => {
11158
11420
  const node = attrs;
11159
- if (node.type !== import_types42.NodeType.RouteNode) return;
11421
+ if (node.type !== import_types43.NodeType.RouteNode) return;
11160
11422
  const route = attrs;
11161
11423
  if (route.service !== serviceName) return;
11162
11424
  entries.push({
@@ -11188,7 +11450,7 @@ function createFirebaseResolveTarget(graph, serviceMap) {
11188
11450
  return {
11189
11451
  targetNodeId: match.routeNodeId,
11190
11452
  serviceName,
11191
- edgeType: import_types42.EdgeType.CALLS
11453
+ edgeType: import_types43.EdgeType.CALLS
11192
11454
  };
11193
11455
  };
11194
11456
  }
@@ -11215,7 +11477,7 @@ init_cjs_shims();
11215
11477
 
11216
11478
  // src/connectors/cloudflare/connector.ts
11217
11479
  init_cjs_shims();
11218
- var import_types44 = require("@neat.is/types");
11480
+ var import_types45 = require("@neat.is/types");
11219
11481
 
11220
11482
  // src/connectors/cloudflare/client.ts
11221
11483
  init_cjs_shims();
@@ -11331,7 +11593,7 @@ function mapEventToSignal(event) {
11331
11593
  if (Number.isNaN(observedAt.getTime())) return null;
11332
11594
  const statusCode = metadata?.statusCode;
11333
11595
  const isError = typeof statusCode === "number" && statusCode >= ERROR_STATUS_THRESHOLD3;
11334
- const path54 = metadata?.trigger ? parsePathFromTrigger(metadata.trigger) : void 0;
11596
+ const path55 = metadata?.trigger ? parsePathFromTrigger(metadata.trigger) : void 0;
11335
11597
  return {
11336
11598
  targetKind: CLOUDFLARE_TARGET_KIND,
11337
11599
  targetName: scriptName,
@@ -11339,7 +11601,7 @@ function mapEventToSignal(event) {
11339
11601
  errorCount: isError ? 1 : 0,
11340
11602
  lastObservedIso: observedAt.toISOString(),
11341
11603
  method,
11342
- ...path54 ? { path: path54 } : {},
11604
+ ...path55 ? { path: path55 } : {},
11343
11605
  ...typeof statusCode === "number" ? { statusCode } : {},
11344
11606
  ...typeof metadata?.duration === "number" ? { duration: metadata.duration } : {}
11345
11607
  };
@@ -11379,19 +11641,19 @@ function findTaggedWorkerFileNode(graph, workerName) {
11379
11641
  graph.forEachNode((id, attrs) => {
11380
11642
  if (found) return;
11381
11643
  const a = attrs;
11382
- if (a.type === import_types44.NodeType.FileNode && a.platform === "cloudflare" && a.platformName === workerName) {
11644
+ if (a.type === import_types45.NodeType.FileNode && a.platform === "cloudflare" && a.platformName === workerName) {
11383
11645
  found = id;
11384
11646
  }
11385
11647
  });
11386
11648
  return found;
11387
11649
  }
11388
- function findMatchingRouteNode(graph, serviceName, method, path54) {
11389
- const normalizedPath = normalizePathTemplate(path54);
11650
+ function findMatchingRouteNode(graph, serviceName, method, path55) {
11651
+ const normalizedPath = normalizePathTemplate(path55);
11390
11652
  let found = null;
11391
11653
  graph.forEachNode((id, attrs) => {
11392
11654
  if (found) return;
11393
11655
  const a = attrs;
11394
- if (a.type !== import_types44.NodeType.RouteNode || a.service !== serviceName) return;
11656
+ if (a.type !== import_types45.NodeType.RouteNode || a.service !== serviceName) return;
11395
11657
  if (!a.pathTemplate || normalizePathTemplate(a.pathTemplate) !== normalizedPath) return;
11396
11658
  const routeMethod = (a.method ?? "").toUpperCase();
11397
11659
  if (routeMethod !== "ALL" && routeMethod !== method) return;
@@ -11403,18 +11665,18 @@ function createCloudflareResolveTarget(config, graph) {
11403
11665
  return (signal) => {
11404
11666
  if (signal.targetKind !== CLOUDFLARE_TARGET_KIND) return null;
11405
11667
  const scriptName = signal.targetName;
11406
- const { method, path: path54 } = signal;
11668
+ const { method, path: path55 } = signal;
11407
11669
  const resolveRouteGrain = (serviceName, wholeFileId) => {
11408
- if (!method || !path54) return wholeFileId;
11409
- return findMatchingRouteNode(graph, serviceName, method, path54) ?? wholeFileId;
11670
+ if (!method || !path55) return wholeFileId;
11671
+ return findMatchingRouteNode(graph, serviceName, method, path55) ?? wholeFileId;
11410
11672
  };
11411
11673
  const mapping = config.workers?.[scriptName];
11412
11674
  if (mapping) {
11413
- const wholeFileId = (0, import_types44.fileId)(mapping.service, mapping.entryFile);
11675
+ const wholeFileId = (0, import_types45.fileId)(mapping.service, mapping.entryFile);
11414
11676
  return {
11415
11677
  targetNodeId: resolveRouteGrain(mapping.service, wholeFileId),
11416
11678
  serviceName: mapping.service,
11417
- edgeType: import_types44.EdgeType.CALLS
11679
+ edgeType: import_types45.EdgeType.CALLS
11418
11680
  };
11419
11681
  }
11420
11682
  const taggedFileId = findTaggedWorkerFileNode(graph, scriptName);
@@ -11423,13 +11685,13 @@ function createCloudflareResolveTarget(config, graph) {
11423
11685
  return {
11424
11686
  targetNodeId: resolveRouteGrain(fileNode.service, taggedFileId),
11425
11687
  serviceName: fileNode.service,
11426
- edgeType: import_types44.EdgeType.CALLS
11688
+ edgeType: import_types45.EdgeType.CALLS
11427
11689
  };
11428
11690
  }
11429
11691
  return {
11430
- targetNodeId: (0, import_types44.infraId)("cloudflare-worker", scriptName),
11692
+ targetNodeId: (0, import_types45.infraId)("cloudflare-worker", scriptName),
11431
11693
  serviceName: scriptName,
11432
- edgeType: import_types44.EdgeType.CALLS,
11694
+ edgeType: import_types45.EdgeType.CALLS,
11433
11695
  ensureInfraNode: { kind: "cloudflare-worker", name: scriptName, provider: "cloudflare" }
11434
11696
  };
11435
11697
  };
@@ -11873,7 +12135,7 @@ init_auth();
11873
12135
  // src/unrouted.ts
11874
12136
  init_cjs_shims();
11875
12137
  var import_node_fs30 = require("fs");
11876
- var import_node_path50 = __toESM(require("path"), 1);
12138
+ var import_node_path51 = __toESM(require("path"), 1);
11877
12139
  function buildUnroutedSpanRecord(serviceName, traceId, now = /* @__PURE__ */ new Date()) {
11878
12140
  return {
11879
12141
  timestamp: now.toISOString(),
@@ -11883,34 +12145,34 @@ function buildUnroutedSpanRecord(serviceName, traceId, now = /* @__PURE__ */ new
11883
12145
  };
11884
12146
  }
11885
12147
  async function appendUnroutedSpan(neatHome4, record) {
11886
- const target = import_node_path50.default.join(neatHome4, "errors.ndjson");
12148
+ const target = import_node_path51.default.join(neatHome4, "errors.ndjson");
11887
12149
  await import_node_fs30.promises.mkdir(neatHome4, { recursive: true });
11888
12150
  await import_node_fs30.promises.appendFile(target, JSON.stringify(record) + "\n", "utf8");
11889
12151
  }
11890
12152
  function unroutedErrorsPath(neatHome4) {
11891
- return import_node_path50.default.join(neatHome4, "errors.ndjson");
12153
+ return import_node_path51.default.join(neatHome4, "errors.ndjson");
11892
12154
  }
11893
12155
 
11894
12156
  // src/daemon.ts
11895
- var import_types47 = require("@neat.is/types");
12157
+ var import_types48 = require("@neat.is/types");
11896
12158
  function daemonJsonPath(scanPath) {
11897
- return import_node_path51.default.join(scanPath, "neat-out", "daemon.json");
12159
+ return import_node_path52.default.join(scanPath, "neat-out", "daemon.json");
11898
12160
  }
11899
12161
  function daemonsDiscoveryDir(home) {
11900
12162
  const base = home && home.length > 0 ? home : neatHomeFromEnv();
11901
- return import_node_path51.default.join(base, "daemons");
12163
+ return import_node_path52.default.join(base, "daemons");
11902
12164
  }
11903
12165
  function daemonDiscoveryPath(project, home) {
11904
- return import_node_path51.default.join(daemonsDiscoveryDir(home), `${sanitizeDiscoveryName(project)}.json`);
12166
+ return import_node_path52.default.join(daemonsDiscoveryDir(home), `${sanitizeDiscoveryName(project)}.json`);
11905
12167
  }
11906
12168
  function sanitizeDiscoveryName(project) {
11907
12169
  return project.replace(/[^A-Za-z0-9._-]/g, "_");
11908
12170
  }
11909
12171
  function neatHomeFromEnv() {
11910
12172
  const env = process.env.NEAT_HOME;
11911
- if (env && env.length > 0) return import_node_path51.default.resolve(env);
12173
+ if (env && env.length > 0) return import_node_path52.default.resolve(env);
11912
12174
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
11913
- return import_node_path51.default.join(home, ".neat");
12175
+ return import_node_path52.default.join(home, ".neat");
11914
12176
  }
11915
12177
  function resolveNeatVersion() {
11916
12178
  if (process.env.NEAT_LOCAL_VERSION && process.env.NEAT_LOCAL_VERSION.length > 0) {
@@ -11979,11 +12241,11 @@ function teardownSlot(slot) {
11979
12241
  }
11980
12242
  }
11981
12243
  function neatHomeFor(opts) {
11982
- if (opts.neatHome && opts.neatHome.length > 0) return import_node_path51.default.resolve(opts.neatHome);
12244
+ if (opts.neatHome && opts.neatHome.length > 0) return import_node_path52.default.resolve(opts.neatHome);
11983
12245
  const env = process.env.NEAT_HOME;
11984
- if (env && env.length > 0) return import_node_path51.default.resolve(env);
12246
+ if (env && env.length > 0) return import_node_path52.default.resolve(env);
11985
12247
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
11986
- return import_node_path51.default.join(home, ".neat");
12248
+ return import_node_path52.default.join(home, ".neat");
11987
12249
  }
11988
12250
  function routeSpanToProject(serviceName, projects) {
11989
12251
  if (!serviceName) return DEFAULT_PROJECT;
@@ -12027,11 +12289,11 @@ function spanBelongsToSingleProject(graph, project, serviceName) {
12027
12289
  if (!serviceName) return true;
12028
12290
  if (serviceNameMatchesProject(serviceName, project)) return true;
12029
12291
  return graph.someNode(
12030
- (_id, attrs) => attrs.type === import_types47.NodeType.ServiceNode && attrs.name === serviceName
12292
+ (_id, attrs) => attrs.type === import_types48.NodeType.ServiceNode && attrs.name === serviceName
12031
12293
  );
12032
12294
  }
12033
12295
  async function bootstrapProject(entry2, connectors = [], neatHome4) {
12034
- const paths = pathsForProject(entry2.name, import_node_path51.default.join(entry2.path, "neat-out"));
12296
+ const paths = pathsForProject(entry2.name, import_node_path52.default.join(entry2.path, "neat-out"));
12035
12297
  try {
12036
12298
  const stat = await import_node_fs31.promises.stat(entry2.path);
12037
12299
  if (!stat.isDirectory()) {
@@ -12162,7 +12424,7 @@ async function startDaemon(opts = {}) {
12162
12424
  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;
12163
12425
  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;
12164
12426
  const singleProject = projectArg;
12165
- const singleProjectPath = singleProject && projectPathArg ? import_node_path51.default.resolve(projectPathArg) : null;
12427
+ const singleProjectPath = singleProject && projectPathArg ? import_node_path52.default.resolve(projectPathArg) : null;
12166
12428
  if (singleProject && !singleProjectPath) {
12167
12429
  throw new Error(
12168
12430
  `neatd: project "${singleProject}" given without a projectPath; pass NEAT_PROJECT_PATH alongside NEAT_PROJECT.`
@@ -12177,7 +12439,7 @@ async function startDaemon(opts = {}) {
12177
12439
  );
12178
12440
  }
12179
12441
  }
12180
- const pidPath = import_node_path51.default.join(home, "neatd.pid");
12442
+ const pidPath = import_node_path52.default.join(home, "neatd.pid");
12181
12443
  await writeAtomically(pidPath, `${process.pid}
12182
12444
  `);
12183
12445
  const slots = /* @__PURE__ */ new Map();
@@ -12584,8 +12846,8 @@ async function startDaemon(opts = {}) {
12584
12846
  let registryWatcher = null;
12585
12847
  let reloadTimer = null;
12586
12848
  if (!singleProject) try {
12587
- const regDir = import_node_path51.default.dirname(regPath);
12588
- const regBase = import_node_path51.default.basename(regPath);
12849
+ const regDir = import_node_path52.default.dirname(regPath);
12850
+ const regBase = import_node_path52.default.basename(regPath);
12589
12851
  registryWatcher = (0, import_node_fs31.watch)(regDir, (_eventType, filename) => {
12590
12852
  if (filename !== null && filename !== regBase) return;
12591
12853
  if (reloadTimer) clearTimeout(reloadTimer);
@@ -12660,7 +12922,7 @@ init_cjs_shims();
12660
12922
  var import_node_child_process2 = require("child_process");
12661
12923
  var import_node_fs32 = require("fs");
12662
12924
  var import_node_net = __toESM(require("net"), 1);
12663
- var import_node_path52 = __toESM(require("path"), 1);
12925
+ var import_node_path53 = __toESM(require("path"), 1);
12664
12926
  var DEFAULT_WEB_PORT = 6328;
12665
12927
  var DEFAULT_REST_PORT = 8080;
12666
12928
  function asValidPort(value) {
@@ -12669,11 +12931,11 @@ function asValidPort(value) {
12669
12931
  }
12670
12932
  function projectRoot() {
12671
12933
  const fromEnv = process.env.NEAT_SCAN_PATH;
12672
- return import_node_path52.default.resolve(fromEnv && fromEnv.length > 0 ? fromEnv : process.cwd());
12934
+ return import_node_path53.default.resolve(fromEnv && fromEnv.length > 0 ? fromEnv : process.cwd());
12673
12935
  }
12674
12936
  async function readDaemonPorts(root) {
12675
12937
  try {
12676
- const raw = await import_node_fs32.promises.readFile(import_node_path52.default.join(root, "neat-out", "daemon.json"), "utf8");
12938
+ const raw = await import_node_fs32.promises.readFile(import_node_path53.default.join(root, "neat-out", "daemon.json"), "utf8");
12677
12939
  const parsed = JSON.parse(raw);
12678
12940
  const ports = parsed?.ports ?? {};
12679
12941
  return { web: asValidPort(ports.web), rest: asValidPort(ports.rest) };
@@ -12718,10 +12980,10 @@ function resolveWebPackageDir() {
12718
12980
  eval("require")
12719
12981
  );
12720
12982
  const pkgJsonPath = req.resolve("@neat.is/web/package.json");
12721
- return import_node_path52.default.dirname(pkgJsonPath);
12983
+ return import_node_path53.default.dirname(pkgJsonPath);
12722
12984
  }
12723
12985
  function resolveStandaloneServerEntry(webDir) {
12724
- return import_node_path52.default.join(webDir, ".next/standalone/packages/web/server.js");
12986
+ return import_node_path53.default.join(webDir, ".next/standalone/packages/web/server.js");
12725
12987
  }
12726
12988
  async function pickInternalPort() {
12727
12989
  return new Promise((resolve, reject) => {
@@ -12774,7 +13036,7 @@ async function spawnWebUI(restPort, opts = {}) {
12774
13036
  NEAT_API_URL: apiUrl
12775
13037
  };
12776
13038
  child = (0, import_node_child_process2.spawn)(process.execPath, [serverEntry], {
12777
- cwd: import_node_path52.default.dirname(serverEntry),
13039
+ cwd: import_node_path53.default.dirname(serverEntry),
12778
13040
  env,
12779
13041
  stdio: ["ignore", "inherit", "inherit"],
12780
13042
  detached: false
@@ -12950,14 +13212,14 @@ function localVersion() {
12950
13212
  }
12951
13213
  function neatHome3() {
12952
13214
  if (process.env.NEAT_HOME && process.env.NEAT_HOME.length > 0) {
12953
- return import_node_path53.default.resolve(process.env.NEAT_HOME);
13215
+ return import_node_path54.default.resolve(process.env.NEAT_HOME);
12954
13216
  }
12955
13217
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
12956
- return import_node_path53.default.join(home, ".neat");
13218
+ return import_node_path54.default.join(home, ".neat");
12957
13219
  }
12958
13220
  async function readPid() {
12959
13221
  try {
12960
- const raw = await import_node_fs33.promises.readFile(import_node_path53.default.join(neatHome3(), "neatd.pid"), "utf8");
13222
+ const raw = await import_node_fs33.promises.readFile(import_node_path54.default.join(neatHome3(), "neatd.pid"), "utf8");
12961
13223
  const n = Number.parseInt(raw.trim(), 10);
12962
13224
  return Number.isFinite(n) ? n : null;
12963
13225
  } catch {