@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/index.cjs CHANGED
@@ -60,8 +60,8 @@ function mountBearerAuth(app, opts) {
60
60
  ]);
61
61
  const publicRead = opts.publicRead === true;
62
62
  app.addHook("preHandler", (req, reply, done) => {
63
- const path52 = (req.url.split("?")[0] ?? "").replace(/\/+$/, "");
64
- if (exactUnauthPaths.has(path52) || PROJECT_SCOPED_UNAUTH_PATTERN.test(path52)) {
63
+ const path53 = (req.url.split("?")[0] ?? "").replace(/\/+$/, "");
64
+ if (exactUnauthPaths.has(path53) || PROJECT_SCOPED_UNAUTH_PATTERN.test(path53)) {
65
65
  done();
66
66
  return;
67
67
  }
@@ -193,8 +193,8 @@ function reshapeGrpcRequest(req) {
193
193
  };
194
194
  }
195
195
  function resolveProtoRoot() {
196
- const here = import_node_path48.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
197
- return import_node_path48.default.resolve(here, "..", "proto");
196
+ const here = import_node_path49.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
197
+ return import_node_path49.default.resolve(here, "..", "proto");
198
198
  }
199
199
  function loadTraceService() {
200
200
  const protoRoot = resolveProtoRoot();
@@ -262,13 +262,13 @@ async function startOtelGrpcReceiver(opts) {
262
262
  })
263
263
  };
264
264
  }
265
- var import_node_url, import_node_path48, import_node_crypto2, grpc, protoLoader;
265
+ var import_node_url, import_node_path49, import_node_crypto2, grpc, protoLoader;
266
266
  var init_otel_grpc = __esm({
267
267
  "src/otel-grpc.ts"() {
268
268
  "use strict";
269
269
  init_cjs_shims();
270
270
  import_node_url = require("url");
271
- import_node_path48 = __toESM(require("path"), 1);
271
+ import_node_path49 = __toESM(require("path"), 1);
272
272
  import_node_crypto2 = require("crypto");
273
273
  grpc = __toESM(require("@grpc/grpc-js"), 1);
274
274
  protoLoader = __toESM(require("@grpc/proto-loader"), 1);
@@ -346,6 +346,14 @@ function normalizeDbSystem(attrs) {
346
346
  if (typeof raw !== "string") return void 0;
347
347
  return raw === "mongoose" ? "mongodb" : raw;
348
348
  }
349
+ function tableFromSqlStatement(sql) {
350
+ if (typeof sql !== "string" || sql.length === 0) return null;
351
+ if (/\bjoin\b/i.test(sql)) return null;
352
+ const froms = sql.match(/\bfrom\b/gi);
353
+ if (froms && froms.length > 1) return null;
354
+ const m = /\b(?:from|into|update)\s+(?:"?[\w$]+"?\s*\.\s*)?"?([a-zA-Z_][\w$]*)"?/i.exec(sql);
355
+ return m ? m[1] : null;
356
+ }
349
357
  function messagingDestinationOf(attrs) {
350
358
  for (const key of ["messaging.destination.name", "messaging.destination"]) {
351
359
  const v = attrs[key];
@@ -366,8 +374,8 @@ function websocketChannelPathOf(attrs) {
366
374
  const v = attrs[key];
367
375
  if (typeof v === "string" && v.length > 0) {
368
376
  const q = v.indexOf("?");
369
- const path52 = q === -1 ? v : v.slice(0, q);
370
- if (path52.length > 0) return path52;
377
+ const path53 = q === -1 ? v : v.slice(0, q);
378
+ if (path53.length > 0) return path53;
371
379
  }
372
380
  }
373
381
  return void 0;
@@ -403,6 +411,7 @@ function parseOtlpRequest(body) {
403
411
  dbSystem: normalizeDbSystem(attrs),
404
412
  dbName: typeof attrs["db.name"] === "string" ? attrs["db.name"] : void 0,
405
413
  dbCollection: typeof attrs["db.collection.name"] === "string" ? attrs["db.collection.name"] : typeof attrs["db.mongodb.collection"] === "string" ? attrs["db.mongodb.collection"] : void 0,
414
+ dbTable: typeof attrs["db.statement"] === "string" ? tableFromSqlStatement(attrs["db.statement"]) ?? void 0 : void 0,
406
415
  messagingSystem: typeof attrs["messaging.system"] === "string" ? attrs["messaging.system"] : void 0,
407
416
  messagingDestination: messagingDestinationOf(attrs),
408
417
  graphqlOperationName: typeof attrs["graphql.operation.name"] === "string" && attrs["graphql.operation.name"].length > 0 ? attrs["graphql.operation.name"] : void 0,
@@ -422,10 +431,10 @@ function parseOtlpRequest(body) {
422
431
  return out;
423
432
  }
424
433
  function loadProtoRoot() {
425
- const here = import_node_path49.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
426
- const protoRoot = import_node_path49.default.resolve(here, "..", "proto");
434
+ const here = import_node_path50.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
435
+ const protoRoot = import_node_path50.default.resolve(here, "..", "proto");
427
436
  const root = new import_protobufjs.default.Root();
428
- root.resolvePath = (_origin, target) => import_node_path49.default.resolve(protoRoot, target);
437
+ root.resolvePath = (_origin, target) => import_node_path50.default.resolve(protoRoot, target);
429
438
  root.loadSync(
430
439
  "opentelemetry/proto/collector/trace/v1/trace_service.proto",
431
440
  { keepCase: true }
@@ -661,12 +670,12 @@ function logSpanHandler(span) {
661
670
  `otel: ${span.service} ${span.name} parent=${parent} status=${status2}${db}`
662
671
  );
663
672
  }
664
- 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;
673
+ 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;
665
674
  var init_otel = __esm({
666
675
  "src/otel.ts"() {
667
676
  "use strict";
668
677
  init_cjs_shims();
669
- import_node_path49 = __toESM(require("path"), 1);
678
+ import_node_path50 = __toESM(require("path"), 1);
670
679
  import_node_url2 = require("url");
671
680
  import_fastify2 = __toESM(require("fastify"), 1);
672
681
  import_protobufjs = __toESM(require("protobufjs"), 1);
@@ -1256,19 +1265,19 @@ function confidenceFromMix(edges, now = Date.now()) {
1256
1265
  function longestIncomingWalk(graph, start, maxDepth) {
1257
1266
  let best = { path: [start], edges: [] };
1258
1267
  const visited = /* @__PURE__ */ new Set([start]);
1259
- function step(node, path52, edges) {
1260
- if (path52.length > best.path.length) {
1261
- best = { path: [...path52], edges: [...edges] };
1268
+ function step(node, path53, edges) {
1269
+ if (path53.length > best.path.length) {
1270
+ best = { path: [...path53], edges: [...edges] };
1262
1271
  }
1263
- if (path52.length - 1 >= maxDepth) return;
1272
+ if (path53.length - 1 >= maxDepth) return;
1264
1273
  const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
1265
1274
  for (const [srcId, edge] of incoming) {
1266
1275
  if (visited.has(srcId)) continue;
1267
1276
  visited.add(srcId);
1268
- path52.push(srcId);
1277
+ path53.push(srcId);
1269
1278
  edges.push(edge);
1270
- step(srcId, path52, edges);
1271
- path52.pop();
1279
+ step(srcId, path53, edges);
1280
+ path53.pop();
1272
1281
  edges.pop();
1273
1282
  visited.delete(srcId);
1274
1283
  }
@@ -1276,11 +1285,11 @@ function longestIncomingWalk(graph, start, maxDepth) {
1276
1285
  step(start, [start], []);
1277
1286
  return best;
1278
1287
  }
1279
- function databaseRootCauseShape(graph, origin, walk3) {
1288
+ function databaseRootCauseShape(graph, origin, walk4) {
1280
1289
  const targetDb = origin;
1281
1290
  const candidatePairs = compatPairs().filter((p) => p.engine === targetDb.engine);
1282
1291
  if (candidatePairs.length === 0) return null;
1283
- for (const id of walk3.path) {
1292
+ for (const id of walk4.path) {
1284
1293
  const owner = resolveOwningService(graph, id);
1285
1294
  if (!owner) continue;
1286
1295
  const { id: serviceId6, svc } = owner;
@@ -1307,8 +1316,8 @@ function databaseRootCauseShape(graph, origin, walk3) {
1307
1316
  }
1308
1317
  return null;
1309
1318
  }
1310
- function serviceRootCauseShape(graph, _origin, walk3) {
1311
- for (const id of walk3.path) {
1319
+ function serviceRootCauseShape(graph, _origin, walk4) {
1320
+ for (const id of walk4.path) {
1312
1321
  const owner = resolveOwningService(graph, id);
1313
1322
  if (!owner) continue;
1314
1323
  const { id: serviceId6, svc } = owner;
@@ -1344,10 +1353,10 @@ function serviceRootCauseShape(graph, _origin, walk3) {
1344
1353
  }
1345
1354
  return null;
1346
1355
  }
1347
- function fileRootCauseShape(graph, origin, walk3) {
1356
+ function fileRootCauseShape(graph, origin, walk4) {
1348
1357
  const owner = resolveOwningService(graph, origin.id);
1349
1358
  if (!owner) return null;
1350
- return serviceRootCauseShape(graph, owner.svc, walk3);
1359
+ return serviceRootCauseShape(graph, owner.svc, walk4);
1351
1360
  }
1352
1361
  var rootCauseShapes = {
1353
1362
  [import_types.NodeType.DatabaseNode]: databaseRootCauseShape,
@@ -1359,16 +1368,16 @@ function getRootCause(graph, errorNodeId, errorEvent, incidents) {
1359
1368
  const origin = graph.getNodeAttributes(errorNodeId);
1360
1369
  const shape = rootCauseShapes[origin.type];
1361
1370
  if (shape) {
1362
- const walk3 = longestIncomingWalk(graph, errorNodeId, ROOT_CAUSE_MAX_DEPTH);
1363
- const match = shape(graph, origin, walk3);
1371
+ const walk4 = longestIncomingWalk(graph, errorNodeId, ROOT_CAUSE_MAX_DEPTH);
1372
+ const match = shape(graph, origin, walk4);
1364
1373
  if (match) {
1365
1374
  const reason = errorEvent ? `${match.rootCauseReason} (observed error: ${errorEvent.errorMessage})` : match.rootCauseReason;
1366
1375
  return import_types.RootCauseResultSchema.parse({
1367
1376
  rootCauseNode: match.rootCauseNode,
1368
1377
  rootCauseReason: reason,
1369
- traversalPath: walk3.path,
1370
- edgeProvenances: walk3.edges.map((e) => e.provenance),
1371
- confidence: confidenceFromMix(walk3.edges),
1378
+ traversalPath: walk4.path,
1379
+ edgeProvenances: walk4.edges.map((e) => e.provenance),
1380
+ confidence: confidenceFromMix(walk4.edges),
1372
1381
  fixRecommendation: match.fixRecommendation
1373
1382
  });
1374
1383
  }
@@ -1389,8 +1398,8 @@ function localizeFromIncidents(nodeId, incidents, errorEvent) {
1389
1398
  if (relevant.length === 0) return null;
1390
1399
  const latest = [...relevant].sort((a, b) => b.timestamp.localeCompare(a.timestamp))[0];
1391
1400
  const attrs = latest.attributes ?? {};
1392
- const filepath = typeof attrs["code.filepath"] === "string" ? attrs["code.filepath"] : void 0;
1393
- const lineno = typeof attrs["code.lineno"] === "number" ? attrs["code.lineno"] : void 0;
1401
+ const filepath = codeFilepathOf(attrs);
1402
+ const lineno = codeLinenoOf(attrs);
1394
1403
  const route = typeof attrs["http.route"] === "string" ? attrs["http.route"] : void 0;
1395
1404
  const location = filepath ? `${filepath}${lineno !== void 0 ? `:${lineno}` : ""}` : void 0;
1396
1405
  const sameMode = relevant.filter((ev) => ev.errorMessage === latest.errorMessage);
@@ -1462,26 +1471,26 @@ function dominantFailingCall(graph, serviceId6, visited) {
1462
1471
  return best;
1463
1472
  }
1464
1473
  function followFailingCallChain(graph, originServiceId, maxDepth) {
1465
- const path52 = [originServiceId];
1474
+ const path53 = [originServiceId];
1466
1475
  const edges = [];
1467
1476
  const visited = /* @__PURE__ */ new Set([originServiceId]);
1468
1477
  let current = originServiceId;
1469
1478
  for (let depth = 0; depth < maxDepth; depth++) {
1470
1479
  const hop = dominantFailingCall(graph, current, visited);
1471
1480
  if (!hop) break;
1472
- path52.push(hop.nextService);
1481
+ path53.push(hop.nextService);
1473
1482
  edges.push(hop.edge);
1474
1483
  visited.add(hop.nextService);
1475
1484
  current = hop.nextService;
1476
1485
  }
1477
1486
  if (edges.length === 0) return null;
1478
- return { path: path52, edges, culprit: current };
1487
+ return { path: path53, edges, culprit: current };
1479
1488
  }
1480
1489
  function crossServiceRootCause(graph, originId, incidents, errorEvent) {
1481
1490
  const chain = followFailingCallChain(graph, originId, ROOT_CAUSE_MAX_DEPTH);
1482
1491
  if (!chain) return null;
1483
1492
  const culprit = chain.culprit;
1484
- const path52 = [...chain.path];
1493
+ const path53 = [...chain.path];
1485
1494
  const edgeProvenances = chain.edges.map((e) => e.provenance);
1486
1495
  const baseConfidence = confidenceFromMix(chain.edges);
1487
1496
  const confidence = Math.max(0, Math.min(1, baseConfidence * INCIDENT_ROOT_CAUSE_CONFIDENCE));
@@ -1489,14 +1498,14 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
1489
1498
  if (loc) {
1490
1499
  let rootCauseNode = culprit;
1491
1500
  if (loc.fileNode) {
1492
- path52.push(loc.fileNode);
1501
+ path53.push(loc.fileNode);
1493
1502
  edgeProvenances.push(import_types.Provenance.OBSERVED);
1494
1503
  rootCauseNode = loc.fileNode;
1495
1504
  }
1496
1505
  return import_types.RootCauseResultSchema.parse({
1497
1506
  rootCauseNode,
1498
1507
  rootCauseReason: loc.rootCauseReason,
1499
- traversalPath: path52,
1508
+ traversalPath: path53,
1500
1509
  edgeProvenances,
1501
1510
  confidence,
1502
1511
  ...loc.fixRecommendation ? { fixRecommendation: loc.fixRecommendation } : {}
@@ -1508,7 +1517,7 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
1508
1517
  return import_types.RootCauseResultSchema.parse({
1509
1518
  rootCauseNode: culprit,
1510
1519
  rootCauseReason: `${culpritName} is failing downstream calls (${errs} observed error${errs === 1 ? "" : "s"})`,
1511
- traversalPath: path52,
1520
+ traversalPath: path53,
1512
1521
  edgeProvenances,
1513
1522
  confidence,
1514
1523
  fixRecommendation: `Inspect ${culpritName}'s failing handler`
@@ -2266,9 +2275,30 @@ function isLoopbackHost(host) {
2266
2275
  const h = host.toLowerCase();
2267
2276
  return h === "localhost" || h === "ip6-localhost" || h === "::1" || h === "[::1]" || /^127(?:\.\d{1,3}){3}$/.test(h);
2268
2277
  }
2278
+ var CODE_FILE_PATH_ATTR = "code.file.path";
2269
2279
  var CODE_FILEPATH_ATTR = "code.filepath";
2280
+ var CODE_LINE_NUMBER_ATTR = "code.line.number";
2270
2281
  var CODE_LINENO_ATTR = "code.lineno";
2282
+ var CODE_FUNCTION_NAME_ATTR = "code.function.name";
2271
2283
  var CODE_FUNCTION_ATTR = "code.function";
2284
+ function codeFilepathOf(attrs) {
2285
+ const stable = attrs[CODE_FILE_PATH_ATTR];
2286
+ if (typeof stable === "string" && stable.length > 0) return stable;
2287
+ const prior = attrs[CODE_FILEPATH_ATTR];
2288
+ return typeof prior === "string" && prior.length > 0 ? prior : void 0;
2289
+ }
2290
+ function codeLinenoOf(attrs) {
2291
+ const stable = attrs[CODE_LINE_NUMBER_ATTR];
2292
+ if (typeof stable === "number" && Number.isFinite(stable)) return stable;
2293
+ const prior = attrs[CODE_LINENO_ATTR];
2294
+ return typeof prior === "number" && Number.isFinite(prior) ? prior : void 0;
2295
+ }
2296
+ function codeFunctionOf(attrs) {
2297
+ const stable = attrs[CODE_FUNCTION_NAME_ATTR];
2298
+ if (typeof stable === "string" && stable.length > 0) return stable;
2299
+ const prior = attrs[CODE_FUNCTION_ATTR];
2300
+ return typeof prior === "string" && prior.length > 0 ? prior : void 0;
2301
+ }
2272
2302
  function toPosix(p) {
2273
2303
  return p.split("\\").join("/");
2274
2304
  }
@@ -2346,10 +2376,9 @@ function resolveDistToSrc(absFilepath, line) {
2346
2376
  }
2347
2377
  }
2348
2378
  function callSiteFromSpan(span, serviceNode, scanPath) {
2349
- const filepath = span.attributes[CODE_FILEPATH_ATTR];
2350
- if (typeof filepath !== "string" || filepath.length === 0) return null;
2351
- const linenoRaw = span.attributes[CODE_LINENO_ATTR];
2352
- let line = typeof linenoRaw === "number" && Number.isFinite(linenoRaw) ? linenoRaw : void 0;
2379
+ const filepath = codeFilepathOf(span.attributes);
2380
+ if (filepath === void 0) return null;
2381
+ let line = codeLinenoOf(span.attributes);
2353
2382
  const abs = toPosix(filepath).replace(/^file:\/\//, "");
2354
2383
  const resolved = resolveDistToSrc(abs, line);
2355
2384
  let effectivePath = filepath;
@@ -2364,8 +2393,7 @@ function callSiteFromSpan(span, serviceNode, scanPath) {
2364
2393
  if (!resolved && abs.endsWith(".js") && relPath.startsWith("dist/") && serviceNode?.name) {
2365
2394
  warnNoSourceMaps(serviceNode.name);
2366
2395
  }
2367
- const fnRaw = span.attributes[CODE_FUNCTION_ATTR];
2368
- const fn = typeof fnRaw === "string" && fnRaw.length > 0 ? fnRaw : void 0;
2396
+ const fn = codeFunctionOf(span.attributes);
2369
2397
  return {
2370
2398
  relPath,
2371
2399
  ...line !== void 0 ? { line } : {},
@@ -2947,6 +2975,18 @@ async function handleSpan(ctx, span) {
2947
2975
  callSiteEvidence
2948
2976
  );
2949
2977
  }
2978
+ if (span.dbTable) {
2979
+ const tableId = ensureInfraNode(ctx.graph, "sql-table", span.dbTable, "self");
2980
+ upsertObservedEdge(
2981
+ ctx.graph,
2982
+ import_types3.EdgeType.CALLS,
2983
+ observedSource(),
2984
+ tableId,
2985
+ ts,
2986
+ isError,
2987
+ callSiteEvidence
2988
+ );
2989
+ }
2950
2990
  }
2951
2991
  } else if (span.messagingSystem && span.messagingDestination && spanMintsMessagingEdge(span.kind)) {
2952
2992
  const targetId = ensureMessagingDestinationNode(
@@ -4114,14 +4154,14 @@ function buildServiceHostIndex(services) {
4114
4154
  }
4115
4155
  async function walkSourceFiles(dir) {
4116
4156
  const out = [];
4117
- async function walk3(current) {
4157
+ async function walk4(current) {
4118
4158
  const entries = await import_node_fs10.promises.readdir(current, { withFileTypes: true }).catch(() => []);
4119
4159
  for (const entry of entries) {
4120
4160
  const full = import_node_path10.default.join(current, entry.name);
4121
4161
  if (entry.isDirectory()) {
4122
4162
  if (IGNORED_DIRS.has(entry.name)) continue;
4123
4163
  if (await isPythonVenvDir(full)) continue;
4124
- await walk3(full);
4164
+ await walk4(full);
4125
4165
  } else if (entry.isFile() && SERVICE_FILE_EXTENSIONS.has(import_node_path10.default.extname(entry.name)) && // Skip NEAT's own generated `otel-init.*` bootstrap — extracting it
4126
4166
  // would attribute our instrumentation imports to the user's service.
4127
4167
  !isNeatAuthoredSourceFile(entry.name)) {
@@ -4129,7 +4169,7 @@ async function walkSourceFiles(dir) {
4129
4169
  }
4130
4170
  }
4131
4171
  }
4132
- await walk3(dir);
4172
+ await walk4(dir);
4133
4173
  return out;
4134
4174
  }
4135
4175
  async function loadSourceFiles(dir) {
@@ -5279,20 +5319,20 @@ var import_node_path21 = __toESM(require("path"), 1);
5279
5319
  var import_types10 = require("@neat.is/types");
5280
5320
  async function walkConfigFiles(dir) {
5281
5321
  const out = [];
5282
- async function walk3(current) {
5322
+ async function walk4(current) {
5283
5323
  const entries = await import_node_fs14.promises.readdir(current, { withFileTypes: true });
5284
5324
  for (const entry of entries) {
5285
5325
  const full = import_node_path21.default.join(current, entry.name);
5286
5326
  if (entry.isDirectory()) {
5287
5327
  if (IGNORED_DIRS.has(entry.name)) continue;
5288
5328
  if (await isPythonVenvDir(full)) continue;
5289
- await walk3(full);
5329
+ await walk4(full);
5290
5330
  } else if (entry.isFile() && isConfigFile(entry.name).match) {
5291
5331
  out.push(full);
5292
5332
  }
5293
5333
  }
5294
5334
  }
5295
- await walk3(dir);
5335
+ await walk4(dir);
5296
5336
  return out;
5297
5337
  }
5298
5338
  async function addConfigNodes(graph, services, scanPath) {
@@ -5345,6 +5385,7 @@ init_cjs_shims();
5345
5385
  var import_node_path22 = __toESM(require("path"), 1);
5346
5386
  var import_tree_sitter2 = __toESM(require("tree-sitter"), 1);
5347
5387
  var import_tree_sitter_javascript2 = __toESM(require("tree-sitter-javascript"), 1);
5388
+ var import_tree_sitter_python2 = __toESM(require("tree-sitter-python"), 1);
5348
5389
  var import_types11 = require("@neat.is/types");
5349
5390
  var PARSE_CHUNK2 = 16384;
5350
5391
  function parseSource2(parser, source) {
@@ -5357,6 +5398,11 @@ function makeJsParser2() {
5357
5398
  p.setLanguage(import_tree_sitter_javascript2.default);
5358
5399
  return p;
5359
5400
  }
5401
+ function makePyParser2() {
5402
+ const p = new import_tree_sitter2.default();
5403
+ p.setLanguage(import_tree_sitter_python2.default);
5404
+ return p;
5405
+ }
5360
5406
  var ROUTER_METHODS = /* @__PURE__ */ new Set([
5361
5407
  "get",
5362
5408
  "post",
@@ -5369,6 +5415,7 @@ var ROUTER_METHODS = /* @__PURE__ */ new Set([
5369
5415
  ]);
5370
5416
  var NEXT_APP_METHODS = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]);
5371
5417
  var JS_ROUTE_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx"]);
5418
+ var FASTAPI_METHODS = /* @__PURE__ */ new Set(["get", "post", "put", "patch", "delete", "options", "head", "trace"]);
5372
5419
  function canonicalizeTemplate(raw) {
5373
5420
  let p = raw.split("?")[0].split("#")[0];
5374
5421
  if (!p.startsWith("/")) p = "/" + p;
@@ -5596,8 +5643,102 @@ function nextRoutesFromFile(source, relFile, parser) {
5596
5643
  }
5597
5644
  return [];
5598
5645
  }
5646
+ function pyStaticStringText(node) {
5647
+ if (node.type !== "string") return null;
5648
+ for (let i = 0; i < node.namedChildCount; i++) {
5649
+ const child = node.namedChild(i);
5650
+ if (child?.type === "interpolation") return null;
5651
+ if (child?.type === "string_content") return child.text;
5652
+ }
5653
+ return "";
5654
+ }
5655
+ function keywordArrayStrings(argsNode, key) {
5656
+ for (let i = 0; i < argsNode.namedChildCount; i++) {
5657
+ const arg = argsNode.namedChild(i);
5658
+ if (arg?.type !== "keyword_argument") continue;
5659
+ if (arg.childForFieldName("name")?.text !== key) continue;
5660
+ const val = arg.childForFieldName("value");
5661
+ if (!val || val.type !== "list") return [];
5662
+ const out = [];
5663
+ for (let j = 0; j < val.namedChildCount; j++) {
5664
+ const el = val.namedChild(j);
5665
+ if (el?.type === "string") {
5666
+ const s = pyStaticStringText(el);
5667
+ if (s) out.push(s);
5668
+ }
5669
+ }
5670
+ return out;
5671
+ }
5672
+ return [];
5673
+ }
5674
+ function collectApiRouterPrefixes(root) {
5675
+ const prefixes = /* @__PURE__ */ new Map();
5676
+ walk(root, (node) => {
5677
+ if (node.type !== "assignment") return;
5678
+ const right = node.childForFieldName("right");
5679
+ if (!right || right.type !== "call") return;
5680
+ const fn = right.childForFieldName("function");
5681
+ if (!fn) return;
5682
+ const ctor = fn.type === "attribute" ? fn.childForFieldName("attribute")?.text : fn.text;
5683
+ if (ctor !== "APIRouter") return;
5684
+ const left = node.childForFieldName("left");
5685
+ if (!left || left.type !== "identifier") return;
5686
+ const args = right.childForFieldName("arguments");
5687
+ if (!args) return;
5688
+ for (let i = 0; i < args.namedChildCount; i++) {
5689
+ const arg = args.namedChild(i);
5690
+ if (arg?.type !== "keyword_argument") continue;
5691
+ if (arg.childForFieldName("name")?.text !== "prefix") continue;
5692
+ const val = arg.childForFieldName("value");
5693
+ const p = val ? pyStaticStringText(val) : null;
5694
+ if (p !== null) prefixes.set(left.text, p);
5695
+ }
5696
+ });
5697
+ return prefixes;
5698
+ }
5699
+ function fastapiRoutesFromSource(source, parser) {
5700
+ const tree = parseSource2(parser, source);
5701
+ const prefixes = collectApiRouterPrefixes(tree.rootNode);
5702
+ const out = [];
5703
+ walk(tree.rootNode, (node) => {
5704
+ if (node.type !== "decorator") return;
5705
+ const call = node.namedChild(0);
5706
+ if (!call || call.type !== "call") return;
5707
+ const fn = call.childForFieldName("function");
5708
+ if (!fn || fn.type !== "attribute") return;
5709
+ const method = fn.childForFieldName("attribute")?.text?.toLowerCase();
5710
+ if (!method) return;
5711
+ const isVerb = FASTAPI_METHODS.has(method);
5712
+ if (!isVerb && method !== "api_route") return;
5713
+ const args = call.childForFieldName("arguments");
5714
+ const first = args?.namedChild(0);
5715
+ if (!first || first.type !== "string") return;
5716
+ const rawPath = pyStaticStringText(first);
5717
+ if (rawPath === null || !rawPath.startsWith("/")) return;
5718
+ const obj = fn.childForFieldName("object")?.text;
5719
+ const prefix = obj ? prefixes.get(obj) ?? "" : "";
5720
+ const pathTemplate = canonicalizeTemplate(prefix + rawPath);
5721
+ const line = node.startPosition.row + 1;
5722
+ if (isVerb) {
5723
+ out.push({ method: method.toUpperCase(), pathTemplate, line, framework: "fastapi" });
5724
+ return;
5725
+ }
5726
+ const methods = keywordArrayStrings(args, "methods");
5727
+ const list = methods.length > 0 ? methods : ["ALL"];
5728
+ for (const m of list) {
5729
+ out.push({
5730
+ method: m === "ALL" ? "ALL" : m.toUpperCase(),
5731
+ pathTemplate,
5732
+ line,
5733
+ framework: "fastapi"
5734
+ });
5735
+ }
5736
+ });
5737
+ return out;
5738
+ }
5599
5739
  async function addRoutes(graph, services) {
5600
5740
  const jsParser = makeJsParser2();
5741
+ const pyParser = makePyParser2();
5601
5742
  let nodesAdded = 0;
5602
5743
  let edgesAdded = 0;
5603
5744
  for (const service of services) {
@@ -5609,15 +5750,20 @@ async function addRoutes(graph, services) {
5609
5750
  const hasFastify = deps["fastify"] !== void 0;
5610
5751
  const hasHono = deps["hono"] !== void 0;
5611
5752
  const hasNext = deps["next"] !== void 0;
5612
- if (!hasExpress && !hasFastify && !hasHono && !hasNext) continue;
5753
+ const hasFastapi = deps["fastapi"] !== void 0;
5754
+ if (!hasExpress && !hasFastify && !hasHono && !hasNext && !hasFastapi) continue;
5613
5755
  const files = await loadSourceFiles(service.dir);
5614
5756
  for (const file of files) {
5615
5757
  if (isTestPath(file.path)) continue;
5616
- if (!JS_ROUTE_EXTENSIONS.has(import_node_path22.default.extname(file.path))) continue;
5758
+ const ext = import_node_path22.default.extname(file.path);
5759
+ const isPy = ext === ".py";
5760
+ if (!JS_ROUTE_EXTENSIONS.has(ext) && !isPy) continue;
5617
5761
  const relFile = toPosix2(import_node_path22.default.relative(service.dir, file.path));
5618
5762
  let routes;
5619
5763
  try {
5620
- if (hasNext && (isNextAppRouteFile(relFile) || isNextPagesApiFile(relFile))) {
5764
+ if (isPy) {
5765
+ routes = hasFastapi ? fastapiRoutesFromSource(file.content, pyParser) : [];
5766
+ } else if (hasNext && (isNextAppRouteFile(relFile) || isNextPagesApiFile(relFile))) {
5621
5767
  routes = nextRoutesFromFile(file.content, relFile, jsParser);
5622
5768
  } else if (hasExpress || hasFastify || hasHono) {
5623
5769
  routes = serverRoutesFromSource(file.content, jsParser, hasExpress, hasFastify, hasHono);
@@ -5713,20 +5859,20 @@ function grpcMethodsFromProto(content, fqPackage) {
5713
5859
  }
5714
5860
  async function walkProtoFiles(dir) {
5715
5861
  const out = [];
5716
- async function walk3(current) {
5862
+ async function walk4(current) {
5717
5863
  const entries = await import_node_fs15.promises.readdir(current, { withFileTypes: true }).catch(() => []);
5718
5864
  for (const entry of entries) {
5719
5865
  const full = import_node_path23.default.join(current, entry.name);
5720
5866
  if (entry.isDirectory()) {
5721
5867
  if (IGNORED_DIRS.has(entry.name)) continue;
5722
5868
  if (await isPythonVenvDir(full)) continue;
5723
- await walk3(full);
5869
+ await walk4(full);
5724
5870
  } else if (entry.isFile() && import_node_path23.default.extname(entry.name) === PROTO_EXTENSION) {
5725
5871
  out.push(full);
5726
5872
  }
5727
5873
  }
5728
5874
  }
5729
- await walk3(dir);
5875
+ await walk4(dir);
5730
5876
  return out;
5731
5877
  }
5732
5878
  async function addGrpcMethods(graph, services) {
@@ -5794,14 +5940,14 @@ async function addGrpcMethods(graph, services) {
5794
5940
 
5795
5941
  // src/extract/calls/index.ts
5796
5942
  init_cjs_shims();
5797
- var import_types21 = require("@neat.is/types");
5943
+ var import_types22 = require("@neat.is/types");
5798
5944
 
5799
5945
  // src/extract/calls/http.ts
5800
5946
  init_cjs_shims();
5801
5947
  var import_node_path24 = __toESM(require("path"), 1);
5802
5948
  var import_tree_sitter3 = __toESM(require("tree-sitter"), 1);
5803
5949
  var import_tree_sitter_javascript3 = __toESM(require("tree-sitter-javascript"), 1);
5804
- var import_tree_sitter_python2 = __toESM(require("tree-sitter-python"), 1);
5950
+ var import_tree_sitter_python3 = __toESM(require("tree-sitter-python"), 1);
5805
5951
  var import_types13 = require("@neat.is/types");
5806
5952
  var STRING_LITERAL_NODE_TYPES = /* @__PURE__ */ new Set(["string_fragment", "string_content"]);
5807
5953
  var JSX_EXTERNAL_LINK_TAGS = /* @__PURE__ */ new Set(["a", "Link", "NavLink", "ExternalLink", "Anchor"]);
@@ -5856,14 +6002,14 @@ function makeJsParser3() {
5856
6002
  p.setLanguage(import_tree_sitter_javascript3.default);
5857
6003
  return p;
5858
6004
  }
5859
- function makePyParser2() {
6005
+ function makePyParser3() {
5860
6006
  const p = new import_tree_sitter3.default();
5861
- p.setLanguage(import_tree_sitter_python2.default);
6007
+ p.setLanguage(import_tree_sitter_python3.default);
5862
6008
  return p;
5863
6009
  }
5864
6010
  async function addHttpCallEdges(graph, services) {
5865
6011
  const jsParser = makeJsParser3();
5866
- const pyParser = makePyParser2();
6012
+ const pyParser = makePyParser3();
5867
6013
  const { knownHosts, hostToNodeId } = buildServiceHostIndex(services);
5868
6014
  let nodesAdded = 0;
5869
6015
  let edgesAdded = 0;
@@ -6803,15 +6949,130 @@ async function mongooseCrossFileEndpoints(files, serviceDir) {
6803
6949
  return out;
6804
6950
  }
6805
6951
 
6952
+ // src/extract/calls/sqlalchemy.ts
6953
+ init_cjs_shims();
6954
+ var import_node_path32 = __toESM(require("path"), 1);
6955
+ var import_tree_sitter5 = __toESM(require("tree-sitter"), 1);
6956
+ var import_tree_sitter_python4 = __toESM(require("tree-sitter-python"), 1);
6957
+ var import_types21 = require("@neat.is/types");
6958
+ var SQLALCHEMY_IMPORT_RE = /(?:from|import)\s+(?:flask_sqlalchemy|sqlalchemy)\b/;
6959
+ var PARSE_CHUNK5 = 16384;
6960
+ function makePyParser4() {
6961
+ const p = new import_tree_sitter5.default();
6962
+ p.setLanguage(import_tree_sitter_python4.default);
6963
+ return p;
6964
+ }
6965
+ function parseSource5(parser, source) {
6966
+ return parser.parse(
6967
+ (index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK5)
6968
+ );
6969
+ }
6970
+ function flaskSqlalchemyTableName(className) {
6971
+ return className.replace(/((?<=[a-z0-9])[A-Z]|(?!^)[A-Z](?=[a-z]))/g, "_$1").toLowerCase().replace(/^_+/, "");
6972
+ }
6973
+ function namedChildren(node) {
6974
+ const out = [];
6975
+ for (let i = 0; i < node.namedChildCount; i++) {
6976
+ const c = node.namedChild(i);
6977
+ if (c) out.push(c);
6978
+ }
6979
+ return out;
6980
+ }
6981
+ function pyStaticStringText2(node) {
6982
+ if (node.type !== "string") return null;
6983
+ for (const child of namedChildren(node)) {
6984
+ if (child.type === "interpolation") return null;
6985
+ if (child.type === "string_content") return child.text;
6986
+ }
6987
+ return "";
6988
+ }
6989
+ function explicitTablename(body) {
6990
+ for (const stmt of namedChildren(body)) {
6991
+ if (stmt.type !== "expression_statement") continue;
6992
+ const assign = stmt.namedChild(0);
6993
+ if (assign?.type !== "assignment") continue;
6994
+ if (assign.childForFieldName("left")?.text !== "__tablename__") continue;
6995
+ const right = assign.childForFieldName("right");
6996
+ if (right?.type === "string") {
6997
+ const s = pyStaticStringText2(right);
6998
+ return s ? { name: s } : "computed";
6999
+ }
7000
+ return "computed";
7001
+ }
7002
+ return null;
7003
+ }
7004
+ function extendsFlaskModel(cls) {
7005
+ const supers = cls.childForFieldName("superclasses");
7006
+ if (!supers) return false;
7007
+ for (const a of namedChildren(supers)) {
7008
+ const t = a.text;
7009
+ if (t === "db.Model" || t === "Model" || t.endsWith(".Model")) return true;
7010
+ }
7011
+ return false;
7012
+ }
7013
+ function walk3(node, visit) {
7014
+ visit(node);
7015
+ for (const c of namedChildren(node)) walk3(c, visit);
7016
+ }
7017
+ function sqlalchemyEndpointsFromFile(file, serviceDir) {
7018
+ if (!SQLALCHEMY_IMPORT_RE.test(file.content)) return [];
7019
+ const tree = parseSource5(makePyParser4(), file.content);
7020
+ const out = [];
7021
+ const seen = /* @__PURE__ */ new Set();
7022
+ const push = (name, line) => {
7023
+ if (seen.has(name)) return;
7024
+ seen.add(name);
7025
+ out.push({
7026
+ infraId: (0, import_types21.infraId)("sql-table", name),
7027
+ name,
7028
+ kind: "sql-table",
7029
+ edgeType: "CALLS",
7030
+ confidenceKind: "verified-call-site",
7031
+ evidence: {
7032
+ file: import_node_path32.default.relative(serviceDir, file.path),
7033
+ line,
7034
+ snippet: snippet(file.content, line)
7035
+ }
7036
+ });
7037
+ };
7038
+ walk3(tree.rootNode, (node) => {
7039
+ if (node.type === "class_definition") {
7040
+ const body = node.childForFieldName("body");
7041
+ const nameNode = node.childForFieldName("name");
7042
+ if (!body || !nameNode) return;
7043
+ const line = node.startPosition.row + 1;
7044
+ const explicit = explicitTablename(body);
7045
+ if (explicit === "computed") return;
7046
+ if (explicit) {
7047
+ push(explicit.name, line);
7048
+ return;
7049
+ }
7050
+ if (extendsFlaskModel(node)) push(flaskSqlalchemyTableName(nameNode.text), line);
7051
+ return;
7052
+ }
7053
+ if (node.type === "call") {
7054
+ const fn = node.childForFieldName("function");
7055
+ const fnText = fn?.text;
7056
+ if (fnText !== "Table" && fnText !== "sa.Table" && fnText !== "sqlalchemy.Table") return;
7057
+ const first = node.childForFieldName("arguments")?.namedChild(0);
7058
+ if (first?.type === "string") {
7059
+ const s = pyStaticStringText2(first);
7060
+ if (s) push(s, node.startPosition.row + 1);
7061
+ }
7062
+ }
7063
+ });
7064
+ return out;
7065
+ }
7066
+
6806
7067
  // src/extract/calls/index.ts
6807
7068
  function edgeTypeFromEndpoint(ep) {
6808
7069
  switch (ep.edgeType) {
6809
7070
  case "PUBLISHES_TO":
6810
- return import_types21.EdgeType.PUBLISHES_TO;
7071
+ return import_types22.EdgeType.PUBLISHES_TO;
6811
7072
  case "CONSUMES_FROM":
6812
- return import_types21.EdgeType.CONSUMES_FROM;
7073
+ return import_types22.EdgeType.CONSUMES_FROM;
6813
7074
  default:
6814
- return import_types21.EdgeType.CALLS;
7075
+ return import_types22.EdgeType.CALLS;
6815
7076
  }
6816
7077
  }
6817
7078
  function isAwsKind(kind) {
@@ -6835,6 +7096,7 @@ async function addExternalEndpointEdges(graph, services) {
6835
7096
  endpoints.push(...grpcEndpointsFromFile(maskedFile, service.dir));
6836
7097
  endpoints.push(...supabaseEndpointsFromFile(maskedFile, service.dir));
6837
7098
  endpoints.push(...mongooseEndpointsFromFile(maskedFile, service.dir));
7099
+ endpoints.push(...sqlalchemyEndpointsFromFile(maskedFile, service.dir));
6838
7100
  }
6839
7101
  endpoints.push(...await mongooseCrossFileEndpoints(maskedFiles, service.dir));
6840
7102
  if (endpoints.length === 0) continue;
@@ -6843,7 +7105,7 @@ async function addExternalEndpointEdges(graph, services) {
6843
7105
  if (!graph.hasNode(ep.infraId)) {
6844
7106
  const node = {
6845
7107
  id: ep.infraId,
6846
- type: import_types21.NodeType.InfraNode,
7108
+ type: import_types22.NodeType.InfraNode,
6847
7109
  name: ep.name,
6848
7110
  // #238 — `aws-*` covers AWS-SDK client kinds (aws-s3, aws-dynamodb,
6849
7111
  // aws-cognito-identity-provider, …); `s3-` / `dynamodb-` cover the
@@ -6855,7 +7117,7 @@ async function addExternalEndpointEdges(graph, services) {
6855
7117
  nodesAdded++;
6856
7118
  }
6857
7119
  const edgeType = edgeTypeFromEndpoint(ep);
6858
- const confidence = (0, import_types21.confidenceForExtracted)(ep.confidenceKind);
7120
+ const confidence = (0, import_types22.confidenceForExtracted)(ep.confidenceKind);
6859
7121
  const relFile = toPosix2(ep.evidence.file);
6860
7122
  const { fileNodeId, nodesAdded: n, edgesAdded: e } = ensureFileNode(
6861
7123
  graph,
@@ -6865,7 +7127,7 @@ async function addExternalEndpointEdges(graph, services) {
6865
7127
  );
6866
7128
  nodesAdded += n;
6867
7129
  edgesAdded += e;
6868
- if (!(0, import_types21.passesExtractedFloor)(confidence)) {
7130
+ if (!(0, import_types22.passesExtractedFloor)(confidence)) {
6869
7131
  noteExtractedDropped({
6870
7132
  source: fileNodeId,
6871
7133
  target: ep.infraId,
@@ -6885,7 +7147,7 @@ async function addExternalEndpointEdges(graph, services) {
6885
7147
  source: fileNodeId,
6886
7148
  target: ep.infraId,
6887
7149
  type: edgeType,
6888
- provenance: import_types21.Provenance.EXTRACTED,
7150
+ provenance: import_types22.Provenance.EXTRACTED,
6889
7151
  confidence,
6890
7152
  evidence: ep.evidence
6891
7153
  };
@@ -6911,16 +7173,16 @@ init_cjs_shims();
6911
7173
 
6912
7174
  // src/extract/infra/docker-compose.ts
6913
7175
  init_cjs_shims();
6914
- var import_node_path32 = __toESM(require("path"), 1);
6915
- var import_types23 = require("@neat.is/types");
7176
+ var import_node_path33 = __toESM(require("path"), 1);
7177
+ var import_types24 = require("@neat.is/types");
6916
7178
 
6917
7179
  // src/extract/infra/shared.ts
6918
7180
  init_cjs_shims();
6919
- var import_types22 = require("@neat.is/types");
7181
+ var import_types23 = require("@neat.is/types");
6920
7182
  function makeInfraNode(kind, name, provider = "self", extras) {
6921
7183
  return {
6922
- id: (0, import_types22.infraId)(kind, name),
6923
- type: import_types22.NodeType.InfraNode,
7184
+ id: (0, import_types23.infraId)(kind, name),
7185
+ type: import_types23.NodeType.InfraNode,
6924
7186
  name,
6925
7187
  provider,
6926
7188
  kind,
@@ -6964,8 +7226,8 @@ function emitPlatformResourceEdge(graph, anchorId, edgeType, kind, name, provide
6964
7226
  source: anchorId,
6965
7227
  target: node.id,
6966
7228
  type: edgeType,
6967
- provenance: import_types22.Provenance.EXTRACTED,
6968
- confidence: (0, import_types22.confidenceForExtracted)("structural"),
7229
+ provenance: import_types23.Provenance.EXTRACTED,
7230
+ confidence: (0, import_types23.confidenceForExtracted)("structural"),
6969
7231
  evidence: { file: evidenceFile, ...line !== void 0 ? { line } : {} }
6970
7232
  };
6971
7233
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -6982,7 +7244,7 @@ function dependsOnList(value) {
6982
7244
  }
6983
7245
  function serviceNameToServiceNode(name, services) {
6984
7246
  for (const s of services) {
6985
- if (s.node.name === name || import_node_path32.default.basename(s.dir) === name) return s.node.id;
7247
+ if (s.node.name === name || import_node_path33.default.basename(s.dir) === name) return s.node.id;
6986
7248
  }
6987
7249
  return null;
6988
7250
  }
@@ -6991,7 +7253,7 @@ async function addComposeInfra(graph, scanPath, services) {
6991
7253
  let edgesAdded = 0;
6992
7254
  let composePath = null;
6993
7255
  for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
6994
- const abs = import_node_path32.default.join(scanPath, name);
7256
+ const abs = import_node_path33.default.join(scanPath, name);
6995
7257
  if (await exists(abs)) {
6996
7258
  composePath = abs;
6997
7259
  break;
@@ -7004,13 +7266,13 @@ async function addComposeInfra(graph, scanPath, services) {
7004
7266
  } catch (err) {
7005
7267
  recordExtractionError(
7006
7268
  "infra docker-compose",
7007
- import_node_path32.default.relative(scanPath, composePath),
7269
+ import_node_path33.default.relative(scanPath, composePath),
7008
7270
  err
7009
7271
  );
7010
7272
  return { nodesAdded, edgesAdded };
7011
7273
  }
7012
7274
  if (!compose?.services) return { nodesAdded, edgesAdded };
7013
- const evidenceFile = import_node_path32.default.relative(scanPath, composePath).split(import_node_path32.default.sep).join("/");
7275
+ const evidenceFile = import_node_path33.default.relative(scanPath, composePath).split(import_node_path33.default.sep).join("/");
7014
7276
  const composeNameToNodeId = /* @__PURE__ */ new Map();
7015
7277
  for (const [composeName, svc] of Object.entries(compose.services)) {
7016
7278
  const matchedServiceId = serviceNameToServiceNode(composeName, services);
@@ -7032,15 +7294,15 @@ async function addComposeInfra(graph, scanPath, services) {
7032
7294
  for (const dep of dependsOnList(svc.depends_on)) {
7033
7295
  const targetId = composeNameToNodeId.get(dep);
7034
7296
  if (!targetId) continue;
7035
- const edgeId = (0, import_types4.extractedEdgeId)(sourceId, targetId, import_types23.EdgeType.DEPENDS_ON);
7297
+ const edgeId = (0, import_types4.extractedEdgeId)(sourceId, targetId, import_types24.EdgeType.DEPENDS_ON);
7036
7298
  if (graph.hasEdge(edgeId)) continue;
7037
7299
  const edge = {
7038
7300
  id: edgeId,
7039
7301
  source: sourceId,
7040
7302
  target: targetId,
7041
- type: import_types23.EdgeType.DEPENDS_ON,
7042
- provenance: import_types23.Provenance.EXTRACTED,
7043
- confidence: (0, import_types23.confidenceForExtracted)("structural"),
7303
+ type: import_types24.EdgeType.DEPENDS_ON,
7304
+ provenance: import_types24.Provenance.EXTRACTED,
7305
+ confidence: (0, import_types24.confidenceForExtracted)("structural"),
7044
7306
  evidence: { file: evidenceFile }
7045
7307
  };
7046
7308
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -7052,9 +7314,9 @@ async function addComposeInfra(graph, scanPath, services) {
7052
7314
 
7053
7315
  // src/extract/infra/dockerfile.ts
7054
7316
  init_cjs_shims();
7055
- var import_node_path33 = __toESM(require("path"), 1);
7317
+ var import_node_path34 = __toESM(require("path"), 1);
7056
7318
  var import_node_fs16 = require("fs");
7057
- var import_types24 = require("@neat.is/types");
7319
+ var import_types25 = require("@neat.is/types");
7058
7320
  function readDockerfile(content) {
7059
7321
  let image = null;
7060
7322
  const ports = [];
@@ -7083,7 +7345,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
7083
7345
  let nodesAdded = 0;
7084
7346
  let edgesAdded = 0;
7085
7347
  for (const service of services) {
7086
- const dockerfilePath = import_node_path33.default.join(service.dir, "Dockerfile");
7348
+ const dockerfilePath = import_node_path34.default.join(service.dir, "Dockerfile");
7087
7349
  if (!await exists(dockerfilePath)) continue;
7088
7350
  let content;
7089
7351
  try {
@@ -7091,7 +7353,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
7091
7353
  } catch (err) {
7092
7354
  recordExtractionError(
7093
7355
  "infra dockerfile",
7094
- import_node_path33.default.relative(scanPath, dockerfilePath),
7356
+ import_node_path34.default.relative(scanPath, dockerfilePath),
7095
7357
  err
7096
7358
  );
7097
7359
  continue;
@@ -7103,8 +7365,8 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
7103
7365
  graph.addNode(node.id, node);
7104
7366
  nodesAdded++;
7105
7367
  }
7106
- const relDockerfile = toPosix2(import_node_path33.default.relative(service.dir, dockerfilePath));
7107
- const evidenceFile = toPosix2(import_node_path33.default.relative(scanPath, dockerfilePath));
7368
+ const relDockerfile = toPosix2(import_node_path34.default.relative(service.dir, dockerfilePath));
7369
+ const evidenceFile = toPosix2(import_node_path34.default.relative(scanPath, dockerfilePath));
7108
7370
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
7109
7371
  graph,
7110
7372
  service.pkg.name,
@@ -7113,15 +7375,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
7113
7375
  );
7114
7376
  nodesAdded += fn;
7115
7377
  edgesAdded += fe;
7116
- const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, node.id, import_types24.EdgeType.RUNS_ON);
7378
+ const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, node.id, import_types25.EdgeType.RUNS_ON);
7117
7379
  if (!graph.hasEdge(edgeId)) {
7118
7380
  const edge = {
7119
7381
  id: edgeId,
7120
7382
  source: fileNodeId,
7121
7383
  target: node.id,
7122
- type: import_types24.EdgeType.RUNS_ON,
7123
- provenance: import_types24.Provenance.EXTRACTED,
7124
- confidence: (0, import_types24.confidenceForExtracted)("structural"),
7384
+ type: import_types25.EdgeType.RUNS_ON,
7385
+ provenance: import_types25.Provenance.EXTRACTED,
7386
+ confidence: (0, import_types25.confidenceForExtracted)("structural"),
7125
7387
  evidence: {
7126
7388
  file: evidenceFile,
7127
7389
  ...facts.entrypoint ? { snippet: facts.entrypoint.slice(0, 120) } : {}
@@ -7136,15 +7398,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
7136
7398
  graph.addNode(portNode.id, portNode);
7137
7399
  nodesAdded++;
7138
7400
  }
7139
- const portEdgeId = (0, import_types4.extractedEdgeId)(fileNodeId, portNode.id, import_types24.EdgeType.CONNECTS_TO);
7401
+ const portEdgeId = (0, import_types4.extractedEdgeId)(fileNodeId, portNode.id, import_types25.EdgeType.CONNECTS_TO);
7140
7402
  if (graph.hasEdge(portEdgeId)) continue;
7141
7403
  const portEdge = {
7142
7404
  id: portEdgeId,
7143
7405
  source: fileNodeId,
7144
7406
  target: portNode.id,
7145
- type: import_types24.EdgeType.CONNECTS_TO,
7146
- provenance: import_types24.Provenance.EXTRACTED,
7147
- confidence: (0, import_types24.confidenceForExtracted)("structural"),
7407
+ type: import_types25.EdgeType.CONNECTS_TO,
7408
+ provenance: import_types25.Provenance.EXTRACTED,
7409
+ confidence: (0, import_types25.confidenceForExtracted)("structural"),
7148
7410
  evidence: { file: evidenceFile, snippet: `EXPOSE ${port}` }
7149
7411
  };
7150
7412
  graph.addEdgeWithKey(portEdgeId, portEdge.source, portEdge.target, portEdge);
@@ -7157,8 +7419,8 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
7157
7419
  // src/extract/infra/terraform.ts
7158
7420
  init_cjs_shims();
7159
7421
  var import_node_fs17 = require("fs");
7160
- var import_node_path34 = __toESM(require("path"), 1);
7161
- var import_types25 = require("@neat.is/types");
7422
+ var import_node_path35 = __toESM(require("path"), 1);
7423
+ var import_types26 = require("@neat.is/types");
7162
7424
  var RESOURCE_RE = /resource\s+"(aws_[A-Za-z0-9_]+)"\s+"([A-Za-z0-9_-]+)"/g;
7163
7425
  var REFERENCE_RE = /(?<![\w.])(aws_[A-Za-z0-9_]+)\.([A-Za-z0-9_-]+)/g;
7164
7426
  async function walkTfFiles(start, depth = 0, max = 5) {
@@ -7168,11 +7430,11 @@ async function walkTfFiles(start, depth = 0, max = 5) {
7168
7430
  for (const entry of entries) {
7169
7431
  if (entry.isDirectory()) {
7170
7432
  if (IGNORED_DIRS.has(entry.name) || entry.name === ".terraform") continue;
7171
- const child = import_node_path34.default.join(start, entry.name);
7433
+ const child = import_node_path35.default.join(start, entry.name);
7172
7434
  if (await isPythonVenvDir(child)) continue;
7173
7435
  out.push(...await walkTfFiles(child, depth + 1, max));
7174
7436
  } else if (entry.isFile() && entry.name.endsWith(".tf")) {
7175
- out.push(import_node_path34.default.join(start, entry.name));
7437
+ out.push(import_node_path35.default.join(start, entry.name));
7176
7438
  }
7177
7439
  }
7178
7440
  return out;
@@ -7204,7 +7466,7 @@ async function addTerraformResources(graph, scanPath) {
7204
7466
  const files = await walkTfFiles(scanPath);
7205
7467
  for (const file of files) {
7206
7468
  const content = await import_node_fs17.promises.readFile(file, "utf8");
7207
- const evidenceFile = toPosix2(import_node_path34.default.relative(scanPath, file));
7469
+ const evidenceFile = toPosix2(import_node_path35.default.relative(scanPath, file));
7208
7470
  const resources = [];
7209
7471
  const byKey = /* @__PURE__ */ new Map();
7210
7472
  RESOURCE_RE.lastIndex = 0;
@@ -7239,16 +7501,16 @@ async function addTerraformResources(graph, scanPath) {
7239
7501
  if (!target) continue;
7240
7502
  if (seen.has(target.nodeId)) continue;
7241
7503
  seen.add(target.nodeId);
7242
- const edgeId = (0, import_types4.extractedEdgeId)(resource.nodeId, target.nodeId, import_types25.EdgeType.DEPENDS_ON);
7504
+ const edgeId = (0, import_types4.extractedEdgeId)(resource.nodeId, target.nodeId, import_types26.EdgeType.DEPENDS_ON);
7243
7505
  if (graph.hasEdge(edgeId)) continue;
7244
7506
  const line = lineAt2(content, resource.bodyOffset + ref.index);
7245
7507
  const edge = {
7246
7508
  id: edgeId,
7247
7509
  source: resource.nodeId,
7248
7510
  target: target.nodeId,
7249
- type: import_types25.EdgeType.DEPENDS_ON,
7250
- provenance: import_types25.Provenance.EXTRACTED,
7251
- confidence: (0, import_types25.confidenceForExtracted)("structural"),
7511
+ type: import_types26.EdgeType.DEPENDS_ON,
7512
+ provenance: import_types26.Provenance.EXTRACTED,
7513
+ confidence: (0, import_types26.confidenceForExtracted)("structural"),
7252
7514
  evidence: { file: evidenceFile, line, snippet: key }
7253
7515
  };
7254
7516
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -7262,7 +7524,7 @@ async function addTerraformResources(graph, scanPath) {
7262
7524
  // src/extract/infra/k8s.ts
7263
7525
  init_cjs_shims();
7264
7526
  var import_node_fs18 = require("fs");
7265
- var import_node_path35 = __toESM(require("path"), 1);
7527
+ var import_node_path36 = __toESM(require("path"), 1);
7266
7528
  var import_yaml3 = require("yaml");
7267
7529
  var K8S_KIND_TO_INFRA_KIND = {
7268
7530
  Service: "k8s-service",
@@ -7280,11 +7542,11 @@ async function walkYamlFiles2(start, depth = 0, max = 5) {
7280
7542
  for (const entry of entries) {
7281
7543
  if (entry.isDirectory()) {
7282
7544
  if (IGNORED_DIRS.has(entry.name)) continue;
7283
- const child = import_node_path35.default.join(start, entry.name);
7545
+ const child = import_node_path36.default.join(start, entry.name);
7284
7546
  if (await isPythonVenvDir(child)) continue;
7285
7547
  out.push(...await walkYamlFiles2(child, depth + 1, max));
7286
- } else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path35.default.extname(entry.name))) {
7287
- out.push(import_node_path35.default.join(start, entry.name));
7548
+ } else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path36.default.extname(entry.name))) {
7549
+ out.push(import_node_path36.default.join(start, entry.name));
7288
7550
  }
7289
7551
  }
7290
7552
  return out;
@@ -7318,13 +7580,13 @@ async function addK8sResources(graph, scanPath) {
7318
7580
  // src/extract/infra/cloudflare.ts
7319
7581
  init_cjs_shims();
7320
7582
  var import_node_fs19 = require("fs");
7321
- var import_node_path36 = __toESM(require("path"), 1);
7583
+ var import_node_path37 = __toESM(require("path"), 1);
7322
7584
  var import_smol_toml2 = require("smol-toml");
7323
- var import_types26 = require("@neat.is/types");
7585
+ var import_types27 = require("@neat.is/types");
7324
7586
  var WRANGLER_FILENAMES = ["wrangler.toml", "wrangler.jsonc", "wrangler.json"];
7325
7587
  async function readWranglerConfig(dir) {
7326
7588
  for (const filename of WRANGLER_FILENAMES) {
7327
- const abs = import_node_path36.default.join(dir, filename);
7589
+ const abs = import_node_path37.default.join(dir, filename);
7328
7590
  if (!await exists(abs)) continue;
7329
7591
  const raw = await import_node_fs19.promises.readFile(abs, "utf8");
7330
7592
  const config = filename === "wrangler.toml" ? (0, import_smol_toml2.parse)(raw) : JSON.parse(maskCommentsInSource(raw));
@@ -7368,8 +7630,8 @@ function addResourceEdge(graph, anchorId, edgeType, kind, name, evidenceFile, li
7368
7630
  source: anchorId,
7369
7631
  target: node.id,
7370
7632
  type: edgeType,
7371
- provenance: import_types26.Provenance.EXTRACTED,
7372
- confidence: (0, import_types26.confidenceForExtracted)("structural"),
7633
+ provenance: import_types27.Provenance.EXTRACTED,
7634
+ confidence: (0, import_types27.confidenceForExtracted)("structural"),
7373
7635
  evidence: { file: evidenceFile, ...line !== void 0 ? { line } : {} }
7374
7636
  };
7375
7637
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -7387,11 +7649,11 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7387
7649
  try {
7388
7650
  read = await readWranglerConfig(service.dir);
7389
7651
  } catch (err) {
7390
- recordExtractionError("infra cloudflare", import_node_path36.default.relative(scanPath, service.dir), err);
7652
+ recordExtractionError("infra cloudflare", import_node_path37.default.relative(scanPath, service.dir), err);
7391
7653
  continue;
7392
7654
  }
7393
7655
  if (!read || !read.config.name) continue;
7394
- const evidenceFile = toPosix2(import_node_path36.default.relative(scanPath, import_node_path36.default.join(service.dir, read.relFile)));
7656
+ const evidenceFile = toPosix2(import_node_path37.default.relative(scanPath, import_node_path37.default.join(service.dir, read.relFile)));
7395
7657
  discovered.push({ service, config: read.config, relFile: read.relFile, raw: read.raw, evidenceFile });
7396
7658
  }
7397
7659
  for (const worker of discovered) {
@@ -7403,7 +7665,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7403
7665
  }
7404
7666
  let anchorId = service.node.id;
7405
7667
  if (config.main) {
7406
- const entryRelPath = toPosix2(import_node_path36.default.normalize(config.main));
7668
+ const entryRelPath = toPosix2(import_node_path37.default.normalize(config.main));
7407
7669
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
7408
7670
  graph,
7409
7671
  service.pkg.name,
@@ -7430,15 +7692,15 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7430
7692
  nodesAdded++;
7431
7693
  }
7432
7694
  if (runtimeNode.id !== anchorId) {
7433
- const runsOnId = (0, import_types4.extractedEdgeId)(anchorId, runtimeNode.id, import_types26.EdgeType.RUNS_ON);
7695
+ const runsOnId = (0, import_types4.extractedEdgeId)(anchorId, runtimeNode.id, import_types27.EdgeType.RUNS_ON);
7434
7696
  if (!graph.hasEdge(runsOnId)) {
7435
7697
  const edge = {
7436
7698
  id: runsOnId,
7437
7699
  source: anchorId,
7438
7700
  target: runtimeNode.id,
7439
- type: import_types26.EdgeType.RUNS_ON,
7440
- provenance: import_types26.Provenance.EXTRACTED,
7441
- confidence: (0, import_types26.confidenceForExtracted)("structural"),
7701
+ type: import_types27.EdgeType.RUNS_ON,
7702
+ provenance: import_types27.Provenance.EXTRACTED,
7703
+ confidence: (0, import_types27.confidenceForExtracted)("structural"),
7442
7704
  evidence: {
7443
7705
  file: evidenceFile,
7444
7706
  ...config.compatibility_date ? { snippet: `compatibility_date = ${config.compatibility_date}`.slice(0, 120) } : {}
@@ -7452,7 +7714,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7452
7714
  const result = addResourceEdge(
7453
7715
  graph,
7454
7716
  anchorId,
7455
- import_types26.EdgeType.CONNECTS_TO,
7717
+ import_types27.EdgeType.CONNECTS_TO,
7456
7718
  "cloudflare-route",
7457
7719
  route,
7458
7720
  evidenceFile,
@@ -7476,7 +7738,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7476
7738
  const result = addResourceEdge(
7477
7739
  graph,
7478
7740
  anchorId,
7479
- import_types26.EdgeType.DEPENDS_ON,
7741
+ import_types27.EdgeType.DEPENDS_ON,
7480
7742
  group.kind,
7481
7743
  name,
7482
7744
  evidenceFile,
@@ -7490,7 +7752,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7490
7752
  const result = addResourceEdge(
7491
7753
  graph,
7492
7754
  anchorId,
7493
- import_types26.EdgeType.DEPENDS_ON,
7755
+ import_types27.EdgeType.DEPENDS_ON,
7494
7756
  "cloudflare-cron",
7495
7757
  cron,
7496
7758
  evidenceFile,
@@ -7503,7 +7765,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7503
7765
  const result = addResourceEdge(
7504
7766
  graph,
7505
7767
  anchorId,
7506
- import_types26.EdgeType.DEPENDS_ON,
7768
+ import_types27.EdgeType.DEPENDS_ON,
7507
7769
  "cloudflare-env-var",
7508
7770
  varName,
7509
7771
  evidenceFile,
@@ -7516,15 +7778,15 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7516
7778
  if (!svc.service) continue;
7517
7779
  const target = workerIndex.get(svc.service);
7518
7780
  if (target && target.anchorId !== anchorId) {
7519
- const edgeId = (0, import_types4.extractedEdgeId)(anchorId, target.anchorId, import_types26.EdgeType.CALLS);
7781
+ const edgeId = (0, import_types4.extractedEdgeId)(anchorId, target.anchorId, import_types27.EdgeType.CALLS);
7520
7782
  if (!graph.hasEdge(edgeId)) {
7521
7783
  const edge = {
7522
7784
  id: edgeId,
7523
7785
  source: anchorId,
7524
7786
  target: target.anchorId,
7525
- type: import_types26.EdgeType.CALLS,
7526
- provenance: import_types26.Provenance.EXTRACTED,
7527
- confidence: (0, import_types26.confidenceForExtracted)("structural"),
7787
+ type: import_types27.EdgeType.CALLS,
7788
+ provenance: import_types27.Provenance.EXTRACTED,
7789
+ confidence: (0, import_types27.confidenceForExtracted)("structural"),
7528
7790
  evidence: { file: evidenceFile, line: lineContaining2(raw, svc.service) }
7529
7791
  };
7530
7792
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -7535,7 +7797,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7535
7797
  const result = addResourceEdge(
7536
7798
  graph,
7537
7799
  anchorId,
7538
- import_types26.EdgeType.DEPENDS_ON,
7800
+ import_types27.EdgeType.DEPENDS_ON,
7539
7801
  "cloudflare-service-binding",
7540
7802
  svc.service,
7541
7803
  evidenceFile,
@@ -7551,12 +7813,12 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7551
7813
  // src/extract/infra/vercel.ts
7552
7814
  init_cjs_shims();
7553
7815
  var import_node_fs20 = require("fs");
7554
- var import_node_path37 = __toESM(require("path"), 1);
7555
- var import_types27 = require("@neat.is/types");
7816
+ var import_node_path38 = __toESM(require("path"), 1);
7817
+ var import_types28 = require("@neat.is/types");
7556
7818
  var VERCEL_CONFIG_FILENAMES = ["vercel.json", "vercel.jsonc"];
7557
7819
  async function readVercelConfig(dir) {
7558
7820
  for (const filename of VERCEL_CONFIG_FILENAMES) {
7559
- const abs = import_node_path37.default.join(dir, filename);
7821
+ const abs = import_node_path38.default.join(dir, filename);
7560
7822
  if (!await exists(abs)) continue;
7561
7823
  const raw = await import_node_fs20.promises.readFile(abs, "utf8");
7562
7824
  const config = JSON.parse(maskCommentsInSource(raw));
@@ -7565,7 +7827,7 @@ async function readVercelConfig(dir) {
7565
7827
  return null;
7566
7828
  }
7567
7829
  async function readLinkedProjectName(dir) {
7568
- const abs = import_node_path37.default.join(dir, ".vercel", "project.json");
7830
+ const abs = import_node_path38.default.join(dir, ".vercel", "project.json");
7569
7831
  if (!await exists(abs)) return void 0;
7570
7832
  const parsed = JSON.parse(await import_node_fs20.promises.readFile(abs, "utf8"));
7571
7833
  return typeof parsed.projectName === "string" ? parsed.projectName : void 0;
@@ -7583,7 +7845,7 @@ async function addVercelServices(graph, services, scanPath) {
7583
7845
  read = await readVercelConfig(service.dir);
7584
7846
  projectName = await readLinkedProjectName(service.dir);
7585
7847
  } catch (err) {
7586
- recordExtractionError("infra vercel", import_node_path37.default.relative(scanPath, service.dir), err);
7848
+ recordExtractionError("infra vercel", import_node_path38.default.relative(scanPath, service.dir), err);
7587
7849
  continue;
7588
7850
  }
7589
7851
  if (!read && !projectName) continue;
@@ -7599,7 +7861,7 @@ async function addVercelServices(graph, services, scanPath) {
7599
7861
  const anchorId = service.node.id;
7600
7862
  if (!read) continue;
7601
7863
  const { config, relFile, raw } = read;
7602
- const evidenceFile = toPosix2(import_node_path37.default.relative(scanPath, import_node_path37.default.join(service.dir, relFile)));
7864
+ const evidenceFile = toPosix2(import_node_path38.default.relative(scanPath, import_node_path38.default.join(service.dir, relFile)));
7603
7865
  const add = (edgeType, kind, name) => {
7604
7866
  if (!name) return;
7605
7867
  const result = emitPlatformResourceEdge(
@@ -7615,12 +7877,12 @@ async function addVercelServices(graph, services, scanPath) {
7615
7877
  nodesAdded += result.nodesAdded;
7616
7878
  edgesAdded += result.edgesAdded;
7617
7879
  };
7618
- add(import_types27.EdgeType.RUNS_ON, "vercel", "vercel");
7619
- for (const cron of config.crons ?? []) add(import_types27.EdgeType.DEPENDS_ON, "vercel-cron", cron.path ?? cron.schedule);
7620
- for (const varName of Object.keys(config.env ?? {})) add(import_types27.EdgeType.DEPENDS_ON, "vercel-env-var", varName);
7621
- for (const varName of Object.keys(config.build?.env ?? {})) add(import_types27.EdgeType.DEPENDS_ON, "vercel-env-var", varName);
7880
+ add(import_types28.EdgeType.RUNS_ON, "vercel", "vercel");
7881
+ for (const cron of config.crons ?? []) add(import_types28.EdgeType.DEPENDS_ON, "vercel-cron", cron.path ?? cron.schedule);
7882
+ for (const varName of Object.keys(config.env ?? {})) add(import_types28.EdgeType.DEPENDS_ON, "vercel-env-var", varName);
7883
+ for (const varName of Object.keys(config.build?.env ?? {})) add(import_types28.EdgeType.DEPENDS_ON, "vercel-env-var", varName);
7622
7884
  for (const route of [...config.rewrites ?? [], ...config.redirects ?? [], ...config.routes ?? []]) {
7623
- add(import_types27.EdgeType.CONNECTS_TO, "vercel-route", routeSource(route));
7885
+ add(import_types28.EdgeType.CONNECTS_TO, "vercel-route", routeSource(route));
7624
7886
  }
7625
7887
  }
7626
7888
  return { nodesAdded, edgesAdded };
@@ -7629,13 +7891,13 @@ async function addVercelServices(graph, services, scanPath) {
7629
7891
  // src/extract/infra/railway.ts
7630
7892
  init_cjs_shims();
7631
7893
  var import_node_fs21 = require("fs");
7632
- var import_node_path38 = __toESM(require("path"), 1);
7894
+ var import_node_path39 = __toESM(require("path"), 1);
7633
7895
  var import_smol_toml3 = require("smol-toml");
7634
- var import_types28 = require("@neat.is/types");
7896
+ var import_types29 = require("@neat.is/types");
7635
7897
  var RAILWAY_FILENAMES = ["railway.toml", "railway.json", "railway.jsonc"];
7636
7898
  async function readRailwayConfig(dir) {
7637
7899
  for (const filename of RAILWAY_FILENAMES) {
7638
- const abs = import_node_path38.default.join(dir, filename);
7900
+ const abs = import_node_path39.default.join(dir, filename);
7639
7901
  if (!await exists(abs)) continue;
7640
7902
  const raw = await import_node_fs21.promises.readFile(abs, "utf8");
7641
7903
  const config = filename === "railway.toml" ? (0, import_smol_toml3.parse)(raw) : JSON.parse(maskCommentsInSource(raw));
@@ -7651,7 +7913,7 @@ async function addRailwayServices(graph, services, scanPath) {
7651
7913
  try {
7652
7914
  read = await readRailwayConfig(service.dir);
7653
7915
  } catch (err) {
7654
- recordExtractionError("infra railway", import_node_path38.default.relative(scanPath, service.dir), err);
7916
+ recordExtractionError("infra railway", import_node_path39.default.relative(scanPath, service.dir), err);
7655
7917
  continue;
7656
7918
  }
7657
7919
  if (!read) continue;
@@ -7661,7 +7923,7 @@ async function addRailwayServices(graph, services, scanPath) {
7661
7923
  }
7662
7924
  const anchorId = service.node.id;
7663
7925
  const { config, relFile, raw } = read;
7664
- const evidenceFile = toPosix2(import_node_path38.default.relative(scanPath, import_node_path38.default.join(service.dir, relFile)));
7926
+ const evidenceFile = toPosix2(import_node_path39.default.relative(scanPath, import_node_path39.default.join(service.dir, relFile)));
7665
7927
  const add = (edgeType, kind, name) => {
7666
7928
  if (!name) return;
7667
7929
  const result = emitPlatformResourceEdge(
@@ -7677,9 +7939,9 @@ async function addRailwayServices(graph, services, scanPath) {
7677
7939
  nodesAdded += result.nodesAdded;
7678
7940
  edgesAdded += result.edgesAdded;
7679
7941
  };
7680
- add(import_types28.EdgeType.RUNS_ON, "railway", "railway");
7681
- add(import_types28.EdgeType.CONNECTS_TO, "railway-route", config.deploy?.healthcheckPath);
7682
- add(import_types28.EdgeType.DEPENDS_ON, "railway-cron", config.deploy?.cronSchedule);
7942
+ add(import_types29.EdgeType.RUNS_ON, "railway", "railway");
7943
+ add(import_types29.EdgeType.CONNECTS_TO, "railway-route", config.deploy?.healthcheckPath);
7944
+ add(import_types29.EdgeType.DEPENDS_ON, "railway-cron", config.deploy?.cronSchedule);
7683
7945
  }
7684
7946
  return { nodesAdded, edgesAdded };
7685
7947
  }
@@ -7687,12 +7949,12 @@ async function addRailwayServices(graph, services, scanPath) {
7687
7949
  // src/extract/infra/supabase.ts
7688
7950
  init_cjs_shims();
7689
7951
  var import_node_fs22 = require("fs");
7690
- var import_node_path39 = __toESM(require("path"), 1);
7952
+ var import_node_path40 = __toESM(require("path"), 1);
7691
7953
  var import_smol_toml4 = require("smol-toml");
7692
- var import_types29 = require("@neat.is/types");
7954
+ var import_types30 = require("@neat.is/types");
7693
7955
  async function readSupabaseConfig(dir) {
7694
- const relFile = import_node_path39.default.join("supabase", "config.toml");
7695
- const abs = import_node_path39.default.join(dir, relFile);
7956
+ const relFile = import_node_path40.default.join("supabase", "config.toml");
7957
+ const abs = import_node_path40.default.join(dir, relFile);
7696
7958
  if (!await exists(abs)) return null;
7697
7959
  const raw = await import_node_fs22.promises.readFile(abs, "utf8");
7698
7960
  const config = (0, import_smol_toml4.parse)(raw);
@@ -7706,7 +7968,7 @@ async function addSupabaseProjects(graph, services, scanPath) {
7706
7968
  try {
7707
7969
  read = await readSupabaseConfig(service.dir);
7708
7970
  } catch (err) {
7709
- recordExtractionError("infra supabase", import_node_path39.default.relative(scanPath, service.dir), err);
7971
+ recordExtractionError("infra supabase", import_node_path40.default.relative(scanPath, service.dir), err);
7710
7972
  continue;
7711
7973
  }
7712
7974
  if (!read) continue;
@@ -7721,7 +7983,7 @@ async function addSupabaseProjects(graph, services, scanPath) {
7721
7983
  });
7722
7984
  }
7723
7985
  const anchorId = service.node.id;
7724
- const evidenceFile = toPosix2(import_node_path39.default.relative(scanPath, import_node_path39.default.join(service.dir, relFile)));
7986
+ const evidenceFile = toPosix2(import_node_path40.default.relative(scanPath, import_node_path40.default.join(service.dir, relFile)));
7725
7987
  const add = (edgeType, kind, name) => {
7726
7988
  if (!name) return;
7727
7989
  const result = emitPlatformResourceEdge(
@@ -7737,10 +7999,10 @@ async function addSupabaseProjects(graph, services, scanPath) {
7737
7999
  nodesAdded += result.nodesAdded;
7738
8000
  edgesAdded += result.edgesAdded;
7739
8001
  };
7740
- add(import_types29.EdgeType.RUNS_ON, "supabase", "supabase");
7741
- for (const fn of Object.keys(config.functions ?? {})) add(import_types29.EdgeType.DEPENDS_ON, "supabase-function", fn);
7742
- if (config.storage) add(import_types29.EdgeType.DEPENDS_ON, "supabase-storage", "storage");
7743
- if (config.auth) add(import_types29.EdgeType.DEPENDS_ON, "supabase-auth", "auth");
8002
+ add(import_types30.EdgeType.RUNS_ON, "supabase", "supabase");
8003
+ for (const fn of Object.keys(config.functions ?? {})) add(import_types30.EdgeType.DEPENDS_ON, "supabase-function", fn);
8004
+ if (config.storage) add(import_types30.EdgeType.DEPENDS_ON, "supabase-storage", "storage");
8005
+ if (config.auth) add(import_types30.EdgeType.DEPENDS_ON, "supabase-auth", "auth");
7744
8006
  }
7745
8007
  return { nodesAdded, edgesAdded };
7746
8008
  }
@@ -7762,17 +8024,17 @@ async function addInfra(graph, scanPath, services) {
7762
8024
  }
7763
8025
 
7764
8026
  // src/extract/index.ts
7765
- var import_node_path41 = __toESM(require("path"), 1);
8027
+ var import_node_path42 = __toESM(require("path"), 1);
7766
8028
 
7767
8029
  // src/extract/retire.ts
7768
8030
  init_cjs_shims();
7769
8031
  var import_node_fs23 = require("fs");
7770
- var import_node_path40 = __toESM(require("path"), 1);
7771
- var import_types30 = require("@neat.is/types");
8032
+ var import_node_path41 = __toESM(require("path"), 1);
8033
+ var import_types31 = require("@neat.is/types");
7772
8034
  function dropOrphanedFileNodes(graph) {
7773
8035
  const orphans = [];
7774
8036
  graph.forEachNode((id, attrs) => {
7775
- if (attrs.type !== import_types30.NodeType.FileNode) return;
8037
+ if (attrs.type !== import_types31.NodeType.FileNode) return;
7776
8038
  if (graph.inboundEdges(id).length === 0 && graph.outboundEdges(id).length === 0) {
7777
8039
  orphans.push(id);
7778
8040
  }
@@ -7785,14 +8047,14 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
7785
8047
  const bases = [scanPath, ...serviceDirs];
7786
8048
  graph.forEachEdge((id, attrs) => {
7787
8049
  const edge = attrs;
7788
- if (edge.provenance !== import_types30.Provenance.EXTRACTED) return;
8050
+ if (edge.provenance !== import_types31.Provenance.EXTRACTED) return;
7789
8051
  const evidenceFile = edge.evidence?.file;
7790
8052
  if (!evidenceFile) return;
7791
- if (import_node_path40.default.isAbsolute(evidenceFile)) {
8053
+ if (import_node_path41.default.isAbsolute(evidenceFile)) {
7792
8054
  if (!(0, import_node_fs23.existsSync)(evidenceFile)) toDrop.push(id);
7793
8055
  return;
7794
8056
  }
7795
- const found = bases.some((base) => (0, import_node_fs23.existsSync)(import_node_path40.default.join(base, evidenceFile)));
8057
+ const found = bases.some((base) => (0, import_node_fs23.existsSync)(import_node_path41.default.join(base, evidenceFile)));
7796
8058
  if (!found) toDrop.push(id);
7797
8059
  });
7798
8060
  for (const id of toDrop) graph.dropEdge(id);
@@ -7834,7 +8096,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
7834
8096
  }
7835
8097
  const droppedEntries = drainDroppedExtracted();
7836
8098
  if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
7837
- const rejectedPath = import_node_path41.default.join(import_node_path41.default.dirname(opts.errorsPath), "rejected.ndjson");
8099
+ const rejectedPath = import_node_path42.default.join(import_node_path42.default.dirname(opts.errorsPath), "rejected.ndjson");
7838
8100
  try {
7839
8101
  await writeRejectedExtracted(droppedEntries, rejectedPath);
7840
8102
  } catch (err) {
@@ -7869,8 +8131,8 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
7869
8131
  // src/persist.ts
7870
8132
  init_cjs_shims();
7871
8133
  var import_node_fs24 = require("fs");
7872
- var import_node_path42 = __toESM(require("path"), 1);
7873
- var import_types31 = require("@neat.is/types");
8134
+ var import_node_path43 = __toESM(require("path"), 1);
8135
+ var import_types32 = require("@neat.is/types");
7874
8136
  var SCHEMA_VERSION = 4;
7875
8137
  function migrateV1ToV2(payload) {
7876
8138
  const nodes = payload.graph.nodes;
@@ -7892,12 +8154,12 @@ function migrateV2ToV3(payload) {
7892
8154
  for (const edge of edges) {
7893
8155
  const attrs = edge.attributes;
7894
8156
  if (!attrs || attrs.provenance !== "FRONTIER") continue;
7895
- attrs.provenance = import_types31.Provenance.OBSERVED;
8157
+ attrs.provenance = import_types32.Provenance.OBSERVED;
7896
8158
  const type = typeof attrs.type === "string" ? attrs.type : void 0;
7897
8159
  const source = typeof attrs.source === "string" ? attrs.source : void 0;
7898
8160
  const target = typeof attrs.target === "string" ? attrs.target : void 0;
7899
8161
  if (type && source && target) {
7900
- const newId = (0, import_types31.observedEdgeId)(source, target, type);
8162
+ const newId = (0, import_types32.observedEdgeId)(source, target, type);
7901
8163
  attrs.id = newId;
7902
8164
  if (edge.key) edge.key = newId;
7903
8165
  }
@@ -7906,7 +8168,7 @@ function migrateV2ToV3(payload) {
7906
8168
  return { ...payload, schemaVersion: 3 };
7907
8169
  }
7908
8170
  async function ensureDir(filePath) {
7909
- await import_node_fs24.promises.mkdir(import_node_path42.default.dirname(filePath), { recursive: true });
8171
+ await import_node_fs24.promises.mkdir(import_node_path43.default.dirname(filePath), { recursive: true });
7910
8172
  }
7911
8173
  async function saveGraphToDisk(graph, outPath) {
7912
8174
  await ensureDir(outPath);
@@ -7989,19 +8251,19 @@ function startPersistLoop(graph, outPath, opts = {}) {
7989
8251
  init_cjs_shims();
7990
8252
  var import_fastify = __toESM(require("fastify"), 1);
7991
8253
  var import_cors = __toESM(require("@fastify/cors"), 1);
7992
- var import_types34 = require("@neat.is/types");
8254
+ var import_types35 = require("@neat.is/types");
7993
8255
 
7994
8256
  // src/extend/index.ts
7995
8257
  init_cjs_shims();
7996
8258
  var import_node_fs26 = require("fs");
7997
- var import_node_path44 = __toESM(require("path"), 1);
8259
+ var import_node_path45 = __toESM(require("path"), 1);
7998
8260
  var import_node_os2 = __toESM(require("os"), 1);
7999
8261
  var import_instrumentation_registry = require("@neat.is/instrumentation-registry");
8000
8262
 
8001
8263
  // src/installers/package-manager.ts
8002
8264
  init_cjs_shims();
8003
8265
  var import_node_fs25 = require("fs");
8004
- var import_node_path43 = __toESM(require("path"), 1);
8266
+ var import_node_path44 = __toESM(require("path"), 1);
8005
8267
  var import_node_child_process = require("child_process");
8006
8268
  var LOCKFILE_PRIORITY = [
8007
8269
  { lockfile: "bun.lockb", pm: "bun", args: ["install", "--no-summary"] },
@@ -8023,22 +8285,22 @@ async function exists2(p) {
8023
8285
  }
8024
8286
  }
8025
8287
  async function detectPackageManager(serviceDir) {
8026
- let dir = import_node_path43.default.resolve(serviceDir);
8288
+ let dir = import_node_path44.default.resolve(serviceDir);
8027
8289
  const stops = /* @__PURE__ */ new Set();
8028
8290
  for (let i = 0; i < 64; i++) {
8029
8291
  if (stops.has(dir)) break;
8030
8292
  stops.add(dir);
8031
8293
  for (const candidate of LOCKFILE_PRIORITY) {
8032
- const lockPath = import_node_path43.default.join(dir, candidate.lockfile);
8294
+ const lockPath = import_node_path44.default.join(dir, candidate.lockfile);
8033
8295
  if (await exists2(lockPath)) {
8034
8296
  return { pm: candidate.pm, cwd: dir, args: [...candidate.args] };
8035
8297
  }
8036
8298
  }
8037
- const parent = import_node_path43.default.dirname(dir);
8299
+ const parent = import_node_path44.default.dirname(dir);
8038
8300
  if (parent === dir) break;
8039
8301
  dir = parent;
8040
8302
  }
8041
- return { pm: "npm", cwd: import_node_path43.default.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
8303
+ return { pm: "npm", cwd: import_node_path44.default.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
8042
8304
  }
8043
8305
  async function runPackageManagerInstall(cmd) {
8044
8306
  return new Promise((resolve) => {
@@ -8087,7 +8349,7 @@ async function fileExists2(p) {
8087
8349
  }
8088
8350
  }
8089
8351
  async function readPackageJson(scanPath) {
8090
- const pkgPath = import_node_path44.default.join(scanPath, "package.json");
8352
+ const pkgPath = import_node_path45.default.join(scanPath, "package.json");
8091
8353
  const raw = await import_node_fs26.promises.readFile(pkgPath, "utf8");
8092
8354
  return JSON.parse(raw);
8093
8355
  }
@@ -8101,27 +8363,27 @@ var HOOK_WALK_SKIP_DIRS = /* @__PURE__ */ new Set([
8101
8363
  ]);
8102
8364
  async function findHookFiles(scanPath) {
8103
8365
  const found = [];
8104
- const walk3 = async (dir) => {
8366
+ const walk4 = async (dir) => {
8105
8367
  const entries = await import_node_fs26.promises.readdir(dir, { withFileTypes: true }).catch(() => []);
8106
8368
  for (const entry of entries) {
8107
8369
  if (entry.isDirectory()) {
8108
8370
  if (entry.name.startsWith(".") || HOOK_WALK_SKIP_DIRS.has(entry.name)) continue;
8109
- await walk3(import_node_path44.default.join(dir, entry.name));
8371
+ await walk4(import_node_path45.default.join(dir, entry.name));
8110
8372
  } else if (entry.isFile()) {
8111
8373
  if ((entry.name.startsWith("instrumentation") || entry.name.startsWith("otel-init")) && /\.(ts|js|cjs|mjs)$/.test(entry.name)) {
8112
- const rel = import_node_path44.default.relative(scanPath, import_node_path44.default.join(dir, entry.name));
8113
- found.push(rel.split(import_node_path44.default.sep).join("/"));
8374
+ const rel = import_node_path45.default.relative(scanPath, import_node_path45.default.join(dir, entry.name));
8375
+ found.push(rel.split(import_node_path45.default.sep).join("/"));
8114
8376
  }
8115
8377
  }
8116
8378
  }
8117
8379
  };
8118
- await walk3(scanPath);
8380
+ await walk4(scanPath);
8119
8381
  return found.sort();
8120
8382
  }
8121
8383
  async function pickPrimaryHookFile(scanPath, hookFiles, snippet2) {
8122
8384
  let fallback = null;
8123
8385
  for (const file of hookFiles) {
8124
- const content = await import_node_fs26.promises.readFile(import_node_path44.default.join(scanPath, file), "utf8");
8386
+ const content = await import_node_fs26.promises.readFile(import_node_path45.default.join(scanPath, file), "utf8");
8125
8387
  const patched = splicedContent(content, snippet2);
8126
8388
  if (patched !== null) return { file, content, patched };
8127
8389
  if (fallback === null) fallback = { file, content };
@@ -8129,11 +8391,11 @@ async function pickPrimaryHookFile(scanPath, hookFiles, snippet2) {
8129
8391
  return { file: fallback.file, content: fallback.content, patched: null };
8130
8392
  }
8131
8393
  function extendLogPath() {
8132
- return process.env.NEAT_EXTEND_LOG ?? import_node_path44.default.join(import_node_os2.default.homedir(), ".neat", "extend-log.ndjson");
8394
+ return process.env.NEAT_EXTEND_LOG ?? import_node_path45.default.join(import_node_os2.default.homedir(), ".neat", "extend-log.ndjson");
8133
8395
  }
8134
8396
  async function appendExtendLog(entry) {
8135
8397
  const logPath = extendLogPath();
8136
- await import_node_fs26.promises.mkdir(import_node_path44.default.dirname(logPath), { recursive: true });
8398
+ await import_node_fs26.promises.mkdir(import_node_path45.default.dirname(logPath), { recursive: true });
8137
8399
  await import_node_fs26.promises.appendFile(logPath, JSON.stringify(entry) + "\n", "utf8");
8138
8400
  }
8139
8401
  function splicedContent(fileContent, snippet2) {
@@ -8192,7 +8454,7 @@ function lookupInstrumentation(library, installedVersion) {
8192
8454
  }
8193
8455
  async function describeProjectInstrumentation(ctx) {
8194
8456
  const hookFiles = await findHookFiles(ctx.scanPath);
8195
- const envNeat = await fileExists2(import_node_path44.default.join(ctx.scanPath, ".env.neat"));
8457
+ const envNeat = await fileExists2(import_node_path45.default.join(ctx.scanPath, ".env.neat"));
8196
8458
  const registryInstrPackages = new Set(
8197
8459
  (0, import_instrumentation_registry.list)().map((e) => e.instrumentation_package).filter((p) => !!p)
8198
8460
  );
@@ -8214,7 +8476,7 @@ async function applyExtension(ctx, args, options) {
8214
8476
  );
8215
8477
  }
8216
8478
  for (const file of hookFiles) {
8217
- const content = await import_node_fs26.promises.readFile(import_node_path44.default.join(ctx.scanPath, file), "utf8");
8479
+ const content = await import_node_fs26.promises.readFile(import_node_path45.default.join(ctx.scanPath, file), "utf8");
8218
8480
  if (content.includes(args.registration_snippet)) {
8219
8481
  return { library: args.library, filesTouched: [], depsAdded: [], installOutput: "", alreadyApplied: true };
8220
8482
  }
@@ -8226,10 +8488,10 @@ async function applyExtension(ctx, args, options) {
8226
8488
  );
8227
8489
  }
8228
8490
  const primaryFile = primary.file;
8229
- const primaryPath = import_node_path44.default.join(ctx.scanPath, primaryFile);
8491
+ const primaryPath = import_node_path45.default.join(ctx.scanPath, primaryFile);
8230
8492
  const filesTouched = [];
8231
8493
  const depsAdded = [];
8232
- const pkgPath = import_node_path44.default.join(ctx.scanPath, "package.json");
8494
+ const pkgPath = import_node_path45.default.join(ctx.scanPath, "package.json");
8233
8495
  const pkg = await readPackageJson(ctx.scanPath);
8234
8496
  if (!(pkg.dependencies ?? {})[args.instrumentation_package]) {
8235
8497
  pkg.dependencies = { ...pkg.dependencies ?? {}, [args.instrumentation_package]: args.version };
@@ -8268,7 +8530,7 @@ async function dryRunExtension(ctx, args) {
8268
8530
  };
8269
8531
  }
8270
8532
  for (const file of hookFiles) {
8271
- const content = await import_node_fs26.promises.readFile(import_node_path44.default.join(ctx.scanPath, file), "utf8");
8533
+ const content = await import_node_fs26.promises.readFile(import_node_path45.default.join(ctx.scanPath, file), "utf8");
8272
8534
  if (content.includes(args.registration_snippet)) {
8273
8535
  return {
8274
8536
  library: args.library,
@@ -8309,7 +8571,7 @@ async function rollbackExtension(ctx, args) {
8309
8571
  if (!match) {
8310
8572
  return { undone: false, message: "no apply found for library" };
8311
8573
  }
8312
- const pkgPath = import_node_path44.default.join(ctx.scanPath, "package.json");
8574
+ const pkgPath = import_node_path45.default.join(ctx.scanPath, "package.json");
8313
8575
  if (await fileExists2(pkgPath)) {
8314
8576
  const pkg = await readPackageJson(ctx.scanPath);
8315
8577
  if (pkg.dependencies?.[match.instrumentation_package]) {
@@ -8320,7 +8582,7 @@ async function rollbackExtension(ctx, args) {
8320
8582
  }
8321
8583
  const hookFiles = await findHookFiles(ctx.scanPath);
8322
8584
  for (const file of hookFiles) {
8323
- const filePath = import_node_path44.default.join(ctx.scanPath, file);
8585
+ const filePath = import_node_path45.default.join(ctx.scanPath, file);
8324
8586
  const content = await import_node_fs26.promises.readFile(filePath, "utf8");
8325
8587
  if (content.includes(match.registration_snippet)) {
8326
8588
  const filtered = content.split("\n").filter((line) => !line.includes(match.registration_snippet)).join("\n");
@@ -8336,39 +8598,39 @@ async function rollbackExtension(ctx, args) {
8336
8598
 
8337
8599
  // src/divergences.ts
8338
8600
  init_cjs_shims();
8339
- var import_types32 = require("@neat.is/types");
8601
+ var import_types33 = require("@neat.is/types");
8340
8602
  function bucketKey(source, target, type) {
8341
8603
  return `${type}|${source}|${target}`;
8342
8604
  }
8343
8605
  function bucketSourceFor(graph, edge) {
8344
- if (edge.type !== import_types32.EdgeType.CONNECTS_TO) return edge.source;
8345
- const parsed = (0, import_types32.parseFileId)(edge.source);
8606
+ if (edge.type !== import_types33.EdgeType.CONNECTS_TO) return edge.source;
8607
+ const parsed = (0, import_types33.parseFileId)(edge.source);
8346
8608
  if (!parsed || !graph.hasNode(edge.target)) return edge.source;
8347
8609
  const target = graph.getNodeAttributes(edge.target);
8348
- if (target.type !== import_types32.NodeType.DatabaseNode) return edge.source;
8349
- return (0, import_types32.serviceId)(parsed.service);
8610
+ if (target.type !== import_types33.NodeType.DatabaseNode) return edge.source;
8611
+ return (0, import_types33.serviceId)(parsed.service);
8350
8612
  }
8351
8613
  function bucketEdges(graph) {
8352
8614
  const buckets2 = /* @__PURE__ */ new Map();
8353
8615
  graph.forEachEdge((id, attrs) => {
8354
8616
  const e = attrs;
8355
- const parsed = (0, import_types32.parseEdgeId)(id);
8617
+ const parsed = (0, import_types33.parseEdgeId)(id);
8356
8618
  const provenance = parsed?.provenance ?? e.provenance;
8357
8619
  const source = bucketSourceFor(graph, e);
8358
8620
  const key = bucketKey(source, e.target, e.type);
8359
8621
  const cur = buckets2.get(key) ?? { source, target: e.target, type: e.type };
8360
8622
  switch (provenance) {
8361
- case import_types32.Provenance.EXTRACTED:
8623
+ case import_types33.Provenance.EXTRACTED:
8362
8624
  cur.extracted = e;
8363
8625
  break;
8364
- case import_types32.Provenance.OBSERVED:
8626
+ case import_types33.Provenance.OBSERVED:
8365
8627
  cur.observed = e;
8366
8628
  break;
8367
- case import_types32.Provenance.INFERRED:
8629
+ case import_types33.Provenance.INFERRED:
8368
8630
  cur.inferred = e;
8369
8631
  break;
8370
8632
  default:
8371
- if (e.provenance === import_types32.Provenance.STALE) cur.stale = e;
8633
+ if (e.provenance === import_types33.Provenance.STALE) cur.stale = e;
8372
8634
  }
8373
8635
  buckets2.set(key, cur);
8374
8636
  });
@@ -8377,12 +8639,12 @@ function bucketEdges(graph) {
8377
8639
  function nodeIsFrontier(graph, nodeId) {
8378
8640
  if (!graph.hasNode(nodeId)) return false;
8379
8641
  const attrs = graph.getNodeAttributes(nodeId);
8380
- return attrs.type === import_types32.NodeType.FrontierNode;
8642
+ return attrs.type === import_types33.NodeType.FrontierNode;
8381
8643
  }
8382
8644
  function nodeIsWebsocketChannel(graph, nodeId) {
8383
8645
  if (!graph.hasNode(nodeId)) return false;
8384
8646
  const attrs = graph.getNodeAttributes(nodeId);
8385
- return attrs.type === import_types32.NodeType.WebSocketChannelNode;
8647
+ return attrs.type === import_types33.NodeType.WebSocketChannelNode;
8386
8648
  }
8387
8649
  function clampConfidence(n) {
8388
8650
  if (!Number.isFinite(n)) return 0;
@@ -8402,14 +8664,14 @@ function gradedConfidence(edge) {
8402
8664
  return clampConfidence(confidenceForEdge(edge));
8403
8665
  }
8404
8666
  var OBSERVABLE_EDGE_TYPES = /* @__PURE__ */ new Set([
8405
- import_types32.EdgeType.CALLS,
8406
- import_types32.EdgeType.CONNECTS_TO,
8407
- import_types32.EdgeType.PUBLISHES_TO,
8408
- import_types32.EdgeType.CONSUMES_FROM
8667
+ import_types33.EdgeType.CALLS,
8668
+ import_types33.EdgeType.CONNECTS_TO,
8669
+ import_types33.EdgeType.PUBLISHES_TO,
8670
+ import_types33.EdgeType.CONSUMES_FROM
8409
8671
  ]);
8410
8672
  function detectMissingDivergences(graph, bucket) {
8411
8673
  const out = [];
8412
- if (bucket.type === import_types32.EdgeType.CONTAINS) return out;
8674
+ if (bucket.type === import_types33.EdgeType.CONTAINS) return out;
8413
8675
  if (bucket.extracted && !bucket.observed && OBSERVABLE_EDGE_TYPES.has(bucket.type)) {
8414
8676
  if (!nodeIsFrontier(graph, bucket.target)) {
8415
8677
  out.push({
@@ -8450,7 +8712,7 @@ function declaredHostFor(svc) {
8450
8712
  function hasExtractedConfiguredBy(graph, svcId) {
8451
8713
  for (const edgeId of graph.outboundEdges(svcId)) {
8452
8714
  const e = graph.getEdgeAttributes(edgeId);
8453
- if (e.type === import_types32.EdgeType.CONFIGURED_BY && e.provenance === import_types32.Provenance.EXTRACTED) {
8715
+ if (e.type === import_types33.EdgeType.CONFIGURED_BY && e.provenance === import_types33.Provenance.EXTRACTED) {
8454
8716
  return true;
8455
8717
  }
8456
8718
  }
@@ -8463,10 +8725,10 @@ function detectHostMismatch(graph, svcId, svc) {
8463
8725
  const out = [];
8464
8726
  for (const edgeId of graph.outboundEdges(svcId)) {
8465
8727
  const edge = graph.getEdgeAttributes(edgeId);
8466
- if (edge.type !== import_types32.EdgeType.CONNECTS_TO) continue;
8467
- if (edge.provenance !== import_types32.Provenance.OBSERVED) continue;
8728
+ if (edge.type !== import_types33.EdgeType.CONNECTS_TO) continue;
8729
+ if (edge.provenance !== import_types33.Provenance.OBSERVED) continue;
8468
8730
  const target = graph.getNodeAttributes(edge.target);
8469
- if (target.type !== import_types32.NodeType.DatabaseNode) continue;
8731
+ if (target.type !== import_types33.NodeType.DatabaseNode) continue;
8470
8732
  const observedHost = target.host?.trim();
8471
8733
  if (!observedHost) continue;
8472
8734
  if (observedHost === declaredHost) continue;
@@ -8488,10 +8750,10 @@ function detectCompatDivergences(graph, svcId, svc) {
8488
8750
  const deps = svc.dependencies ?? {};
8489
8751
  for (const edgeId of graph.outboundEdges(svcId)) {
8490
8752
  const edge = graph.getEdgeAttributes(edgeId);
8491
- if (edge.type !== import_types32.EdgeType.CONNECTS_TO) continue;
8492
- if (edge.provenance !== import_types32.Provenance.OBSERVED) continue;
8753
+ if (edge.type !== import_types33.EdgeType.CONNECTS_TO) continue;
8754
+ if (edge.provenance !== import_types33.Provenance.OBSERVED) continue;
8493
8755
  const target = graph.getNodeAttributes(edge.target);
8494
- if (target.type !== import_types32.NodeType.DatabaseNode) continue;
8756
+ if (target.type !== import_types33.NodeType.DatabaseNode) continue;
8495
8757
  for (const pair of compatPairs()) {
8496
8758
  if (pair.engine !== target.engine) continue;
8497
8759
  const declared = deps[pair.driver];
@@ -8550,7 +8812,7 @@ function suppressHostMismatchHalves(all) {
8550
8812
  for (const d of all) {
8551
8813
  if (d.type !== "host-mismatch") continue;
8552
8814
  observedHalf.add(`${d.source}->${d.target}`);
8553
- declaredHalf.add((0, import_types32.databaseId)(d.extractedHost));
8815
+ declaredHalf.add((0, import_types33.databaseId)(d.extractedHost));
8554
8816
  }
8555
8817
  if (observedHalf.size === 0) return all;
8556
8818
  return all.filter((d) => {
@@ -8569,7 +8831,7 @@ function computeDivergences(graph, opts = {}) {
8569
8831
  }
8570
8832
  graph.forEachNode((nodeId, attrs) => {
8571
8833
  const n = attrs;
8572
- if (n.type !== import_types32.NodeType.ServiceNode) return;
8834
+ if (n.type !== import_types33.NodeType.ServiceNode) return;
8573
8835
  const svc = n;
8574
8836
  for (const d of detectHostMismatch(graph, nodeId, svc)) all.push(d);
8575
8837
  for (const d of detectCompatDivergences(graph, nodeId, svc)) all.push(d);
@@ -8603,7 +8865,7 @@ function computeDivergences(graph, opts = {}) {
8603
8865
  if (a.source !== b.source) return a.source.localeCompare(b.source);
8604
8866
  return a.target.localeCompare(b.target);
8605
8867
  });
8606
- return import_types32.DivergenceResultSchema.parse({
8868
+ return import_types33.DivergenceResultSchema.parse({
8607
8869
  divergences: filtered,
8608
8870
  totalAffected: filtered.length,
8609
8871
  computedAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -8734,23 +8996,23 @@ function canonicalJson(value) {
8734
8996
 
8735
8997
  // src/projects.ts
8736
8998
  init_cjs_shims();
8737
- var import_node_path45 = __toESM(require("path"), 1);
8999
+ var import_node_path46 = __toESM(require("path"), 1);
8738
9000
  function pathsForProject(project, baseDir) {
8739
9001
  if (project === DEFAULT_PROJECT) {
8740
9002
  return {
8741
- snapshotPath: import_node_path45.default.join(baseDir, "graph.json"),
8742
- errorsPath: import_node_path45.default.join(baseDir, "errors.ndjson"),
8743
- staleEventsPath: import_node_path45.default.join(baseDir, "stale-events.ndjson"),
8744
- embeddingsCachePath: import_node_path45.default.join(baseDir, "embeddings.json"),
8745
- policyViolationsPath: import_node_path45.default.join(baseDir, "policy-violations.ndjson")
9003
+ snapshotPath: import_node_path46.default.join(baseDir, "graph.json"),
9004
+ errorsPath: import_node_path46.default.join(baseDir, "errors.ndjson"),
9005
+ staleEventsPath: import_node_path46.default.join(baseDir, "stale-events.ndjson"),
9006
+ embeddingsCachePath: import_node_path46.default.join(baseDir, "embeddings.json"),
9007
+ policyViolationsPath: import_node_path46.default.join(baseDir, "policy-violations.ndjson")
8746
9008
  };
8747
9009
  }
8748
9010
  return {
8749
- snapshotPath: import_node_path45.default.join(baseDir, `${project}.json`),
8750
- errorsPath: import_node_path45.default.join(baseDir, `errors.${project}.ndjson`),
8751
- staleEventsPath: import_node_path45.default.join(baseDir, `stale-events.${project}.ndjson`),
8752
- embeddingsCachePath: import_node_path45.default.join(baseDir, `embeddings.${project}.json`),
8753
- policyViolationsPath: import_node_path45.default.join(baseDir, `policy-violations.${project}.ndjson`)
9011
+ snapshotPath: import_node_path46.default.join(baseDir, `${project}.json`),
9012
+ errorsPath: import_node_path46.default.join(baseDir, `errors.${project}.ndjson`),
9013
+ staleEventsPath: import_node_path46.default.join(baseDir, `stale-events.${project}.ndjson`),
9014
+ embeddingsCachePath: import_node_path46.default.join(baseDir, `embeddings.${project}.json`),
9015
+ policyViolationsPath: import_node_path46.default.join(baseDir, `policy-violations.${project}.ndjson`)
8754
9016
  };
8755
9017
  }
8756
9018
  var Projects = class {
@@ -8788,23 +9050,23 @@ var Projects = class {
8788
9050
  init_cjs_shims();
8789
9051
  var import_node_fs28 = require("fs");
8790
9052
  var import_node_os3 = __toESM(require("os"), 1);
8791
- var import_node_path46 = __toESM(require("path"), 1);
8792
- var import_types33 = require("@neat.is/types");
9053
+ var import_node_path47 = __toESM(require("path"), 1);
9054
+ var import_types34 = require("@neat.is/types");
8793
9055
  var LOCK_TIMEOUT_MS = 5e3;
8794
9056
  var LOCK_RETRY_MS = 50;
8795
9057
  function neatHome() {
8796
9058
  const override = process.env.NEAT_HOME;
8797
- if (override && override.length > 0) return import_node_path46.default.resolve(override);
8798
- return import_node_path46.default.join(import_node_os3.default.homedir(), ".neat");
9059
+ if (override && override.length > 0) return import_node_path47.default.resolve(override);
9060
+ return import_node_path47.default.join(import_node_os3.default.homedir(), ".neat");
8799
9061
  }
8800
9062
  function registryPath() {
8801
- return import_node_path46.default.join(neatHome(), "projects.json");
9063
+ return import_node_path47.default.join(neatHome(), "projects.json");
8802
9064
  }
8803
9065
  function registryLockPath() {
8804
- return import_node_path46.default.join(neatHome(), "projects.json.lock");
9066
+ return import_node_path47.default.join(neatHome(), "projects.json.lock");
8805
9067
  }
8806
9068
  function daemonPidPath() {
8807
- return import_node_path46.default.join(neatHome(), "neatd.pid");
9069
+ return import_node_path47.default.join(neatHome(), "neatd.pid");
8808
9070
  }
8809
9071
  function isPidAliveDefault(pid) {
8810
9072
  try {
@@ -8864,7 +9126,7 @@ function lockHolderMessage(holder, lockPath, timeoutMs) {
8864
9126
  }
8865
9127
  }
8866
9128
  async function normalizeProjectPath(input) {
8867
- const resolved = import_node_path46.default.resolve(input);
9129
+ const resolved = import_node_path47.default.resolve(input);
8868
9130
  try {
8869
9131
  return await import_node_fs28.promises.realpath(resolved);
8870
9132
  } catch {
@@ -8872,7 +9134,7 @@ async function normalizeProjectPath(input) {
8872
9134
  }
8873
9135
  }
8874
9136
  async function writeAtomically(target, contents) {
8875
- await import_node_fs28.promises.mkdir(import_node_path46.default.dirname(target), { recursive: true });
9137
+ await import_node_fs28.promises.mkdir(import_node_path47.default.dirname(target), { recursive: true });
8876
9138
  const tmp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
8877
9139
  const fd = await import_node_fs28.promises.open(tmp, "w");
8878
9140
  try {
@@ -8885,7 +9147,7 @@ async function writeAtomically(target, contents) {
8885
9147
  }
8886
9148
  async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaultLockHolderProbe) {
8887
9149
  const deadline = Date.now() + timeoutMs;
8888
- await import_node_fs28.promises.mkdir(import_node_path46.default.dirname(lockPath), { recursive: true });
9150
+ await import_node_fs28.promises.mkdir(import_node_path47.default.dirname(lockPath), { recursive: true });
8889
9151
  let probedHolder = false;
8890
9152
  while (true) {
8891
9153
  try {
@@ -8938,10 +9200,10 @@ async function readRegistry() {
8938
9200
  throw err;
8939
9201
  }
8940
9202
  const parsed = JSON.parse(raw);
8941
- return import_types33.RegistryFileSchema.parse(parsed);
9203
+ return import_types34.RegistryFileSchema.parse(parsed);
8942
9204
  }
8943
9205
  async function writeRegistry(reg) {
8944
- const validated = import_types33.RegistryFileSchema.parse(reg);
9206
+ const validated = import_types34.RegistryFileSchema.parse(reg);
8945
9207
  await writeAtomically(registryPath(), JSON.stringify(validated, null, 2) + "\n");
8946
9208
  }
8947
9209
  var ProjectNameCollisionError = class extends Error {
@@ -9136,7 +9398,7 @@ init_auth();
9136
9398
  // src/connectors-config.ts
9137
9399
  init_cjs_shims();
9138
9400
  var import_node_os4 = __toESM(require("os"), 1);
9139
- var import_node_path47 = __toESM(require("path"), 1);
9401
+ var import_node_path48 = __toESM(require("path"), 1);
9140
9402
  var import_node_fs29 = require("fs");
9141
9403
  var CONNECTORS_CONFIG_VERSION = 1;
9142
9404
  var EnvRefUnsetError = class extends Error {
@@ -9151,11 +9413,11 @@ var EnvRefUnsetError = class extends Error {
9151
9413
  };
9152
9414
  function neatHome2() {
9153
9415
  const override = process.env.NEAT_HOME;
9154
- if (override && override.length > 0) return import_node_path47.default.resolve(override);
9155
- return import_node_path47.default.join(import_node_os4.default.homedir(), ".neat");
9416
+ if (override && override.length > 0) return import_node_path48.default.resolve(override);
9417
+ return import_node_path48.default.join(import_node_os4.default.homedir(), ".neat");
9156
9418
  }
9157
9419
  function connectorsConfigPath(home = neatHome2()) {
9158
- return import_node_path47.default.join(home, "connectors.json");
9420
+ return import_node_path48.default.join(home, "connectors.json");
9159
9421
  }
9160
9422
  var MODE_MASK_LOOSER_THAN_0600 = 63;
9161
9423
  async function warnIfModeLooserThan0600(file) {
@@ -9491,11 +9753,11 @@ function registerRoutes(scope, ctx) {
9491
9753
  const candidates = req.query.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
9492
9754
  const parsed = [];
9493
9755
  for (const c of candidates) {
9494
- const r = import_types34.DivergenceTypeSchema.safeParse(c);
9756
+ const r = import_types35.DivergenceTypeSchema.safeParse(c);
9495
9757
  if (!r.success) {
9496
9758
  return reply.code(400).send({
9497
9759
  error: `unknown divergence type "${c}"`,
9498
- allowed: import_types34.DivergenceTypeSchema.options
9760
+ allowed: import_types35.DivergenceTypeSchema.options
9499
9761
  });
9500
9762
  }
9501
9763
  parsed.push(r.data);
@@ -9766,7 +10028,7 @@ function registerRoutes(scope, ctx) {
9766
10028
  const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
9767
10029
  let violations = await log.readAll();
9768
10030
  if (req.query.severity) {
9769
- const sev = import_types34.PolicySeveritySchema.safeParse(req.query.severity);
10031
+ const sev = import_types35.PolicySeveritySchema.safeParse(req.query.severity);
9770
10032
  if (!sev.success) {
9771
10033
  return reply.code(400).send({
9772
10034
  error: "invalid severity",
@@ -9805,7 +10067,7 @@ function registerRoutes(scope, ctx) {
9805
10067
  scope.post("/policies/check", async (req, reply) => {
9806
10068
  const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
9807
10069
  if (!proj) return;
9808
- const parsed = import_types34.PoliciesCheckBodySchema.safeParse(req.body ?? {});
10070
+ const parsed = import_types35.PoliciesCheckBodySchema.safeParse(req.body ?? {});
9809
10071
  if (!parsed.success) {
9810
10072
  return reply.code(400).send({
9811
10073
  error: "invalid /policies/check body",
@@ -10107,21 +10369,21 @@ init_otel_grpc();
10107
10369
  // src/daemon.ts
10108
10370
  init_cjs_shims();
10109
10371
  var import_node_fs31 = require("fs");
10110
- var import_node_path51 = __toESM(require("path"), 1);
10372
+ var import_node_path52 = __toESM(require("path"), 1);
10111
10373
  var import_node_module = require("module");
10112
10374
  init_otel();
10113
10375
 
10114
10376
  // src/connectors/index.ts
10115
10377
  init_cjs_shims();
10116
- var import_types35 = require("@neat.is/types");
10378
+ var import_types36 = require("@neat.is/types");
10117
10379
  var NO_ENV = "unknown";
10118
10380
  function staticCallSiteFor(graph, serviceName, targetNodeId) {
10119
10381
  if (!graph.hasNode(targetNodeId)) return void 0;
10120
10382
  const sites = [];
10121
10383
  for (const edgeId of graph.inboundEdges(targetNodeId)) {
10122
10384
  const edge = graph.getEdgeAttributes(edgeId);
10123
- if (edge.provenance !== import_types35.Provenance.EXTRACTED) continue;
10124
- const parsed = (0, import_types35.parseFileId)(edge.source);
10385
+ if (edge.provenance !== import_types36.Provenance.EXTRACTED) continue;
10386
+ const parsed = (0, import_types36.parseFileId)(edge.source);
10125
10387
  if (!parsed || parsed.service !== serviceName || !edge.evidence) continue;
10126
10388
  const site = { relPath: edge.evidence.file };
10127
10389
  if (edge.evidence.line !== void 0) site.line = edge.evidence.line;
@@ -10132,7 +10394,7 @@ function staticCallSiteFor(graph, serviceName, targetNodeId) {
10132
10394
  function routeCallSiteFor(graph, targetNodeId) {
10133
10395
  if (!graph.hasNode(targetNodeId)) return void 0;
10134
10396
  const attrs = graph.getNodeAttributes(targetNodeId);
10135
- if (attrs.type !== import_types35.NodeType.RouteNode || !attrs.path) return void 0;
10397
+ if (attrs.type !== import_types36.NodeType.RouteNode || !attrs.path) return void 0;
10136
10398
  const site = { relPath: attrs.path };
10137
10399
  if (attrs.line !== void 0) site.line = attrs.line;
10138
10400
  return site;
@@ -10603,10 +10865,10 @@ var SUPABASE_RPC_TARGET_KIND = "supabase-rpc";
10603
10865
  // src/connectors/supabase/map.ts
10604
10866
  var REST_RPC_PATH_RE = /^\/rest\/v1\/rpc\/([^/?]+)/;
10605
10867
  var REST_TABLE_PATH_RE = /^\/rest\/v1\/([^/?]+)/;
10606
- function targetFromRestPath(path52) {
10607
- const rpcMatch = REST_RPC_PATH_RE.exec(path52);
10868
+ function targetFromRestPath(path53) {
10869
+ const rpcMatch = REST_RPC_PATH_RE.exec(path53);
10608
10870
  if (rpcMatch) return { targetKind: SUPABASE_RPC_TARGET_KIND, name: rpcMatch[1] };
10609
- const tableMatch = REST_TABLE_PATH_RE.exec(path52);
10871
+ const tableMatch = REST_TABLE_PATH_RE.exec(path53);
10610
10872
  if (tableMatch) return { targetKind: SUPABASE_TABLE_TARGET_KIND, name: tableMatch[1] };
10611
10873
  return null;
10612
10874
  }
@@ -10715,23 +10977,23 @@ async function fetchPgStatStatements(connectionString, limit = DEFAULT_STATEMENT
10715
10977
 
10716
10978
  // src/connectors/supabase/resolve.ts
10717
10979
  init_cjs_shims();
10718
- var import_types37 = require("@neat.is/types");
10980
+ var import_types38 = require("@neat.is/types");
10719
10981
  function createSupabaseResolveTarget(graph, config) {
10720
10982
  return (signal, _ctx) => {
10721
10983
  if (signal.targetKind !== SUPABASE_TABLE_TARGET_KIND && signal.targetKind !== SUPABASE_RPC_TARGET_KIND) {
10722
10984
  return null;
10723
10985
  }
10724
- const subResourceId = (0, import_types37.infraId)(signal.targetKind, `${config.nodeRef}/${signal.targetName}`);
10986
+ const subResourceId = (0, import_types38.infraId)(signal.targetKind, `${config.nodeRef}/${signal.targetName}`);
10725
10987
  if (graph.hasNode(subResourceId)) {
10726
- return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType: import_types37.EdgeType.CALLS };
10988
+ return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType: import_types38.EdgeType.CALLS };
10727
10989
  }
10728
- const bareResourceId = (0, import_types37.infraId)(signal.targetKind, signal.targetName);
10990
+ const bareResourceId = (0, import_types38.infraId)(signal.targetKind, signal.targetName);
10729
10991
  if (graph.hasNode(bareResourceId)) {
10730
- return { targetNodeId: bareResourceId, serviceName: config.serviceName, edgeType: import_types37.EdgeType.CALLS };
10992
+ return { targetNodeId: bareResourceId, serviceName: config.serviceName, edgeType: import_types38.EdgeType.CALLS };
10731
10993
  }
10732
- const projectLevelId = (0, import_types37.infraId)("supabase", config.nodeRef);
10994
+ const projectLevelId = (0, import_types38.infraId)("supabase", config.nodeRef);
10733
10995
  if (graph.hasNode(projectLevelId)) {
10734
- return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType: import_types37.EdgeType.CALLS };
10996
+ return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType: import_types38.EdgeType.CALLS };
10735
10997
  }
10736
10998
  return null;
10737
10999
  };
@@ -10824,7 +11086,7 @@ function createSupabaseConnector(graph, config, deps = {}) {
10824
11086
 
10825
11087
  // src/connectors/railway/index.ts
10826
11088
  init_cjs_shims();
10827
- var import_types41 = require("@neat.is/types");
11089
+ var import_types42 = require("@neat.is/types");
10828
11090
 
10829
11091
  // src/connectors/railway/client.ts
10830
11092
  init_cjs_shims();
@@ -10950,7 +11212,7 @@ function buildRailwayRouteIndex(graph, serviceName) {
10950
11212
  const out = [];
10951
11213
  graph.forEachNode((_id, attrs) => {
10952
11214
  const node = attrs;
10953
- if (node.type !== import_types41.NodeType.RouteNode) return;
11215
+ if (node.type !== import_types42.NodeType.RouteNode) return;
10954
11216
  const route = attrs;
10955
11217
  if (route.service !== serviceName) return;
10956
11218
  out.push({
@@ -11054,12 +11316,12 @@ function createRailwayResolveTarget(config) {
11054
11316
  const serviceName = config.serviceNameById[config.serviceId];
11055
11317
  if (!serviceName) return null;
11056
11318
  if (signal.targetKind === ROUTE_TARGET_KIND) {
11057
- return { targetNodeId: signal.targetName, serviceName, edgeType: import_types41.EdgeType.CALLS };
11319
+ return { targetNodeId: signal.targetName, serviceName, edgeType: import_types42.EdgeType.CALLS };
11058
11320
  }
11059
11321
  if (signal.targetKind === PEER_SERVICE_TARGET_KIND) {
11060
11322
  const peerName = config.serviceNameById[signal.targetName];
11061
11323
  if (!peerName) return null;
11062
- return { targetNodeId: (0, import_types41.serviceId)(peerName), serviceName, edgeType: import_types41.EdgeType.CONNECTS_TO };
11324
+ return { targetNodeId: (0, import_types42.serviceId)(peerName), serviceName, edgeType: import_types42.EdgeType.CONNECTS_TO };
11063
11325
  }
11064
11326
  return null;
11065
11327
  };
@@ -11183,9 +11445,9 @@ function parseFirebaseTargetName(targetName) {
11183
11445
  const secondSep = rest.indexOf(FIELD_SEP);
11184
11446
  if (secondSep === -1) return null;
11185
11447
  const method = rest.slice(0, secondSep);
11186
- const path52 = rest.slice(secondSep + 1);
11187
- if (!resourceName || !method || !path52) return null;
11188
- return { resourceName, method, path: path52 };
11448
+ const path53 = rest.slice(secondSep + 1);
11449
+ if (!resourceName || !method || !path53) return null;
11450
+ return { resourceName, method, path: path53 };
11189
11451
  }
11190
11452
  function resourceNameFor(type, labels) {
11191
11453
  if (!labels) return null;
@@ -11223,14 +11485,14 @@ function mapLogEntryToSignal(entry) {
11223
11485
  if (!req) return null;
11224
11486
  if (typeof req.requestMethod !== "string" || req.requestMethod.length === 0) return null;
11225
11487
  const method = req.requestMethod.toUpperCase();
11226
- const path52 = pathFromRequestUrl(req.requestUrl);
11227
- if (path52 === null) return null;
11488
+ const path53 = pathFromRequestUrl(req.requestUrl);
11489
+ if (path53 === null) return null;
11228
11490
  const timestamp = entry.timestamp;
11229
11491
  if (typeof timestamp !== "string" || timestamp.length === 0) return null;
11230
11492
  const isError = typeof req.status === "number" && req.status >= ERROR_STATUS_THRESHOLD2;
11231
11493
  return {
11232
11494
  targetKind: resourceType,
11233
- targetName: packFirebaseTargetName({ resourceName, method, path: path52 }),
11495
+ targetName: packFirebaseTargetName({ resourceName, method, path: path53 }),
11234
11496
  callCount: 1,
11235
11497
  errorCount: isError ? 1 : 0,
11236
11498
  lastObservedIso: timestamp
@@ -11247,7 +11509,7 @@ function mapLogEntriesToSignals(entries) {
11247
11509
 
11248
11510
  // src/connectors/firebase/resolve.ts
11249
11511
  init_cjs_shims();
11250
- var import_types42 = require("@neat.is/types");
11512
+ var import_types43 = require("@neat.is/types");
11251
11513
  function neatServiceNameFor(resourceType, resourceName, serviceMap) {
11252
11514
  switch (resourceType) {
11253
11515
  case "cloud_function":
@@ -11262,7 +11524,7 @@ function routeEntriesFor(graph, serviceName) {
11262
11524
  const entries = [];
11263
11525
  graph.forEachNode((_id, attrs) => {
11264
11526
  const node = attrs;
11265
- if (node.type !== import_types42.NodeType.RouteNode) return;
11527
+ if (node.type !== import_types43.NodeType.RouteNode) return;
11266
11528
  const route = attrs;
11267
11529
  if (route.service !== serviceName) return;
11268
11530
  entries.push({
@@ -11294,7 +11556,7 @@ function createFirebaseResolveTarget(graph, serviceMap) {
11294
11556
  return {
11295
11557
  targetNodeId: match.routeNodeId,
11296
11558
  serviceName,
11297
- edgeType: import_types42.EdgeType.CALLS
11559
+ edgeType: import_types43.EdgeType.CALLS
11298
11560
  };
11299
11561
  };
11300
11562
  }
@@ -11321,7 +11583,7 @@ init_cjs_shims();
11321
11583
 
11322
11584
  // src/connectors/cloudflare/connector.ts
11323
11585
  init_cjs_shims();
11324
- var import_types44 = require("@neat.is/types");
11586
+ var import_types45 = require("@neat.is/types");
11325
11587
 
11326
11588
  // src/connectors/cloudflare/client.ts
11327
11589
  init_cjs_shims();
@@ -11437,7 +11699,7 @@ function mapEventToSignal(event) {
11437
11699
  if (Number.isNaN(observedAt.getTime())) return null;
11438
11700
  const statusCode = metadata?.statusCode;
11439
11701
  const isError = typeof statusCode === "number" && statusCode >= ERROR_STATUS_THRESHOLD3;
11440
- const path52 = metadata?.trigger ? parsePathFromTrigger(metadata.trigger) : void 0;
11702
+ const path53 = metadata?.trigger ? parsePathFromTrigger(metadata.trigger) : void 0;
11441
11703
  return {
11442
11704
  targetKind: CLOUDFLARE_TARGET_KIND,
11443
11705
  targetName: scriptName,
@@ -11445,7 +11707,7 @@ function mapEventToSignal(event) {
11445
11707
  errorCount: isError ? 1 : 0,
11446
11708
  lastObservedIso: observedAt.toISOString(),
11447
11709
  method,
11448
- ...path52 ? { path: path52 } : {},
11710
+ ...path53 ? { path: path53 } : {},
11449
11711
  ...typeof statusCode === "number" ? { statusCode } : {},
11450
11712
  ...typeof metadata?.duration === "number" ? { duration: metadata.duration } : {}
11451
11713
  };
@@ -11485,19 +11747,19 @@ function findTaggedWorkerFileNode(graph, workerName) {
11485
11747
  graph.forEachNode((id, attrs) => {
11486
11748
  if (found) return;
11487
11749
  const a = attrs;
11488
- if (a.type === import_types44.NodeType.FileNode && a.platform === "cloudflare" && a.platformName === workerName) {
11750
+ if (a.type === import_types45.NodeType.FileNode && a.platform === "cloudflare" && a.platformName === workerName) {
11489
11751
  found = id;
11490
11752
  }
11491
11753
  });
11492
11754
  return found;
11493
11755
  }
11494
- function findMatchingRouteNode(graph, serviceName, method, path52) {
11495
- const normalizedPath = normalizePathTemplate(path52);
11756
+ function findMatchingRouteNode(graph, serviceName, method, path53) {
11757
+ const normalizedPath = normalizePathTemplate(path53);
11496
11758
  let found = null;
11497
11759
  graph.forEachNode((id, attrs) => {
11498
11760
  if (found) return;
11499
11761
  const a = attrs;
11500
- if (a.type !== import_types44.NodeType.RouteNode || a.service !== serviceName) return;
11762
+ if (a.type !== import_types45.NodeType.RouteNode || a.service !== serviceName) return;
11501
11763
  if (!a.pathTemplate || normalizePathTemplate(a.pathTemplate) !== normalizedPath) return;
11502
11764
  const routeMethod = (a.method ?? "").toUpperCase();
11503
11765
  if (routeMethod !== "ALL" && routeMethod !== method) return;
@@ -11509,18 +11771,18 @@ function createCloudflareResolveTarget(config, graph) {
11509
11771
  return (signal) => {
11510
11772
  if (signal.targetKind !== CLOUDFLARE_TARGET_KIND) return null;
11511
11773
  const scriptName = signal.targetName;
11512
- const { method, path: path52 } = signal;
11774
+ const { method, path: path53 } = signal;
11513
11775
  const resolveRouteGrain = (serviceName, wholeFileId) => {
11514
- if (!method || !path52) return wholeFileId;
11515
- return findMatchingRouteNode(graph, serviceName, method, path52) ?? wholeFileId;
11776
+ if (!method || !path53) return wholeFileId;
11777
+ return findMatchingRouteNode(graph, serviceName, method, path53) ?? wholeFileId;
11516
11778
  };
11517
11779
  const mapping = config.workers?.[scriptName];
11518
11780
  if (mapping) {
11519
- const wholeFileId = (0, import_types44.fileId)(mapping.service, mapping.entryFile);
11781
+ const wholeFileId = (0, import_types45.fileId)(mapping.service, mapping.entryFile);
11520
11782
  return {
11521
11783
  targetNodeId: resolveRouteGrain(mapping.service, wholeFileId),
11522
11784
  serviceName: mapping.service,
11523
- edgeType: import_types44.EdgeType.CALLS
11785
+ edgeType: import_types45.EdgeType.CALLS
11524
11786
  };
11525
11787
  }
11526
11788
  const taggedFileId = findTaggedWorkerFileNode(graph, scriptName);
@@ -11529,13 +11791,13 @@ function createCloudflareResolveTarget(config, graph) {
11529
11791
  return {
11530
11792
  targetNodeId: resolveRouteGrain(fileNode.service, taggedFileId),
11531
11793
  serviceName: fileNode.service,
11532
- edgeType: import_types44.EdgeType.CALLS
11794
+ edgeType: import_types45.EdgeType.CALLS
11533
11795
  };
11534
11796
  }
11535
11797
  return {
11536
- targetNodeId: (0, import_types44.infraId)("cloudflare-worker", scriptName),
11798
+ targetNodeId: (0, import_types45.infraId)("cloudflare-worker", scriptName),
11537
11799
  serviceName: scriptName,
11538
- edgeType: import_types44.EdgeType.CALLS,
11800
+ edgeType: import_types45.EdgeType.CALLS,
11539
11801
  ensureInfraNode: { kind: "cloudflare-worker", name: scriptName, provider: "cloudflare" }
11540
11802
  };
11541
11803
  };
@@ -11979,7 +12241,7 @@ init_auth();
11979
12241
  // src/unrouted.ts
11980
12242
  init_cjs_shims();
11981
12243
  var import_node_fs30 = require("fs");
11982
- var import_node_path50 = __toESM(require("path"), 1);
12244
+ var import_node_path51 = __toESM(require("path"), 1);
11983
12245
  function buildUnroutedSpanRecord(serviceName, traceId, now = /* @__PURE__ */ new Date()) {
11984
12246
  return {
11985
12247
  timestamp: now.toISOString(),
@@ -11989,34 +12251,34 @@ function buildUnroutedSpanRecord(serviceName, traceId, now = /* @__PURE__ */ new
11989
12251
  };
11990
12252
  }
11991
12253
  async function appendUnroutedSpan(neatHome3, record) {
11992
- const target = import_node_path50.default.join(neatHome3, "errors.ndjson");
12254
+ const target = import_node_path51.default.join(neatHome3, "errors.ndjson");
11993
12255
  await import_node_fs30.promises.mkdir(neatHome3, { recursive: true });
11994
12256
  await import_node_fs30.promises.appendFile(target, JSON.stringify(record) + "\n", "utf8");
11995
12257
  }
11996
12258
  function unroutedErrorsPath(neatHome3) {
11997
- return import_node_path50.default.join(neatHome3, "errors.ndjson");
12259
+ return import_node_path51.default.join(neatHome3, "errors.ndjson");
11998
12260
  }
11999
12261
 
12000
12262
  // src/daemon.ts
12001
- var import_types47 = require("@neat.is/types");
12263
+ var import_types48 = require("@neat.is/types");
12002
12264
  function daemonJsonPath(scanPath) {
12003
- return import_node_path51.default.join(scanPath, "neat-out", "daemon.json");
12265
+ return import_node_path52.default.join(scanPath, "neat-out", "daemon.json");
12004
12266
  }
12005
12267
  function daemonsDiscoveryDir(home) {
12006
12268
  const base = home && home.length > 0 ? home : neatHomeFromEnv();
12007
- return import_node_path51.default.join(base, "daemons");
12269
+ return import_node_path52.default.join(base, "daemons");
12008
12270
  }
12009
12271
  function daemonDiscoveryPath(project, home) {
12010
- return import_node_path51.default.join(daemonsDiscoveryDir(home), `${sanitizeDiscoveryName(project)}.json`);
12272
+ return import_node_path52.default.join(daemonsDiscoveryDir(home), `${sanitizeDiscoveryName(project)}.json`);
12011
12273
  }
12012
12274
  function sanitizeDiscoveryName(project) {
12013
12275
  return project.replace(/[^A-Za-z0-9._-]/g, "_");
12014
12276
  }
12015
12277
  function neatHomeFromEnv() {
12016
12278
  const env = process.env.NEAT_HOME;
12017
- if (env && env.length > 0) return import_node_path51.default.resolve(env);
12279
+ if (env && env.length > 0) return import_node_path52.default.resolve(env);
12018
12280
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
12019
- return import_node_path51.default.join(home, ".neat");
12281
+ return import_node_path52.default.join(home, ".neat");
12020
12282
  }
12021
12283
  function resolveNeatVersion() {
12022
12284
  if (process.env.NEAT_LOCAL_VERSION && process.env.NEAT_LOCAL_VERSION.length > 0) {
@@ -12071,11 +12333,11 @@ function teardownSlot(slot) {
12071
12333
  }
12072
12334
  }
12073
12335
  function neatHomeFor(opts) {
12074
- if (opts.neatHome && opts.neatHome.length > 0) return import_node_path51.default.resolve(opts.neatHome);
12336
+ if (opts.neatHome && opts.neatHome.length > 0) return import_node_path52.default.resolve(opts.neatHome);
12075
12337
  const env = process.env.NEAT_HOME;
12076
- if (env && env.length > 0) return import_node_path51.default.resolve(env);
12338
+ if (env && env.length > 0) return import_node_path52.default.resolve(env);
12077
12339
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
12078
- return import_node_path51.default.join(home, ".neat");
12340
+ return import_node_path52.default.join(home, ".neat");
12079
12341
  }
12080
12342
  function routeSpanToProject(serviceName, projects) {
12081
12343
  if (!serviceName) return DEFAULT_PROJECT;
@@ -12119,11 +12381,11 @@ function spanBelongsToSingleProject(graph, project, serviceName) {
12119
12381
  if (!serviceName) return true;
12120
12382
  if (serviceNameMatchesProject(serviceName, project)) return true;
12121
12383
  return graph.someNode(
12122
- (_id, attrs) => attrs.type === import_types47.NodeType.ServiceNode && attrs.name === serviceName
12384
+ (_id, attrs) => attrs.type === import_types48.NodeType.ServiceNode && attrs.name === serviceName
12123
12385
  );
12124
12386
  }
12125
12387
  async function bootstrapProject(entry, connectors = [], neatHome3) {
12126
- const paths = pathsForProject(entry.name, import_node_path51.default.join(entry.path, "neat-out"));
12388
+ const paths = pathsForProject(entry.name, import_node_path52.default.join(entry.path, "neat-out"));
12127
12389
  try {
12128
12390
  const stat = await import_node_fs31.promises.stat(entry.path);
12129
12391
  if (!stat.isDirectory()) {
@@ -12254,7 +12516,7 @@ async function startDaemon(opts = {}) {
12254
12516
  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;
12255
12517
  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;
12256
12518
  const singleProject = projectArg;
12257
- const singleProjectPath = singleProject && projectPathArg ? import_node_path51.default.resolve(projectPathArg) : null;
12519
+ const singleProjectPath = singleProject && projectPathArg ? import_node_path52.default.resolve(projectPathArg) : null;
12258
12520
  if (singleProject && !singleProjectPath) {
12259
12521
  throw new Error(
12260
12522
  `neatd: project "${singleProject}" given without a projectPath; pass NEAT_PROJECT_PATH alongside NEAT_PROJECT.`
@@ -12269,7 +12531,7 @@ async function startDaemon(opts = {}) {
12269
12531
  );
12270
12532
  }
12271
12533
  }
12272
- const pidPath = import_node_path51.default.join(home, "neatd.pid");
12534
+ const pidPath = import_node_path52.default.join(home, "neatd.pid");
12273
12535
  await writeAtomically(pidPath, `${process.pid}
12274
12536
  `);
12275
12537
  const slots = /* @__PURE__ */ new Map();
@@ -12676,8 +12938,8 @@ async function startDaemon(opts = {}) {
12676
12938
  let registryWatcher = null;
12677
12939
  let reloadTimer = null;
12678
12940
  if (!singleProject) try {
12679
- const regDir = import_node_path51.default.dirname(regPath);
12680
- const regBase = import_node_path51.default.basename(regPath);
12941
+ const regDir = import_node_path52.default.dirname(regPath);
12942
+ const regBase = import_node_path52.default.basename(regPath);
12681
12943
  registryWatcher = (0, import_node_fs31.watch)(regDir, (_eventType, filename) => {
12682
12944
  if (filename !== null && filename !== regBase) return;
12683
12945
  if (reloadTimer) clearTimeout(reloadTimer);