@neat.is/core 0.7.6 → 0.7.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/neatd.cjs CHANGED
@@ -61,8 +61,8 @@ function mountBearerAuth(app, opts) {
61
61
  ]);
62
62
  const publicRead = opts.publicRead === true;
63
63
  app.addHook("preHandler", (req2, reply, done) => {
64
- const path69 = (req2.url.split("?")[0] ?? "").replace(/\/+$/, "");
65
- if (exactUnauthPaths.has(path69) || PROJECT_SCOPED_UNAUTH_PATTERN.test(path69)) {
64
+ const path70 = (req2.url.split("?")[0] ?? "").replace(/\/+$/, "");
65
+ if (exactUnauthPaths.has(path70) || PROJECT_SCOPED_UNAUTH_PATTERN.test(path70)) {
66
66
  done();
67
67
  return;
68
68
  }
@@ -343,7 +343,7 @@ function pickEnv(spanAttrs, resourceAttrs) {
343
343
  return ENV_FALLBACK;
344
344
  }
345
345
  function normalizeDbSystem(attrs) {
346
- const raw = attrs["db.system"];
346
+ const raw = attrs["db.system"] ?? attrs["db.system.name"];
347
347
  if (typeof raw !== "string") return void 0;
348
348
  return raw === "mongoose" ? "mongodb" : raw;
349
349
  }
@@ -415,8 +415,8 @@ function websocketChannelPathOf(attrs) {
415
415
  const v = attrs[key];
416
416
  if (typeof v === "string" && v.length > 0) {
417
417
  const q = v.indexOf("?");
418
- const path69 = q === -1 ? v : v.slice(0, q);
419
- if (path69.length > 0) return path69;
418
+ const path70 = q === -1 ? v : v.slice(0, q);
419
+ if (path70.length > 0) return path70;
420
420
  }
421
421
  }
422
422
  return void 0;
@@ -435,6 +435,9 @@ function parseOtlpRequest(body) {
435
435
  for (const ss of rs.scopeSpans ?? []) {
436
436
  for (const span of ss.spans ?? []) {
437
437
  const attrs = attrsToRecord(span.attributes);
438
+ const dbSqlText = typeof attrs["db.statement"] === "string" ? attrs["db.statement"] : typeof attrs["db.query.text"] === "string" ? attrs["db.query.text"] : void 0;
439
+ const dbSystemName = normalizeDbSystem(attrs);
440
+ const directDbTable = typeof attrs["db.sql.table"] === "string" ? attrs["db.sql.table"] : dbSystemName !== "mongodb" && typeof attrs["db.collection.name"] === "string" ? attrs["db.collection.name"] : void 0;
438
441
  const parsed = {
439
442
  service,
440
443
  resourceServiceNamePresent,
@@ -449,11 +452,11 @@ function parseOtlpRequest(body) {
449
452
  durationNanos: durationNanos(span.startTimeUnixNano, span.endTimeUnixNano),
450
453
  env: pickEnv(attrs, resourceAttrs),
451
454
  attributes: attrs,
452
- dbSystem: normalizeDbSystem(attrs),
455
+ dbSystem: dbSystemName,
453
456
  dbName: typeof attrs["db.name"] === "string" ? attrs["db.name"] : void 0,
454
457
  dbCollection: typeof attrs["db.collection.name"] === "string" ? attrs["db.collection.name"] : typeof attrs["db.mongodb.collection"] === "string" ? attrs["db.mongodb.collection"] : void 0,
455
- dbTable: typeof attrs["db.statement"] === "string" ? tableFromSqlStatement(attrs["db.statement"]) ?? void 0 : void 0,
456
- dbColumns: typeof attrs["db.statement"] === "string" ? columnsFromSqlStatement(attrs["db.statement"]) : void 0,
458
+ dbTable: directDbTable ?? (dbSqlText ? tableFromSqlStatement(dbSqlText) ?? void 0 : void 0),
459
+ dbColumns: dbSqlText ? columnsFromSqlStatement(dbSqlText) : void 0,
457
460
  httpRoute: typeof attrs["http.route"] === "string" ? attrs["http.route"] : void 0,
458
461
  httpMethod: typeof attrs["http.request.method"] === "string" ? attrs["http.request.method"] : typeof attrs["http.method"] === "string" ? attrs["http.method"] : void 0,
459
462
  messagingSystem: typeof attrs["messaging.system"] === "string" ? attrs["messaging.system"] : void 0,
@@ -738,13 +741,13 @@ __export(neatd_exports, {
738
741
  module.exports = __toCommonJS(neatd_exports);
739
742
  init_cjs_shims();
740
743
  var import_node_fs35 = require("fs");
741
- var import_node_path68 = __toESM(require("path"), 1);
744
+ var import_node_path69 = __toESM(require("path"), 1);
742
745
  var import_node_module2 = require("module");
743
746
 
744
747
  // src/daemon.ts
745
748
  init_cjs_shims();
746
749
  var import_node_fs33 = require("fs");
747
- var import_node_path66 = __toESM(require("path"), 1);
750
+ var import_node_path67 = __toESM(require("path"), 1);
748
751
  var import_node_module = require("module");
749
752
 
750
753
  // src/graph.ts
@@ -1277,19 +1280,19 @@ function confidenceFromMix(edges, now = Date.now()) {
1277
1280
  function longestIncomingWalk(graph, start, maxDepth) {
1278
1281
  let best = { path: [start], edges: [] };
1279
1282
  const visited = /* @__PURE__ */ new Set([start]);
1280
- function step(node, path69, edges) {
1281
- if (path69.length > best.path.length) {
1282
- best = { path: [...path69], edges: [...edges] };
1283
+ function step(node, path70, edges) {
1284
+ if (path70.length > best.path.length) {
1285
+ best = { path: [...path70], edges: [...edges] };
1283
1286
  }
1284
- if (path69.length - 1 >= maxDepth) return;
1287
+ if (path70.length - 1 >= maxDepth) return;
1285
1288
  const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
1286
1289
  for (const [srcId, edge] of incoming) {
1287
1290
  if (visited.has(srcId)) continue;
1288
1291
  visited.add(srcId);
1289
- path69.push(srcId);
1292
+ path70.push(srcId);
1290
1293
  edges.push(edge);
1291
- step(srcId, path69, edges);
1292
- path69.pop();
1294
+ step(srcId, path70, edges);
1295
+ path70.pop();
1293
1296
  edges.pop();
1294
1297
  visited.delete(srcId);
1295
1298
  }
@@ -1297,11 +1300,11 @@ function longestIncomingWalk(graph, start, maxDepth) {
1297
1300
  step(start, [start], []);
1298
1301
  return best;
1299
1302
  }
1300
- function databaseRootCauseShape(graph, origin, walk8) {
1303
+ function databaseRootCauseShape(graph, origin, walk9) {
1301
1304
  const targetDb = origin;
1302
1305
  const candidatePairs = compatPairs().filter((p) => p.engine === targetDb.engine);
1303
1306
  if (candidatePairs.length === 0) return null;
1304
- for (const id of walk8.path) {
1307
+ for (const id of walk9.path) {
1305
1308
  const owner = resolveOwningService(graph, id);
1306
1309
  if (!owner) continue;
1307
1310
  const { id: serviceId9, svc } = owner;
@@ -1328,8 +1331,8 @@ function databaseRootCauseShape(graph, origin, walk8) {
1328
1331
  }
1329
1332
  return null;
1330
1333
  }
1331
- function serviceRootCauseShape(graph, _origin, walk8) {
1332
- for (const id of walk8.path) {
1334
+ function serviceRootCauseShape(graph, _origin, walk9) {
1335
+ for (const id of walk9.path) {
1333
1336
  const owner = resolveOwningService(graph, id);
1334
1337
  if (!owner) continue;
1335
1338
  const { id: serviceId9, svc } = owner;
@@ -1365,15 +1368,15 @@ function serviceRootCauseShape(graph, _origin, walk8) {
1365
1368
  }
1366
1369
  return null;
1367
1370
  }
1368
- function fileRootCauseShape(graph, origin, walk8) {
1371
+ function fileRootCauseShape(graph, origin, walk9) {
1369
1372
  const owner = resolveOwningService(graph, origin.id);
1370
1373
  if (!owner) return null;
1371
- return serviceRootCauseShape(graph, owner.svc, walk8);
1374
+ return serviceRootCauseShape(graph, owner.svc, walk9);
1372
1375
  }
1373
- function symbolRootCauseShape(graph, origin, walk8) {
1376
+ function symbolRootCauseShape(graph, origin, walk9) {
1374
1377
  const owner = resolveOwningService(graph, origin.id);
1375
1378
  if (!owner) return null;
1376
- return serviceRootCauseShape(graph, owner.svc, walk8);
1379
+ return serviceRootCauseShape(graph, owner.svc, walk9);
1377
1380
  }
1378
1381
  var rootCauseShapes = {
1379
1382
  [import_types.NodeType.DatabaseNode]: databaseRootCauseShape,
@@ -1386,16 +1389,16 @@ function getRootCause(graph, errorNodeId, errorEvent, incidents) {
1386
1389
  const origin = graph.getNodeAttributes(errorNodeId);
1387
1390
  const shape = rootCauseShapes[origin.type];
1388
1391
  if (shape) {
1389
- const walk8 = longestIncomingWalk(graph, errorNodeId, ROOT_CAUSE_MAX_DEPTH);
1390
- const match = shape(graph, origin, walk8);
1392
+ const walk9 = longestIncomingWalk(graph, errorNodeId, ROOT_CAUSE_MAX_DEPTH);
1393
+ const match = shape(graph, origin, walk9);
1391
1394
  if (match) {
1392
1395
  const reason = errorEvent ? `${match.rootCauseReason} (observed error: ${errorEvent.errorMessage})` : match.rootCauseReason;
1393
1396
  return import_types.RootCauseResultSchema.parse({
1394
1397
  rootCauseNode: match.rootCauseNode,
1395
1398
  rootCauseReason: reason,
1396
- traversalPath: walk8.path,
1397
- edgeProvenances: walk8.edges.map((e) => e.provenance),
1398
- confidence: confidenceFromMix(walk8.edges),
1399
+ traversalPath: walk9.path,
1400
+ edgeProvenances: walk9.edges.map((e) => e.provenance),
1401
+ confidence: confidenceFromMix(walk9.edges),
1399
1402
  fixRecommendation: match.fixRecommendation
1400
1403
  });
1401
1404
  }
@@ -1496,26 +1499,26 @@ function dominantFailingCall(graph, serviceId9, visited) {
1496
1499
  return best;
1497
1500
  }
1498
1501
  function followFailingCallChain(graph, originServiceId, maxDepth) {
1499
- const path69 = [originServiceId];
1502
+ const path70 = [originServiceId];
1500
1503
  const edges = [];
1501
1504
  const visited = /* @__PURE__ */ new Set([originServiceId]);
1502
1505
  let current = originServiceId;
1503
1506
  for (let depth = 0; depth < maxDepth; depth++) {
1504
1507
  const hop = dominantFailingCall(graph, current, visited);
1505
1508
  if (!hop) break;
1506
- path69.push(hop.nextService);
1509
+ path70.push(hop.nextService);
1507
1510
  edges.push(hop.edge);
1508
1511
  visited.add(hop.nextService);
1509
1512
  current = hop.nextService;
1510
1513
  }
1511
1514
  if (edges.length === 0) return null;
1512
- return { path: path69, edges, culprit: current };
1515
+ return { path: path70, edges, culprit: current };
1513
1516
  }
1514
1517
  function crossServiceRootCause(graph, originId, incidents, errorEvent) {
1515
1518
  const chain = followFailingCallChain(graph, originId, ROOT_CAUSE_MAX_DEPTH);
1516
1519
  if (!chain) return null;
1517
1520
  const culprit = chain.culprit;
1518
- const path69 = [...chain.path];
1521
+ const path70 = [...chain.path];
1519
1522
  const edgeProvenances = chain.edges.map((e) => e.provenance);
1520
1523
  const baseConfidence = confidenceFromMix(chain.edges);
1521
1524
  const confidence = Math.max(0, Math.min(1, baseConfidence * INCIDENT_ROOT_CAUSE_CONFIDENCE));
@@ -1523,14 +1526,14 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
1523
1526
  if (loc) {
1524
1527
  let rootCauseNode = culprit;
1525
1528
  if (loc.fileNode) {
1526
- path69.push(loc.fileNode);
1529
+ path70.push(loc.fileNode);
1527
1530
  edgeProvenances.push(import_types.Provenance.OBSERVED);
1528
1531
  rootCauseNode = loc.fileNode;
1529
1532
  }
1530
1533
  return import_types.RootCauseResultSchema.parse({
1531
1534
  rootCauseNode,
1532
1535
  rootCauseReason: loc.rootCauseReason,
1533
- traversalPath: path69,
1536
+ traversalPath: path70,
1534
1537
  edgeProvenances,
1535
1538
  confidence,
1536
1539
  ...loc.fixRecommendation ? { fixRecommendation: loc.fixRecommendation } : {}
@@ -1542,7 +1545,7 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
1542
1545
  return import_types.RootCauseResultSchema.parse({
1543
1546
  rootCauseNode: culprit,
1544
1547
  rootCauseReason: `${culpritName} is failing downstream calls (${errs} observed error${errs === 1 ? "" : "s"})`,
1545
- traversalPath: path69,
1548
+ traversalPath: path70,
1546
1549
  edgeProvenances,
1547
1550
  confidence,
1548
1551
  fixRecommendation: `Inspect ${culpritName}'s failing handler`
@@ -2477,14 +2480,14 @@ function buildServiceHostIndex(services) {
2477
2480
  }
2478
2481
  async function walkSourceFiles(dir) {
2479
2482
  const out = [];
2480
- async function walk8(current) {
2483
+ async function walk9(current) {
2481
2484
  const entries = await import_node_fs5.promises.readdir(current, { withFileTypes: true }).catch(() => []);
2482
2485
  for (const entry2 of entries) {
2483
2486
  const full = import_node_path5.default.join(current, entry2.name);
2484
2487
  if (entry2.isDirectory()) {
2485
2488
  if (IGNORED_DIRS.has(entry2.name)) continue;
2486
2489
  if (await isPythonVenvDir(full)) continue;
2487
- await walk8(full);
2490
+ await walk9(full);
2488
2491
  } else if (entry2.isFile() && SERVICE_FILE_EXTENSIONS.has(import_node_path5.default.extname(entry2.name)) && // Skip NEAT's own generated `otel-init.*` bootstrap — extracting it
2489
2492
  // would attribute our instrumentation imports to the user's service.
2490
2493
  !isNeatAuthoredSourceFile(entry2.name)) {
@@ -2492,7 +2495,7 @@ async function walkSourceFiles(dir) {
2492
2495
  }
2493
2496
  }
2494
2497
  }
2495
- await walk8(dir);
2498
+ await walk9(dir);
2496
2499
  return out;
2497
2500
  }
2498
2501
  async function loadSourceFiles(dir) {
@@ -3005,8 +3008,9 @@ var ROUTER_METHODS = /* @__PURE__ */ new Set([
3005
3008
  "all"
3006
3009
  ]);
3007
3010
  var NEXT_APP_METHODS = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]);
3011
+ var NET_HTTP_METHODS = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]);
3008
3012
  var JS_ROUTE_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx"]);
3009
- function ginRoutesFromSource(source, parser) {
3013
+ function goRouterRoutesFromSource(source, parser, framework) {
3010
3014
  const tree = parseSource2(parser, source);
3011
3015
  const prefixes = /* @__PURE__ */ new Map();
3012
3016
  const out = [];
@@ -3016,10 +3020,12 @@ function ginRoutesFromSource(source, parser) {
3016
3020
  const value = node.childForFieldName("right")?.namedChild(0) ?? node.childForFieldName("value");
3017
3021
  if (name && value?.type === "call_expression") {
3018
3022
  const fn2 = value.childForFieldName("function");
3019
- const field = fn2?.childForFieldName("field")?.text;
3020
- const first2 = value.childForFieldName("arguments")?.namedChild(0);
3021
- if (field === "Group" && first2?.type === "interpreted_string_literal") {
3022
- prefixes.set(name, first2.text.slice(1, -1));
3023
+ if (fn2?.childForFieldName("field")?.text === "Group") {
3024
+ const leaf2 = goStringLiteral(value.childForFieldName("arguments")?.namedChild(0));
3025
+ if (leaf2 !== null) {
3026
+ const parent = fn2.childForFieldName("operand")?.text ?? "";
3027
+ prefixes.set(name, (prefixes.get(parent) ?? "") + leaf2);
3028
+ }
3023
3029
  }
3024
3030
  }
3025
3031
  return;
@@ -3030,18 +3036,127 @@ function ginRoutesFromSource(source, parser) {
3030
3036
  const method = fn.childForFieldName("field")?.text?.toUpperCase();
3031
3037
  if (!method || !ROUTER_METHODS.has(method.toLowerCase())) return;
3032
3038
  const receiver = fn.childForFieldName("operand")?.text ?? "";
3033
- const first = node.childForFieldName("arguments")?.namedChild(0);
3034
- if (first?.type !== "interpreted_string_literal") return;
3035
- const leaf = first.text.slice(1, -1);
3039
+ const leaf = goStringLiteral(node.childForFieldName("arguments")?.namedChild(0));
3040
+ if (leaf === null) return;
3036
3041
  out.push({
3037
- method: method === "ALL" ? "ALL" : method,
3042
+ method,
3038
3043
  pathTemplate: canonicalizeTemplate((prefixes.get(receiver) ?? "") + leaf),
3039
3044
  line: node.startPosition.row + 1,
3040
- framework: "gin"
3045
+ framework
3046
+ });
3047
+ });
3048
+ return out;
3049
+ }
3050
+ function goStringLiteral(node) {
3051
+ if (node?.type === "interpreted_string_literal" || node?.type === "raw_string_literal") {
3052
+ return node.text.slice(1, -1);
3053
+ }
3054
+ return null;
3055
+ }
3056
+ function ginRoutesFromSource(source, parser) {
3057
+ return goRouterRoutesFromSource(source, parser, "gin");
3058
+ }
3059
+ function echoRoutesFromSource(source, parser) {
3060
+ return goRouterRoutesFromSource(source, parser, "echo");
3061
+ }
3062
+ function fiberRoutesFromSource(source, parser) {
3063
+ return goRouterRoutesFromSource(source, parser, "fiber");
3064
+ }
3065
+ function chiRoutesFromSource(source, parser) {
3066
+ const tree = parseSource2(parser, source);
3067
+ const out = [];
3068
+ chiWalk(tree.rootNode, "", out);
3069
+ return out;
3070
+ }
3071
+ function stripChiRegex(path70) {
3072
+ return path70.replace(/\{([^{}:]+):[^{}]*\}/g, "{$1}");
3073
+ }
3074
+ function chiWalk(node, prefix, out) {
3075
+ for (let i = 0; i < node.namedChildCount; i++) {
3076
+ const child = node.namedChild(i);
3077
+ if (child) chiHandle(child, prefix, out);
3078
+ }
3079
+ }
3080
+ function chiHandle(node, prefix, out) {
3081
+ if (node.type === "call_expression") {
3082
+ const fn = node.childForFieldName("function");
3083
+ if (fn?.type === "selector_expression") {
3084
+ const field = fn.childForFieldName("field")?.text;
3085
+ const args = node.childForFieldName("arguments");
3086
+ if (field === "Route") {
3087
+ const leaf = goStringLiteral(args?.namedChild(0));
3088
+ const closure = args?.namedChild(1);
3089
+ if (leaf !== null && closure?.type === "func_literal") {
3090
+ const body = closure.childForFieldName("body");
3091
+ if (body) chiWalk(body, prefix + leaf, out);
3092
+ }
3093
+ return;
3094
+ }
3095
+ if (field === "Group") {
3096
+ const closure = args?.namedChild(0);
3097
+ if (closure?.type === "func_literal") {
3098
+ const body = closure.childForFieldName("body");
3099
+ if (body) chiWalk(body, prefix, out);
3100
+ }
3101
+ return;
3102
+ }
3103
+ if (field === "Mount") {
3104
+ return;
3105
+ }
3106
+ if (field && ROUTER_METHODS.has(field.toLowerCase())) {
3107
+ const leaf = goStringLiteral(args?.namedChild(0));
3108
+ if (leaf !== null) {
3109
+ out.push({
3110
+ method: field.toUpperCase(),
3111
+ pathTemplate: canonicalizeTemplate(stripChiRegex(prefix + leaf)),
3112
+ line: node.startPosition.row + 1,
3113
+ framework: "chi"
3114
+ });
3115
+ }
3116
+ return;
3117
+ }
3118
+ }
3119
+ }
3120
+ chiWalk(node, prefix, out);
3121
+ }
3122
+ function netHttpRoutesFromSource(source, parser) {
3123
+ const tree = parseSource2(parser, source);
3124
+ if (!goImportsNetHttp(tree.rootNode)) return [];
3125
+ const out = [];
3126
+ walk(tree.rootNode, (node) => {
3127
+ if (node.type !== "call_expression") return;
3128
+ const fn = node.childForFieldName("function");
3129
+ if (fn?.type !== "selector_expression") return;
3130
+ const field = fn.childForFieldName("field")?.text;
3131
+ if (field !== "HandleFunc" && field !== "Handle") return;
3132
+ const leaf = goStringLiteral(node.childForFieldName("arguments")?.namedChild(0));
3133
+ if (leaf === null) return;
3134
+ const sp = leaf.indexOf(" ");
3135
+ if (sp < 0) return;
3136
+ const method = leaf.slice(0, sp);
3137
+ const rest = leaf.slice(sp + 1);
3138
+ if (!NET_HTTP_METHODS.has(method)) return;
3139
+ if (!rest.startsWith("/")) return;
3140
+ out.push({
3141
+ method,
3142
+ pathTemplate: canonicalizeTemplate(rest),
3143
+ line: node.startPosition.row + 1,
3144
+ framework: "net/http"
3041
3145
  });
3042
3146
  });
3043
3147
  return out;
3044
3148
  }
3149
+ function goImportsNetHttp(root) {
3150
+ let found = false;
3151
+ walk(root, (node) => {
3152
+ if (found || node.type !== "import_spec") return;
3153
+ for (let i = 0; i < node.namedChildCount; i++) {
3154
+ const child = node.namedChild(i);
3155
+ if (goStringLiteral(child) === "net/http") found = true;
3156
+ }
3157
+ });
3158
+ return found;
3159
+ }
3045
3160
  var FASTAPI_METHODS = /* @__PURE__ */ new Set(["get", "post", "put", "patch", "delete", "options", "head", "trace"]);
3046
3161
  var NESTJS_METHODS = /* @__PURE__ */ new Map([
3047
3162
  ["Get", "GET"],
@@ -3626,9 +3741,9 @@ function rubyRocketRoute(args) {
3626
3741
  if (!pair || pair.type !== "pair") continue;
3627
3742
  const k = pair.childForFieldName("key");
3628
3743
  if (k?.type !== "string") continue;
3629
- const path69 = rubyLiteral(k);
3630
- if (path69 === null) continue;
3631
- return { path: path69, target: rubyLiteral(pair.childForFieldName("value")) };
3744
+ const path70 = rubyLiteral(k);
3745
+ if (path70 === null) continue;
3746
+ return { path: path70, target: rubyLiteral(pair.childForFieldName("value")) };
3632
3747
  }
3633
3748
  return null;
3634
3749
  }
@@ -4305,9 +4420,13 @@ async function addRoutes(graph, services) {
4305
4420
  const hasFlask = deps["flask"] !== void 0;
4306
4421
  const hasDjango = deps["django"] !== void 0;
4307
4422
  const hasGin = deps["github.com/gin-gonic/gin"] !== void 0;
4423
+ const hasEcho = deps["github.com/labstack/echo/v4"] !== void 0 || deps["github.com/labstack/echo"] !== void 0;
4424
+ const hasFiber = deps["github.com/gofiber/fiber/v2"] !== void 0 || deps["github.com/gofiber/fiber/v3"] !== void 0;
4425
+ const hasChi = deps["github.com/go-chi/chi/v5"] !== void 0 || deps["github.com/go-chi/chi"] !== void 0;
4426
+ const isGoService = service.node.language === "go";
4308
4427
  const hasRails = deps["rails"] !== void 0;
4309
4428
  const hasLaravel = deps["laravel/framework"] !== void 0;
4310
- if (!hasExpress && !hasFastify && !hasHono && !hasNext && !hasNestjs && !hasFastapi && !hasFlask && !hasDjango && !hasGin && !hasRails && !hasLaravel)
4429
+ if (!hasExpress && !hasFastify && !hasHono && !hasNext && !hasNestjs && !hasFastapi && !hasFlask && !hasDjango && !hasGin && !hasEcho && !hasFiber && !hasChi && !isGoService && !hasRails && !hasLaravel)
4311
4430
  continue;
4312
4431
  const files = await loadSourceFiles(service.dir);
4313
4432
  const mountPrefixes = hasExpress ? await expressMountPrefixes(files, service.dir, await loadTsPathConfig(service.dir)) : /* @__PURE__ */ new Map();
@@ -4331,7 +4450,12 @@ async function addRoutes(graph, services) {
4331
4450
  } else if (isRb) {
4332
4451
  routes = hasRails && relFile === "config/routes.rb" ? railsRoutesFromSource(file.content, rubyParser) : [];
4333
4452
  } else if (isGo) {
4334
- routes = hasGin ? ginRoutesFromSource(file.content, goParser) : [];
4453
+ if (hasGin) routes = ginRoutesFromSource(file.content, goParser);
4454
+ else if (hasEcho) routes = echoRoutesFromSource(file.content, goParser);
4455
+ else if (hasFiber) routes = fiberRoutesFromSource(file.content, goParser);
4456
+ else if (hasChi) routes = chiRoutesFromSource(file.content, goParser);
4457
+ else routes = [];
4458
+ routes = routes.concat(netHttpRoutesFromSource(file.content, goParser));
4335
4459
  } else if (isPy) {
4336
4460
  routes = hasFastapi || hasFlask ? pythonRoutesFromSource(file.content, pyParser, hasFastapi ? "fastapi" : "flask") : [];
4337
4461
  if (hasDjango) routes = routes.concat(djangoRoutesFromSource(file.content, pyParser));
@@ -5938,6 +6062,13 @@ function parseGoMod(source) {
5938
6062
  }
5939
6063
  return { module: module2, ...goVersion ? { goVersion } : {}, dependencies };
5940
6064
  }
6065
+ function goFramework(deps) {
6066
+ if (deps["github.com/gin-gonic/gin"]) return "gin";
6067
+ if (deps["github.com/labstack/echo/v4"] || deps["github.com/labstack/echo"]) return "echo";
6068
+ if (deps["github.com/gofiber/fiber/v2"] || deps["github.com/gofiber/fiber/v3"]) return "fiber";
6069
+ if (deps["github.com/go-chi/chi/v5"] || deps["github.com/go-chi/chi"]) return "chi";
6070
+ return void 0;
6071
+ }
5941
6072
  async function discoverGoService(scanPath, dir) {
5942
6073
  let raw;
5943
6074
  try {
@@ -5949,6 +6080,7 @@ async function discoverGoService(scanPath, dir) {
5949
6080
  if (!mod) return null;
5950
6081
  const name = mod.module.split("/").filter(Boolean).pop() ?? mod.module;
5951
6082
  const pkg = { name, dependencies: mod.dependencies };
6083
+ const framework = goFramework(mod.dependencies);
5952
6084
  const node = {
5953
6085
  id: (0, import_types9.serviceId)(name),
5954
6086
  type: import_types9.NodeType.ServiceNode,
@@ -5956,7 +6088,7 @@ async function discoverGoService(scanPath, dir) {
5956
6088
  language: "go",
5957
6089
  dependencies: mod.dependencies,
5958
6090
  repoPath: import_node_path10.default.relative(scanPath, dir),
5959
- ...mod.dependencies["github.com/gin-gonic/gin"] ? { framework: "gin" } : {}
6091
+ ...framework ? { framework } : {}
5960
6092
  };
5961
6093
  return { pkg, dir, node };
5962
6094
  }
@@ -6856,7 +6988,7 @@ async function addSymbolEdges(graph, services) {
6856
6988
  return best;
6857
6989
  };
6858
6990
  const requests = [];
6859
- const walk8 = (node) => {
6991
+ const walk9 = (node) => {
6860
6992
  if (node.type === "class_declaration" || node.type === "abstract_class_declaration" || node.type === "class") {
6861
6993
  const self = localBySpan.get(`${node.startPosition.row + 1}:${node.endPosition.row + 1}`);
6862
6994
  if (self && self.kind === "class") {
@@ -6902,10 +7034,10 @@ async function addSymbolEdges(graph, services) {
6902
7034
  }
6903
7035
  for (let i = 0; i < node.namedChildCount; i++) {
6904
7036
  const child = node.namedChild(i);
6905
- if (child) walk8(child);
7037
+ if (child) walk9(child);
6906
7038
  }
6907
7039
  };
6908
- walk8(root);
7040
+ walk9(root);
6909
7041
  for (const req2 of requests) {
6910
7042
  const targetSid = resolveTarget(req2.targetName, req2.wantKind);
6911
7043
  if (!targetSid) continue;
@@ -7918,20 +8050,20 @@ var import_node_path28 = __toESM(require("path"), 1);
7918
8050
  var import_types18 = require("@neat.is/types");
7919
8051
  async function walkConfigFiles(dir) {
7920
8052
  const out = [];
7921
- async function walk8(current) {
8053
+ async function walk9(current) {
7922
8054
  const entries = await import_node_fs16.promises.readdir(current, { withFileTypes: true });
7923
8055
  for (const entry2 of entries) {
7924
8056
  const full = import_node_path28.default.join(current, entry2.name);
7925
8057
  if (entry2.isDirectory()) {
7926
8058
  if (IGNORED_DIRS.has(entry2.name)) continue;
7927
8059
  if (await isPythonVenvDir(full)) continue;
7928
- await walk8(full);
8060
+ await walk9(full);
7929
8061
  } else if (entry2.isFile() && isConfigFile(entry2.name).match) {
7930
8062
  out.push(full);
7931
8063
  }
7932
8064
  }
7933
8065
  }
7934
- await walk8(dir);
8066
+ await walk9(dir);
7935
8067
  return out;
7936
8068
  }
7937
8069
  async function addConfigNodes(graph, services, scanPath) {
@@ -8021,20 +8153,20 @@ function grpcMethodsFromProto(content, fqPackage) {
8021
8153
  }
8022
8154
  async function walkProtoFiles(dir) {
8023
8155
  const out = [];
8024
- async function walk8(current) {
8156
+ async function walk9(current) {
8025
8157
  const entries = await import_node_fs17.promises.readdir(current, { withFileTypes: true }).catch(() => []);
8026
8158
  for (const entry2 of entries) {
8027
8159
  const full = import_node_path29.default.join(current, entry2.name);
8028
8160
  if (entry2.isDirectory()) {
8029
8161
  if (IGNORED_DIRS.has(entry2.name)) continue;
8030
8162
  if (await isPythonVenvDir(full)) continue;
8031
- await walk8(full);
8163
+ await walk9(full);
8032
8164
  } else if (entry2.isFile() && import_node_path29.default.extname(entry2.name) === PROTO_EXTENSION) {
8033
8165
  out.push(full);
8034
8166
  }
8035
8167
  }
8036
8168
  }
8037
- await walk8(dir);
8169
+ await walk9(dir);
8038
8170
  return out;
8039
8171
  }
8040
8172
  async function addGrpcMethods(graph, services) {
@@ -8102,7 +8234,7 @@ async function addGrpcMethods(graph, services) {
8102
8234
 
8103
8235
  // src/extract/calls/index.ts
8104
8236
  init_cjs_shims();
8105
- var import_types36 = require("@neat.is/types");
8237
+ var import_types37 = require("@neat.is/types");
8106
8238
 
8107
8239
  // src/extract/calls/http.ts
8108
8240
  init_cjs_shims();
@@ -8856,7 +8988,7 @@ function isFirestoreClientFactory(node) {
8856
8988
  }
8857
8989
  function firestoreClientVars(root) {
8858
8990
  const vars = /* @__PURE__ */ new Set();
8859
- const walk8 = (node) => {
8991
+ const walk9 = (node) => {
8860
8992
  if (node.type === "variable_declarator") {
8861
8993
  const name = node.childForFieldName("name");
8862
8994
  let value = node.childForFieldName("value");
@@ -8865,9 +8997,9 @@ function firestoreClientVars(root) {
8865
8997
  vars.add(name.text);
8866
8998
  }
8867
8999
  }
8868
- for (const c of namedChildren(node)) walk8(c);
9000
+ for (const c of namedChildren(node)) walk9(c);
8869
9001
  };
8870
- walk8(root);
9002
+ walk9(root);
8871
9003
  return vars;
8872
9004
  }
8873
9005
  function isClientExpr(node, clientVars) {
@@ -9022,7 +9154,7 @@ function firestoreEndpointsFromFile(file, serviceDir) {
9022
9154
  }
9023
9155
  s.add(field);
9024
9156
  };
9025
- const walk8 = (node) => {
9157
+ const walk9 = (node) => {
9026
9158
  if (node.type === "call_expression") {
9027
9159
  const fn = node.childForFieldName("function");
9028
9160
  const line = node.startPosition.row + 1;
@@ -9062,9 +9194,9 @@ function firestoreEndpointsFromFile(file, serviceDir) {
9062
9194
  }
9063
9195
  }
9064
9196
  }
9065
- for (const c of namedChildren(node)) walk8(c);
9197
+ for (const c of namedChildren(node)) walk9(c);
9066
9198
  };
9067
- walk8(tree.rootNode);
9199
+ walk9(tree.rootNode);
9068
9200
  const out = [];
9069
9201
  for (const [collPath, line] of collLine) {
9070
9202
  const byField = writes.get(collPath);
@@ -9859,7 +9991,7 @@ function drizzleEndpointsFromFile(file, serviceDir) {
9859
9991
  const tree = parseSource3(parserForExt2(import_node_path41.default.extname(file.path)), file.content);
9860
9992
  const out = [];
9861
9993
  const seen = /* @__PURE__ */ new Set();
9862
- const walk8 = (node) => {
9994
+ const walk9 = (node) => {
9863
9995
  if (node.type === "call_expression") {
9864
9996
  const fn = node.childForFieldName("function");
9865
9997
  if (fn?.type === "identifier" && TABLE_BUILDERS.has(fn.text)) {
@@ -9887,9 +10019,9 @@ function drizzleEndpointsFromFile(file, serviceDir) {
9887
10019
  }
9888
10020
  }
9889
10021
  }
9890
- for (const c of namedChildren4(node)) walk8(c);
10022
+ for (const c of namedChildren4(node)) walk9(c);
9891
10023
  };
9892
- walk8(tree.rootNode);
10024
+ walk9(tree.rootNode);
9893
10025
  return out;
9894
10026
  }
9895
10027
  function enclosingVarName(call) {
@@ -9911,7 +10043,7 @@ function enclosingVarName(call) {
9911
10043
  function collectDrizzleTables(root) {
9912
10044
  const tables = [];
9913
10045
  const varToTable = /* @__PURE__ */ new Map();
9914
- const walk8 = (node) => {
10046
+ const walk9 = (node) => {
9915
10047
  if (node.type === "call_expression") {
9916
10048
  const fn = node.childForFieldName("function");
9917
10049
  if (fn?.type === "identifier" && TABLE_BUILDERS.has(fn.text)) {
@@ -9926,9 +10058,9 @@ function collectDrizzleTables(root) {
9926
10058
  }
9927
10059
  }
9928
10060
  }
9929
- for (const c of namedChildren4(node)) walk8(c);
10061
+ for (const c of namedChildren4(node)) walk9(c);
9930
10062
  };
9931
- walk8(root);
10063
+ walk9(root);
9932
10064
  return { tables, varToTable };
9933
10065
  }
9934
10066
  function referencesTargetVar(call) {
@@ -9951,7 +10083,7 @@ function drizzleForeignKeys(file, serviceDir) {
9951
10083
  const seen = /* @__PURE__ */ new Set();
9952
10084
  for (const table of tables) {
9953
10085
  if (!table.object) continue;
9954
- const walk8 = (node) => {
10086
+ const walk9 = (node) => {
9955
10087
  if (node.type === "call_expression") {
9956
10088
  const targetVar = referencesTargetVar(node);
9957
10089
  const parentTable = targetVar ? varToTable.get(targetVar) : void 0;
@@ -9972,9 +10104,9 @@ function drizzleForeignKeys(file, serviceDir) {
9972
10104
  }
9973
10105
  }
9974
10106
  }
9975
- for (const c of namedChildren4(node)) walk8(c);
10107
+ for (const c of namedChildren4(node)) walk9(c);
9976
10108
  };
9977
- walk8(table.object);
10109
+ walk9(table.object);
9978
10110
  }
9979
10111
  return out;
9980
10112
  }
@@ -11054,15 +11186,531 @@ function goSqlEndpointsFromFile(file, serviceDir) {
11054
11186
  return out;
11055
11187
  }
11056
11188
 
11189
+ // src/extract/calls/gorm.ts
11190
+ init_cjs_shims();
11191
+ var import_node_path48 = __toESM(require("path"), 1);
11192
+ var import_tree_sitter15 = __toESM(require("tree-sitter"), 1);
11193
+ var import_tree_sitter_go4 = __toESM(require("tree-sitter-go"), 1);
11194
+ var import_types36 = require("@neat.is/types");
11195
+ var GORM_IMPORT_RE = /gorm\.io\/gorm/;
11196
+ var PARSE_CHUNK11 = 16384;
11197
+ function makeGoParser3() {
11198
+ const p = new import_tree_sitter15.default();
11199
+ p.setLanguage(import_tree_sitter_go4.default);
11200
+ return p;
11201
+ }
11202
+ function parseSource10(parser, source) {
11203
+ return parser.parse(
11204
+ (index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK11)
11205
+ );
11206
+ }
11207
+ function walk8(node, visit) {
11208
+ visit(node);
11209
+ for (let i = 0; i < node.namedChildCount; i++) {
11210
+ const c = node.namedChild(i);
11211
+ if (c) walk8(c, visit);
11212
+ }
11213
+ }
11214
+ var COMMON_INITIALISMS = [
11215
+ "ASCII",
11216
+ "HTTPS",
11217
+ "UTF8",
11218
+ "XSRF",
11219
+ "HTML",
11220
+ "HTTP",
11221
+ "JSON",
11222
+ "UUID",
11223
+ "XMPP",
11224
+ "ACL",
11225
+ "API",
11226
+ "CPU",
11227
+ "CSS",
11228
+ "DNS",
11229
+ "EOF",
11230
+ "GUID",
11231
+ "LHS",
11232
+ "QPS",
11233
+ "RAM",
11234
+ "RHS",
11235
+ "RPC",
11236
+ "SLA",
11237
+ "SQL",
11238
+ "SSH",
11239
+ "TCP",
11240
+ "TLS",
11241
+ "TTL",
11242
+ "UDP",
11243
+ "UID",
11244
+ "URI",
11245
+ "URL",
11246
+ "UID",
11247
+ "XSS",
11248
+ "ID",
11249
+ "IP",
11250
+ "UI",
11251
+ "VM",
11252
+ "XML"
11253
+ ].sort((a, b) => b.length - a.length);
11254
+ function titleCase(word) {
11255
+ return word.charAt(0) + word.slice(1).toLowerCase();
11256
+ }
11257
+ function replaceInitialisms(name) {
11258
+ let out = "";
11259
+ let i = 0;
11260
+ while (i < name.length) {
11261
+ let matched = false;
11262
+ for (const init of COMMON_INITIALISMS) {
11263
+ if (name.startsWith(init, i)) {
11264
+ out += titleCase(init);
11265
+ i += init.length;
11266
+ matched = true;
11267
+ break;
11268
+ }
11269
+ }
11270
+ if (!matched) {
11271
+ out += name[i];
11272
+ i++;
11273
+ }
11274
+ }
11275
+ return out;
11276
+ }
11277
+ var isUpper = (c) => c >= "A" && c <= "Z";
11278
+ var isDigit = (c) => c >= "0" && c <= "9";
11279
+ function toDBName(name) {
11280
+ if (name === "") return "";
11281
+ const value = replaceInitialisms(name);
11282
+ if (value.length === 1) return value.toLowerCase();
11283
+ let buf = "";
11284
+ let lastCase = false;
11285
+ let curCase = isUpper(value[0]);
11286
+ for (let i = 0; i < value.length - 1; i++) {
11287
+ const v = value[i];
11288
+ const nextCase = isUpper(value[i + 1]);
11289
+ const nextNumber = isDigit(value[i + 1]);
11290
+ if (curCase) {
11291
+ if (lastCase && (nextCase || nextNumber)) {
11292
+ buf += v.toLowerCase();
11293
+ } else {
11294
+ if (i > 0 && value[i - 1] !== "_" && lastCase !== curCase) buf += "_";
11295
+ buf += v.toLowerCase();
11296
+ }
11297
+ } else {
11298
+ buf += v;
11299
+ }
11300
+ lastCase = curCase;
11301
+ curCase = nextCase;
11302
+ }
11303
+ const last = value[value.length - 1];
11304
+ if (curCase) {
11305
+ if (!lastCase && value.length > 1) buf += "_";
11306
+ buf += last.toLowerCase();
11307
+ } else {
11308
+ buf += last;
11309
+ }
11310
+ return buf;
11311
+ }
11312
+ var UNCOUNTABLE = /* @__PURE__ */ new Set([
11313
+ "equipment",
11314
+ "information",
11315
+ "rice",
11316
+ "money",
11317
+ "species",
11318
+ "series",
11319
+ "fish",
11320
+ "sheep",
11321
+ "jeans",
11322
+ "police"
11323
+ ]);
11324
+ var IRREGULAR = [
11325
+ ["person", "people"],
11326
+ ["man", "men"],
11327
+ ["child", "children"],
11328
+ ["sex", "sexes"],
11329
+ ["move", "moves"]
11330
+ ];
11331
+ var PLURAL_RULES = [
11332
+ [/(quiz)$/i, "$1zes"],
11333
+ [/^(ox)$/i, "$1en"],
11334
+ [/([ml])ouse$/i, "$1ice"],
11335
+ [/(matr|vert|ind)(?:ix|ex)$/i, "$1ices"],
11336
+ [/(x|ch|ss|sh)$/i, "$1es"],
11337
+ [/([^aeiouy]|qu)y$/i, "$1ies"],
11338
+ [/(hive)$/i, "$1s"],
11339
+ [/(?:([^f])fe|([lr])f)$/i, "$1$2ves"],
11340
+ [/sis$/i, "ses"],
11341
+ [/([ti])um$/i, "$1a"],
11342
+ [/([ti])a$/i, "$1a"],
11343
+ [/(buffal|tomat)o$/i, "$1oes"],
11344
+ [/(bu)s$/i, "$1ses"],
11345
+ [/(alias|status)$/i, "$1es"],
11346
+ [/(octop|vir)i$/i, "$1i"],
11347
+ [/(octop|vir)us$/i, "$1i"],
11348
+ [/(ax|test)is$/i, "$1es"],
11349
+ [/s$/i, "s"]
11350
+ ];
11351
+ function pluralize3(word) {
11352
+ if (word === "") return word;
11353
+ const lower = word.toLowerCase();
11354
+ for (const u of UNCOUNTABLE) {
11355
+ if (lower === u || lower.endsWith("_" + u)) return word;
11356
+ }
11357
+ for (const [sing, plur] of IRREGULAR) {
11358
+ const re = new RegExp(sing + "$", "i");
11359
+ if (re.test(word)) return word.replace(re, plur);
11360
+ }
11361
+ for (const [re, rep] of PLURAL_RULES) {
11362
+ if (re.test(word)) return word.replace(re, rep);
11363
+ }
11364
+ return word + "s";
11365
+ }
11366
+ function deriveTableName(structName) {
11367
+ return pluralize3(toDBName(structName));
11368
+ }
11369
+ function stringLiteralValue(node) {
11370
+ if (!node) return null;
11371
+ if (node.type === "interpreted_string_literal" || node.type === "raw_string_literal") {
11372
+ const t = node.text;
11373
+ return t.length >= 2 ? t.slice(1, -1) : "";
11374
+ }
11375
+ return null;
11376
+ }
11377
+ function parseGormTag(tagNode) {
11378
+ const tag = {};
11379
+ if (!tagNode) return tag;
11380
+ let inner = tagNode.text;
11381
+ if (inner.length >= 2) inner = inner.slice(1, -1);
11382
+ if (tagNode.type === "interpreted_string_literal") inner = inner.replace(/\\"/g, '"');
11383
+ const m = inner.match(/gorm:"([^"]*)"/);
11384
+ if (!m) return tag;
11385
+ for (const part of m[1].split(";")) {
11386
+ if (part === "") continue;
11387
+ const idx = part.indexOf(":");
11388
+ const key = (idx >= 0 ? part.slice(0, idx) : part).trim().toLowerCase();
11389
+ const value = idx >= 0 ? part.slice(idx + 1).trim() : "";
11390
+ if (key === "-") tag.skip = true;
11391
+ else if (key === "column") tag.column = value;
11392
+ else if (key === "primarykey" || key === "primary_key") tag.primaryKey = true;
11393
+ else if (key === "foreignkey") tag.foreignKey = value;
11394
+ else if (key === "many2many") tag.many2many = value;
11395
+ else if (key === "embedded") tag.embedded = true;
11396
+ else if (key === "embeddedprefix") tag.embeddedPrefix = value;
11397
+ }
11398
+ return tag;
11399
+ }
11400
+ function unwrapType(typeNode) {
11401
+ let isSlice = false;
11402
+ let isPointer = false;
11403
+ let n = typeNode;
11404
+ while (n && (n.type === "slice_type" || n.type === "array_type" || n.type === "pointer_type")) {
11405
+ if (n.type === "slice_type" || n.type === "array_type") isSlice = true;
11406
+ if (n.type === "pointer_type") isPointer = true;
11407
+ n = n.childForFieldName("element") ?? n.namedChild(n.namedChildCount - 1);
11408
+ }
11409
+ if (!n) return { name: null, qualifier: null, isSlice, isPointer, isQualified: false };
11410
+ if (n.type === "type_identifier") {
11411
+ return { name: n.text, qualifier: null, isSlice, isPointer, isQualified: false };
11412
+ }
11413
+ if (n.type === "qualified_type") {
11414
+ const pkg = n.childForFieldName("package")?.text ?? n.namedChild(0)?.text ?? null;
11415
+ const nm = n.childForFieldName("name")?.text ?? n.namedChild(1)?.text ?? null;
11416
+ return { name: nm, qualifier: pkg, isSlice, isPointer, isQualified: true };
11417
+ }
11418
+ return { name: null, qualifier: null, isSlice, isPointer, isQualified: false };
11419
+ }
11420
+ function readField(fieldDecl) {
11421
+ const names = [];
11422
+ let tagNode = null;
11423
+ for (let i = 0; i < fieldDecl.namedChildCount; i++) {
11424
+ const c = fieldDecl.namedChild(i);
11425
+ if (!c) continue;
11426
+ if (c.type === "field_identifier") names.push(c.text);
11427
+ else if (c.type === "raw_string_literal" || c.type === "interpreted_string_literal") tagNode = c;
11428
+ }
11429
+ const typeNode = fieldDecl.childForFieldName("type");
11430
+ const t = unwrapType(typeNode);
11431
+ return {
11432
+ names,
11433
+ typeName: t.name,
11434
+ qualifier: t.qualifier,
11435
+ isSlice: t.isSlice,
11436
+ isPointer: t.isPointer,
11437
+ isQualified: t.isQualified,
11438
+ tag: parseGormTag(tagNode),
11439
+ line: fieldDecl.startPosition.row + 1
11440
+ };
11441
+ }
11442
+ function collectStructs(tree) {
11443
+ const structs = /* @__PURE__ */ new Map();
11444
+ walk8(tree.rootNode, (node) => {
11445
+ if (node.type !== "type_spec") return;
11446
+ const nameNode = node.childForFieldName("name");
11447
+ const typeNode = node.childForFieldName("type");
11448
+ if (!nameNode || typeNode?.type !== "struct_type") return;
11449
+ const list = typeNode.childForFieldName("body") ?? typeNode.namedChild(0);
11450
+ const fields = [];
11451
+ if (list && list.type === "field_declaration_list") {
11452
+ for (let i = 0; i < list.namedChildCount; i++) {
11453
+ const fd = list.namedChild(i);
11454
+ if (fd?.type === "field_declaration") fields.push(readField(fd));
11455
+ }
11456
+ }
11457
+ structs.set(nameNode.text, {
11458
+ name: nameNode.text,
11459
+ fields,
11460
+ line: node.startPosition.row + 1
11461
+ });
11462
+ });
11463
+ return structs;
11464
+ }
11465
+ var GORM_MODEL_METHODS = /* @__PURE__ */ new Set([
11466
+ "AutoMigrate",
11467
+ "Model",
11468
+ "Create",
11469
+ "Find",
11470
+ "First",
11471
+ "Take",
11472
+ "Last",
11473
+ "Save",
11474
+ "Delete",
11475
+ "Where",
11476
+ "FirstOrCreate",
11477
+ "FirstOrInit"
11478
+ ]);
11479
+ function compositeStructName(arg) {
11480
+ let n = arg;
11481
+ if (n.type === "unary_expression") n = n.childForFieldName("operand") ?? n.namedChild(0);
11482
+ if (!n || n.type !== "composite_literal") return null;
11483
+ const typeNode = n.childForFieldName("type");
11484
+ if (!typeNode) return null;
11485
+ if (typeNode.type === "type_identifier") return typeNode.text;
11486
+ if (typeNode.type === "qualified_type") {
11487
+ return typeNode.childForFieldName("name")?.text ?? typeNode.namedChild(1)?.text ?? null;
11488
+ }
11489
+ return null;
11490
+ }
11491
+ function collectCallModels(tree) {
11492
+ const models = /* @__PURE__ */ new Set();
11493
+ walk8(tree.rootNode, (node) => {
11494
+ if (node.type !== "call_expression") return;
11495
+ const fn = node.childForFieldName("function");
11496
+ if (fn?.type !== "selector_expression") return;
11497
+ const method = fn.childForFieldName("field")?.text;
11498
+ if (!method || !GORM_MODEL_METHODS.has(method)) return;
11499
+ const args = node.childForFieldName("arguments");
11500
+ if (!args) return;
11501
+ for (let i = 0; i < args.namedChildCount; i++) {
11502
+ const arg = args.namedChild(i);
11503
+ if (!arg) continue;
11504
+ const name = compositeStructName(arg);
11505
+ if (name) models.add(name);
11506
+ }
11507
+ });
11508
+ return models;
11509
+ }
11510
+ function collectTableNameOverrides(tree) {
11511
+ const overrides = /* @__PURE__ */ new Map();
11512
+ const declarers = /* @__PURE__ */ new Set();
11513
+ walk8(tree.rootNode, (node) => {
11514
+ if (node.type !== "method_declaration") return;
11515
+ if (node.childForFieldName("name")?.text !== "TableName") return;
11516
+ const receiver = node.childForFieldName("receiver");
11517
+ if (!receiver) return;
11518
+ let recvType = null;
11519
+ for (let i = 0; i < receiver.namedChildCount; i++) {
11520
+ const pd = receiver.namedChild(i);
11521
+ if (pd?.type !== "parameter_declaration") continue;
11522
+ const t = unwrapType(pd.childForFieldName("type"));
11523
+ recvType = t.name;
11524
+ }
11525
+ if (!recvType) return;
11526
+ declarers.add(recvType);
11527
+ const body = node.childForFieldName("body");
11528
+ if (!body) return;
11529
+ let literal = null;
11530
+ walk8(body, (n) => {
11531
+ if (literal !== null) return;
11532
+ if (n.type !== "return_statement") return;
11533
+ const exprList = n.namedChild(0);
11534
+ const first = exprList?.namedChild(0) ?? exprList;
11535
+ const v = stringLiteralValue(first);
11536
+ if (v) literal = v;
11537
+ });
11538
+ if (literal !== null) overrides.set(recvType, literal);
11539
+ });
11540
+ return { overrides, declarers };
11541
+ }
11542
+ function isRelationField(field, structs) {
11543
+ if (field.names.length === 0) return false;
11544
+ if (field.isQualified) return false;
11545
+ if (!field.typeName) return false;
11546
+ return structs.has(field.typeName);
11547
+ }
11548
+ function isGormModelEmbed(field) {
11549
+ return field.names.length === 0 && field.qualifier === "gorm" && field.typeName === "Model";
11550
+ }
11551
+ function analyze(tree) {
11552
+ const structs = collectStructs(tree);
11553
+ const { overrides, declarers } = collectTableNameOverrides(tree);
11554
+ const callModels = collectCallModels(tree);
11555
+ const models = /* @__PURE__ */ new Set();
11556
+ for (const [name, info] of structs) {
11557
+ if (info.fields.some(isGormModelEmbed)) models.add(name);
11558
+ }
11559
+ for (const name of callModels) if (structs.has(name)) models.add(name);
11560
+ for (const name of declarers) if (structs.has(name)) models.add(name);
11561
+ let grew = true;
11562
+ while (grew) {
11563
+ grew = false;
11564
+ for (const name of Array.from(models)) {
11565
+ const info = structs.get(name);
11566
+ if (!info) continue;
11567
+ for (const field of info.fields) {
11568
+ if (!isRelationField(field, structs)) continue;
11569
+ const target = field.typeName;
11570
+ if (!models.has(target) && structs.has(target)) {
11571
+ models.add(target);
11572
+ grew = true;
11573
+ }
11574
+ }
11575
+ }
11576
+ }
11577
+ const tableFor = (structName) => overrides.get(structName) ?? deriveTableName(structName);
11578
+ return { structs, models, tableFor };
11579
+ }
11580
+ function collectColumns(struct, structs, seen, prefix, out, emitted) {
11581
+ if (seen.has(struct.name)) return;
11582
+ seen.add(struct.name);
11583
+ const add = (col) => {
11584
+ const full = prefix + col;
11585
+ if (!emitted.has(full)) {
11586
+ emitted.add(full);
11587
+ out.push(full);
11588
+ }
11589
+ };
11590
+ for (const field of struct.fields) {
11591
+ if (field.tag.skip) continue;
11592
+ if (field.names.length === 0) {
11593
+ if (isGormModelEmbed(field)) {
11594
+ add("id");
11595
+ add("created_at");
11596
+ add("updated_at");
11597
+ add("deleted_at");
11598
+ } else if (!field.isQualified && field.typeName && structs.has(field.typeName)) {
11599
+ collectColumns(structs.get(field.typeName), structs, seen, prefix, out, emitted);
11600
+ }
11601
+ continue;
11602
+ }
11603
+ if (field.tag.embedded && !field.isQualified && field.typeName && structs.has(field.typeName)) {
11604
+ collectColumns(
11605
+ structs.get(field.typeName),
11606
+ structs,
11607
+ seen,
11608
+ prefix + (field.tag.embeddedPrefix ?? ""),
11609
+ out,
11610
+ emitted
11611
+ );
11612
+ continue;
11613
+ }
11614
+ if (isRelationField(field, structs)) continue;
11615
+ if (field.names.length === 1 && field.tag.column) {
11616
+ add(field.tag.column);
11617
+ } else {
11618
+ for (const n of field.names) add(toDBName(n));
11619
+ }
11620
+ }
11621
+ seen.delete(struct.name);
11622
+ }
11623
+ function gormEndpointsFromFile(file, serviceDir) {
11624
+ if (import_node_path48.default.extname(file.path) !== ".go") return [];
11625
+ if (!GORM_IMPORT_RE.test(file.content)) return [];
11626
+ const tree = parseSource10(makeGoParser3(), file.content);
11627
+ const { structs, models, tableFor } = analyze(tree);
11628
+ const out = [];
11629
+ const seenTables = /* @__PURE__ */ new Set();
11630
+ for (const name of models) {
11631
+ const struct = structs.get(name);
11632
+ if (!struct) continue;
11633
+ const table = tableFor(name);
11634
+ if (seenTables.has(table)) continue;
11635
+ seenTables.add(table);
11636
+ const columns = [];
11637
+ collectColumns(struct, structs, /* @__PURE__ */ new Set(), "", columns, /* @__PURE__ */ new Set());
11638
+ out.push({
11639
+ infraId: (0, import_types36.infraId)("sql-table", table),
11640
+ name: table,
11641
+ kind: "sql-table",
11642
+ edgeType: "CALLS",
11643
+ confidenceKind: "structural",
11644
+ ...columns.length > 0 ? { columns } : {},
11645
+ evidence: {
11646
+ file: toPosix(import_node_path48.default.relative(serviceDir, file.path)),
11647
+ line: struct.line,
11648
+ snippet: snippet(file.content, struct.line)
11649
+ }
11650
+ });
11651
+ }
11652
+ return out;
11653
+ }
11654
+ function gormForeignKeys(file, serviceDir) {
11655
+ if (import_node_path48.default.extname(file.path) !== ".go") return [];
11656
+ if (!GORM_IMPORT_RE.test(file.content)) return [];
11657
+ const tree = parseSource10(makeGoParser3(), file.content);
11658
+ const { structs, models, tableFor } = analyze(tree);
11659
+ const out = [];
11660
+ const seen = /* @__PURE__ */ new Set();
11661
+ const emit = (childTable, parentTable, line) => {
11662
+ if (!childTable || !parentTable || childTable === parentTable) return;
11663
+ const key = `${childTable}->${parentTable}`;
11664
+ if (seen.has(key)) return;
11665
+ seen.add(key);
11666
+ out.push({
11667
+ childTable,
11668
+ parentTable,
11669
+ evidence: {
11670
+ file: toPosix(import_node_path48.default.relative(serviceDir, file.path)),
11671
+ line,
11672
+ snippet: snippet(file.content, line)
11673
+ }
11674
+ });
11675
+ };
11676
+ for (const name of models) {
11677
+ const struct = structs.get(name);
11678
+ if (!struct) continue;
11679
+ const thisTable = tableFor(name);
11680
+ const scalarNames = new Set(
11681
+ struct.fields.filter((f) => f.names.length > 0 && !isRelationField(f, structs)).flatMap((f) => f.names)
11682
+ );
11683
+ for (const field of struct.fields) {
11684
+ if (field.tag.skip) continue;
11685
+ if (!isRelationField(field, structs)) continue;
11686
+ const relTable = tableFor(field.typeName);
11687
+ if (field.tag.many2many) {
11688
+ emit(field.tag.many2many, thisTable, field.line);
11689
+ emit(field.tag.many2many, relTable, field.line);
11690
+ continue;
11691
+ }
11692
+ if (field.isSlice) {
11693
+ emit(relTable, thisTable, field.line);
11694
+ continue;
11695
+ }
11696
+ const convFk = field.names[0] + "ID";
11697
+ const belongsTo = scalarNames.has(convFk) || (field.tag.foreignKey ? scalarNames.has(field.tag.foreignKey) : false);
11698
+ if (belongsTo) emit(thisTable, relTable, field.line);
11699
+ else emit(relTable, thisTable, field.line);
11700
+ }
11701
+ }
11702
+ return out;
11703
+ }
11704
+
11057
11705
  // src/extract/calls/index.ts
11058
11706
  function edgeTypeFromEndpoint(ep) {
11059
11707
  switch (ep.edgeType) {
11060
11708
  case "PUBLISHES_TO":
11061
- return import_types36.EdgeType.PUBLISHES_TO;
11709
+ return import_types37.EdgeType.PUBLISHES_TO;
11062
11710
  case "CONSUMES_FROM":
11063
- return import_types36.EdgeType.CONSUMES_FROM;
11711
+ return import_types37.EdgeType.CONSUMES_FROM;
11064
11712
  default:
11065
- return import_types36.EdgeType.CALLS;
11713
+ return import_types37.EdgeType.CALLS;
11066
11714
  }
11067
11715
  }
11068
11716
  function isAwsKind(kind) {
@@ -11095,6 +11743,11 @@ async function addExternalEndpointEdges(graph, services) {
11095
11743
  } catch (err) {
11096
11744
  recordExtractionError("go SQL call extraction", file.path, err);
11097
11745
  }
11746
+ try {
11747
+ endpoints.push(...gormEndpointsFromFile(file, service.dir));
11748
+ } catch (err) {
11749
+ recordExtractionError("gorm data-axis extraction", file.path, err);
11750
+ }
11098
11751
  try {
11099
11752
  endpoints.push(...railsSchemaEndpointsFromFile(file, service.dir));
11100
11753
  endpoints.push(...railsModelEndpointsFromFile(file, service.dir));
@@ -11117,7 +11770,7 @@ async function addExternalEndpointEdges(graph, services) {
11117
11770
  if (!graph.hasNode(ep.infraId)) {
11118
11771
  const node = {
11119
11772
  id: ep.infraId,
11120
- type: import_types36.NodeType.InfraNode,
11773
+ type: import_types37.NodeType.InfraNode,
11121
11774
  name: ep.name,
11122
11775
  // #238 — `aws-*` covers AWS-SDK client kinds (aws-s3, aws-dynamodb,
11123
11776
  // aws-cognito-identity-provider, …); `s3-` / `dynamodb-` cover the
@@ -11130,21 +11783,21 @@ async function addExternalEndpointEdges(graph, services) {
11130
11783
  }
11131
11784
  if (ep.columns && ep.columns.length > 0) {
11132
11785
  const node = graph.getNodeAttributes(ep.infraId);
11133
- if (node.type === import_types36.NodeType.InfraNode) {
11786
+ if (node.type === import_types37.NodeType.InfraNode) {
11134
11787
  graph.replaceNodeAttributes(ep.infraId, {
11135
11788
  ...node,
11136
11789
  columns: foldColumns(
11137
11790
  node.columns,
11138
11791
  ep.columns,
11139
- import_types36.Provenance.EXTRACTED,
11140
- (0, import_types36.confidenceForExtracted)(ep.confidenceKind)
11792
+ import_types37.Provenance.EXTRACTED,
11793
+ (0, import_types37.confidenceForExtracted)(ep.confidenceKind)
11141
11794
  )
11142
11795
  });
11143
11796
  }
11144
11797
  }
11145
11798
  if (ep.sdkWrites && Object.keys(ep.sdkWrites).length > 0) {
11146
11799
  const node = graph.getNodeAttributes(ep.infraId);
11147
- if (node.type === import_types36.NodeType.InfraNode) {
11800
+ if (node.type === import_types37.NodeType.InfraNode) {
11148
11801
  graph.replaceNodeAttributes(ep.infraId, {
11149
11802
  ...node,
11150
11803
  columns: foldSdkWrites(node.columns, ep.sdkWrites)
@@ -11152,7 +11805,7 @@ async function addExternalEndpointEdges(graph, services) {
11152
11805
  }
11153
11806
  }
11154
11807
  const edgeType = edgeTypeFromEndpoint(ep);
11155
- const confidence = (0, import_types36.confidenceForExtracted)(ep.confidenceKind);
11808
+ const confidence = (0, import_types37.confidenceForExtracted)(ep.confidenceKind);
11156
11809
  const relFile = toPosix(ep.evidence.file);
11157
11810
  const { fileNodeId, nodesAdded: n, edgesAdded: e } = ensureFileNode(
11158
11811
  graph,
@@ -11162,7 +11815,7 @@ async function addExternalEndpointEdges(graph, services) {
11162
11815
  );
11163
11816
  nodesAdded += n;
11164
11817
  edgesAdded += e;
11165
- if (!(0, import_types36.passesExtractedFloor)(confidence)) {
11818
+ if (!(0, import_types37.passesExtractedFloor)(confidence)) {
11166
11819
  noteExtractedDropped({
11167
11820
  source: fileNodeId,
11168
11821
  target: ep.infraId,
@@ -11182,7 +11835,7 @@ async function addExternalEndpointEdges(graph, services) {
11182
11835
  source: fileNodeId,
11183
11836
  target: ep.infraId,
11184
11837
  type: edgeType,
11185
- provenance: import_types36.Provenance.EXTRACTED,
11838
+ provenance: import_types37.Provenance.EXTRACTED,
11186
11839
  confidence,
11187
11840
  evidence: ep.evidence
11188
11841
  };
@@ -11205,7 +11858,7 @@ async function addCallEdges(graph, services) {
11205
11858
 
11206
11859
  // src/extract/table-edges.ts
11207
11860
  init_cjs_shims();
11208
- var import_types37 = require("@neat.is/types");
11861
+ var import_types38 = require("@neat.is/types");
11209
11862
  async function addTableEdges(graph, services) {
11210
11863
  let nodesAdded = 0;
11211
11864
  let edgesAdded = 0;
@@ -11219,6 +11872,7 @@ async function addTableEdges(graph, services) {
11219
11872
  refs.push(...sqlalchemyForeignKeys(file, service.dir));
11220
11873
  refs.push(...railsSchemaForeignKeys(file, service.dir));
11221
11874
  refs.push(...laravelMigrationForeignKeys(file, service.dir));
11875
+ refs.push(...gormForeignKeys(file, service.dir));
11222
11876
  modelRefs.push(...railsModelForeignKeys(file, service.dir));
11223
11877
  modelRefs.push(...laravelModelForeignKeys(file, service.dir));
11224
11878
  } catch (err) {
@@ -11232,20 +11886,20 @@ async function addTableEdges(graph, services) {
11232
11886
  }
11233
11887
  refs.push(...modelRefs);
11234
11888
  for (const ref of refs) {
11235
- const childId = (0, import_types37.infraId)("sql-table", ref.childTable);
11236
- const parentId = (0, import_types37.infraId)("sql-table", ref.parentTable);
11889
+ const childId = (0, import_types38.infraId)("sql-table", ref.childTable);
11890
+ const parentId = (0, import_types38.infraId)("sql-table", ref.parentTable);
11237
11891
  if (childId === parentId) continue;
11238
11892
  nodesAdded += ensureTableNode(graph, childId, ref.childTable);
11239
11893
  nodesAdded += ensureTableNode(graph, parentId, ref.parentTable);
11240
- const edgeId = (0, import_types37.extractedEdgeId)(childId, parentId, import_types37.EdgeType.REFERENCES);
11894
+ const edgeId = (0, import_types38.extractedEdgeId)(childId, parentId, import_types38.EdgeType.REFERENCES);
11241
11895
  if (graph.hasEdge(edgeId)) continue;
11242
11896
  const edge = {
11243
11897
  id: edgeId,
11244
11898
  source: childId,
11245
11899
  target: parentId,
11246
- type: import_types37.EdgeType.REFERENCES,
11247
- provenance: import_types37.Provenance.EXTRACTED,
11248
- confidence: (0, import_types37.confidenceForExtracted)("structural"),
11900
+ type: import_types38.EdgeType.REFERENCES,
11901
+ provenance: import_types38.Provenance.EXTRACTED,
11902
+ confidence: (0, import_types38.confidenceForExtracted)("structural"),
11249
11903
  evidence: ref.evidence
11250
11904
  };
11251
11905
  graph.addEdgeWithKey(edgeId, childId, parentId, edge);
@@ -11258,7 +11912,7 @@ function ensureTableNode(graph, id, name) {
11258
11912
  if (graph.hasNode(id)) return 0;
11259
11913
  const node = {
11260
11914
  id,
11261
- type: import_types37.NodeType.InfraNode,
11915
+ type: import_types38.NodeType.InfraNode,
11262
11916
  name,
11263
11917
  provider: "self",
11264
11918
  kind: "sql-table"
@@ -11272,16 +11926,16 @@ init_cjs_shims();
11272
11926
 
11273
11927
  // src/extract/infra/docker-compose.ts
11274
11928
  init_cjs_shims();
11275
- var import_node_path48 = __toESM(require("path"), 1);
11276
- var import_types39 = require("@neat.is/types");
11929
+ var import_node_path49 = __toESM(require("path"), 1);
11930
+ var import_types40 = require("@neat.is/types");
11277
11931
 
11278
11932
  // src/extract/infra/shared.ts
11279
11933
  init_cjs_shims();
11280
- var import_types38 = require("@neat.is/types");
11934
+ var import_types39 = require("@neat.is/types");
11281
11935
  function makeInfraNode(kind, name, provider = "self", extras) {
11282
11936
  return {
11283
- id: (0, import_types38.infraId)(kind, name),
11284
- type: import_types38.NodeType.InfraNode,
11937
+ id: (0, import_types39.infraId)(kind, name),
11938
+ type: import_types39.NodeType.InfraNode,
11285
11939
  name,
11286
11940
  provider,
11287
11941
  kind,
@@ -11325,8 +11979,8 @@ function emitPlatformResourceEdge(graph, anchorId, edgeType, kind, name, provide
11325
11979
  source: anchorId,
11326
11980
  target: node.id,
11327
11981
  type: edgeType,
11328
- provenance: import_types38.Provenance.EXTRACTED,
11329
- confidence: (0, import_types38.confidenceForExtracted)("structural"),
11982
+ provenance: import_types39.Provenance.EXTRACTED,
11983
+ confidence: (0, import_types39.confidenceForExtracted)("structural"),
11330
11984
  evidence: { file: evidenceFile, ...line !== void 0 ? { line } : {} }
11331
11985
  };
11332
11986
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -11343,7 +11997,7 @@ function dependsOnList(value) {
11343
11997
  }
11344
11998
  function serviceNameToServiceNode(name, services) {
11345
11999
  for (const s of services) {
11346
- if (s.node.name === name || import_node_path48.default.basename(s.dir) === name) return s.node.id;
12000
+ if (s.node.name === name || import_node_path49.default.basename(s.dir) === name) return s.node.id;
11347
12001
  }
11348
12002
  return null;
11349
12003
  }
@@ -11352,7 +12006,7 @@ async function addComposeInfra(graph, scanPath, services) {
11352
12006
  let edgesAdded = 0;
11353
12007
  let composePath = null;
11354
12008
  for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
11355
- const abs = import_node_path48.default.join(scanPath, name);
12009
+ const abs = import_node_path49.default.join(scanPath, name);
11356
12010
  if (await exists(abs)) {
11357
12011
  composePath = abs;
11358
12012
  break;
@@ -11365,13 +12019,13 @@ async function addComposeInfra(graph, scanPath, services) {
11365
12019
  } catch (err) {
11366
12020
  recordExtractionError(
11367
12021
  "infra docker-compose",
11368
- import_node_path48.default.relative(scanPath, composePath),
12022
+ import_node_path49.default.relative(scanPath, composePath),
11369
12023
  err
11370
12024
  );
11371
12025
  return { nodesAdded, edgesAdded };
11372
12026
  }
11373
12027
  if (!compose?.services) return { nodesAdded, edgesAdded };
11374
- const evidenceFile = import_node_path48.default.relative(scanPath, composePath).split(import_node_path48.default.sep).join("/");
12028
+ const evidenceFile = import_node_path49.default.relative(scanPath, composePath).split(import_node_path49.default.sep).join("/");
11375
12029
  const composeNameToNodeId = /* @__PURE__ */ new Map();
11376
12030
  for (const [composeName, svc] of Object.entries(compose.services)) {
11377
12031
  const matchedServiceId = serviceNameToServiceNode(composeName, services);
@@ -11393,15 +12047,15 @@ async function addComposeInfra(graph, scanPath, services) {
11393
12047
  for (const dep of dependsOnList(svc.depends_on)) {
11394
12048
  const targetId = composeNameToNodeId.get(dep);
11395
12049
  if (!targetId) continue;
11396
- const edgeId = (0, import_types3.extractedEdgeId)(sourceId, targetId, import_types39.EdgeType.DEPENDS_ON);
12050
+ const edgeId = (0, import_types3.extractedEdgeId)(sourceId, targetId, import_types40.EdgeType.DEPENDS_ON);
11397
12051
  if (graph.hasEdge(edgeId)) continue;
11398
12052
  const edge = {
11399
12053
  id: edgeId,
11400
12054
  source: sourceId,
11401
12055
  target: targetId,
11402
- type: import_types39.EdgeType.DEPENDS_ON,
11403
- provenance: import_types39.Provenance.EXTRACTED,
11404
- confidence: (0, import_types39.confidenceForExtracted)("structural"),
12056
+ type: import_types40.EdgeType.DEPENDS_ON,
12057
+ provenance: import_types40.Provenance.EXTRACTED,
12058
+ confidence: (0, import_types40.confidenceForExtracted)("structural"),
11405
12059
  evidence: { file: evidenceFile }
11406
12060
  };
11407
12061
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -11413,9 +12067,9 @@ async function addComposeInfra(graph, scanPath, services) {
11413
12067
 
11414
12068
  // src/extract/infra/dockerfile.ts
11415
12069
  init_cjs_shims();
11416
- var import_node_path49 = __toESM(require("path"), 1);
12070
+ var import_node_path50 = __toESM(require("path"), 1);
11417
12071
  var import_node_fs18 = require("fs");
11418
- var import_types40 = require("@neat.is/types");
12072
+ var import_types41 = require("@neat.is/types");
11419
12073
  function readDockerfile(content) {
11420
12074
  let image = null;
11421
12075
  const ports = [];
@@ -11444,7 +12098,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
11444
12098
  let nodesAdded = 0;
11445
12099
  let edgesAdded = 0;
11446
12100
  for (const service of services) {
11447
- const dockerfilePath = import_node_path49.default.join(service.dir, "Dockerfile");
12101
+ const dockerfilePath = import_node_path50.default.join(service.dir, "Dockerfile");
11448
12102
  if (!await exists(dockerfilePath)) continue;
11449
12103
  let content;
11450
12104
  try {
@@ -11452,7 +12106,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
11452
12106
  } catch (err) {
11453
12107
  recordExtractionError(
11454
12108
  "infra dockerfile",
11455
- import_node_path49.default.relative(scanPath, dockerfilePath),
12109
+ import_node_path50.default.relative(scanPath, dockerfilePath),
11456
12110
  err
11457
12111
  );
11458
12112
  continue;
@@ -11464,8 +12118,8 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
11464
12118
  graph.addNode(node.id, node);
11465
12119
  nodesAdded++;
11466
12120
  }
11467
- const relDockerfile = toPosix(import_node_path49.default.relative(service.dir, dockerfilePath));
11468
- const evidenceFile = toPosix(import_node_path49.default.relative(scanPath, dockerfilePath));
12121
+ const relDockerfile = toPosix(import_node_path50.default.relative(service.dir, dockerfilePath));
12122
+ const evidenceFile = toPosix(import_node_path50.default.relative(scanPath, dockerfilePath));
11469
12123
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
11470
12124
  graph,
11471
12125
  service.pkg.name,
@@ -11474,15 +12128,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
11474
12128
  );
11475
12129
  nodesAdded += fn;
11476
12130
  edgesAdded += fe;
11477
- const edgeId = (0, import_types3.extractedEdgeId)(fileNodeId, node.id, import_types40.EdgeType.RUNS_ON);
12131
+ const edgeId = (0, import_types3.extractedEdgeId)(fileNodeId, node.id, import_types41.EdgeType.RUNS_ON);
11478
12132
  if (!graph.hasEdge(edgeId)) {
11479
12133
  const edge = {
11480
12134
  id: edgeId,
11481
12135
  source: fileNodeId,
11482
12136
  target: node.id,
11483
- type: import_types40.EdgeType.RUNS_ON,
11484
- provenance: import_types40.Provenance.EXTRACTED,
11485
- confidence: (0, import_types40.confidenceForExtracted)("structural"),
12137
+ type: import_types41.EdgeType.RUNS_ON,
12138
+ provenance: import_types41.Provenance.EXTRACTED,
12139
+ confidence: (0, import_types41.confidenceForExtracted)("structural"),
11486
12140
  evidence: {
11487
12141
  file: evidenceFile,
11488
12142
  ...facts.entrypoint ? { snippet: facts.entrypoint.slice(0, 120) } : {}
@@ -11497,15 +12151,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
11497
12151
  graph.addNode(portNode.id, portNode);
11498
12152
  nodesAdded++;
11499
12153
  }
11500
- const portEdgeId = (0, import_types3.extractedEdgeId)(fileNodeId, portNode.id, import_types40.EdgeType.CONNECTS_TO);
12154
+ const portEdgeId = (0, import_types3.extractedEdgeId)(fileNodeId, portNode.id, import_types41.EdgeType.CONNECTS_TO);
11501
12155
  if (graph.hasEdge(portEdgeId)) continue;
11502
12156
  const portEdge = {
11503
12157
  id: portEdgeId,
11504
12158
  source: fileNodeId,
11505
12159
  target: portNode.id,
11506
- type: import_types40.EdgeType.CONNECTS_TO,
11507
- provenance: import_types40.Provenance.EXTRACTED,
11508
- confidence: (0, import_types40.confidenceForExtracted)("structural"),
12160
+ type: import_types41.EdgeType.CONNECTS_TO,
12161
+ provenance: import_types41.Provenance.EXTRACTED,
12162
+ confidence: (0, import_types41.confidenceForExtracted)("structural"),
11509
12163
  evidence: { file: evidenceFile, snippet: `EXPOSE ${port}` }
11510
12164
  };
11511
12165
  graph.addEdgeWithKey(portEdgeId, portEdge.source, portEdge.target, portEdge);
@@ -11518,8 +12172,8 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
11518
12172
  // src/extract/infra/terraform.ts
11519
12173
  init_cjs_shims();
11520
12174
  var import_node_fs19 = require("fs");
11521
- var import_node_path50 = __toESM(require("path"), 1);
11522
- var import_types41 = require("@neat.is/types");
12175
+ var import_node_path51 = __toESM(require("path"), 1);
12176
+ var import_types42 = require("@neat.is/types");
11523
12177
  var RESOURCE_RE = /resource\s+"(aws_[A-Za-z0-9_]+)"\s+"([A-Za-z0-9_-]+)"/g;
11524
12178
  var REFERENCE_RE = /(?<![\w.])(aws_[A-Za-z0-9_]+)\.([A-Za-z0-9_-]+)/g;
11525
12179
  async function walkTfFiles(start, depth = 0, max = 5) {
@@ -11529,11 +12183,11 @@ async function walkTfFiles(start, depth = 0, max = 5) {
11529
12183
  for (const entry2 of entries) {
11530
12184
  if (entry2.isDirectory()) {
11531
12185
  if (IGNORED_DIRS.has(entry2.name) || entry2.name === ".terraform") continue;
11532
- const child = import_node_path50.default.join(start, entry2.name);
12186
+ const child = import_node_path51.default.join(start, entry2.name);
11533
12187
  if (await isPythonVenvDir(child)) continue;
11534
12188
  out.push(...await walkTfFiles(child, depth + 1, max));
11535
12189
  } else if (entry2.isFile() && entry2.name.endsWith(".tf")) {
11536
- out.push(import_node_path50.default.join(start, entry2.name));
12190
+ out.push(import_node_path51.default.join(start, entry2.name));
11537
12191
  }
11538
12192
  }
11539
12193
  return out;
@@ -11565,7 +12219,7 @@ async function addTerraformResources(graph, scanPath) {
11565
12219
  const files = await walkTfFiles(scanPath);
11566
12220
  for (const file of files) {
11567
12221
  const content = await import_node_fs19.promises.readFile(file, "utf8");
11568
- const evidenceFile = toPosix(import_node_path50.default.relative(scanPath, file));
12222
+ const evidenceFile = toPosix(import_node_path51.default.relative(scanPath, file));
11569
12223
  const resources = [];
11570
12224
  const byKey = /* @__PURE__ */ new Map();
11571
12225
  RESOURCE_RE.lastIndex = 0;
@@ -11600,16 +12254,16 @@ async function addTerraformResources(graph, scanPath) {
11600
12254
  if (!target) continue;
11601
12255
  if (seen.has(target.nodeId)) continue;
11602
12256
  seen.add(target.nodeId);
11603
- const edgeId = (0, import_types3.extractedEdgeId)(resource.nodeId, target.nodeId, import_types41.EdgeType.DEPENDS_ON);
12257
+ const edgeId = (0, import_types3.extractedEdgeId)(resource.nodeId, target.nodeId, import_types42.EdgeType.DEPENDS_ON);
11604
12258
  if (graph.hasEdge(edgeId)) continue;
11605
12259
  const line = lineAt2(content, resource.bodyOffset + ref.index);
11606
12260
  const edge = {
11607
12261
  id: edgeId,
11608
12262
  source: resource.nodeId,
11609
12263
  target: target.nodeId,
11610
- type: import_types41.EdgeType.DEPENDS_ON,
11611
- provenance: import_types41.Provenance.EXTRACTED,
11612
- confidence: (0, import_types41.confidenceForExtracted)("structural"),
12264
+ type: import_types42.EdgeType.DEPENDS_ON,
12265
+ provenance: import_types42.Provenance.EXTRACTED,
12266
+ confidence: (0, import_types42.confidenceForExtracted)("structural"),
11613
12267
  evidence: { file: evidenceFile, line, snippet: key }
11614
12268
  };
11615
12269
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -11623,7 +12277,7 @@ async function addTerraformResources(graph, scanPath) {
11623
12277
  // src/extract/infra/k8s.ts
11624
12278
  init_cjs_shims();
11625
12279
  var import_node_fs20 = require("fs");
11626
- var import_node_path51 = __toESM(require("path"), 1);
12280
+ var import_node_path52 = __toESM(require("path"), 1);
11627
12281
  var import_yaml3 = require("yaml");
11628
12282
  var K8S_KIND_TO_INFRA_KIND = {
11629
12283
  Service: "k8s-service",
@@ -11641,11 +12295,11 @@ async function walkYamlFiles2(start, depth = 0, max = 5) {
11641
12295
  for (const entry2 of entries) {
11642
12296
  if (entry2.isDirectory()) {
11643
12297
  if (IGNORED_DIRS.has(entry2.name)) continue;
11644
- const child = import_node_path51.default.join(start, entry2.name);
12298
+ const child = import_node_path52.default.join(start, entry2.name);
11645
12299
  if (await isPythonVenvDir(child)) continue;
11646
12300
  out.push(...await walkYamlFiles2(child, depth + 1, max));
11647
- } else if (entry2.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path51.default.extname(entry2.name))) {
11648
- out.push(import_node_path51.default.join(start, entry2.name));
12301
+ } else if (entry2.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path52.default.extname(entry2.name))) {
12302
+ out.push(import_node_path52.default.join(start, entry2.name));
11649
12303
  }
11650
12304
  }
11651
12305
  return out;
@@ -11679,13 +12333,13 @@ async function addK8sResources(graph, scanPath) {
11679
12333
  // src/extract/infra/cloudflare.ts
11680
12334
  init_cjs_shims();
11681
12335
  var import_node_fs21 = require("fs");
11682
- var import_node_path52 = __toESM(require("path"), 1);
12336
+ var import_node_path53 = __toESM(require("path"), 1);
11683
12337
  var import_smol_toml2 = require("smol-toml");
11684
- var import_types42 = require("@neat.is/types");
12338
+ var import_types43 = require("@neat.is/types");
11685
12339
  var WRANGLER_FILENAMES = ["wrangler.toml", "wrangler.jsonc", "wrangler.json"];
11686
12340
  async function readWranglerConfig(dir) {
11687
12341
  for (const filename of WRANGLER_FILENAMES) {
11688
- const abs = import_node_path52.default.join(dir, filename);
12342
+ const abs = import_node_path53.default.join(dir, filename);
11689
12343
  if (!await exists(abs)) continue;
11690
12344
  const raw = await import_node_fs21.promises.readFile(abs, "utf8");
11691
12345
  const config = filename === "wrangler.toml" ? (0, import_smol_toml2.parse)(raw) : JSON.parse(maskCommentsInSource(raw));
@@ -11729,8 +12383,8 @@ function addResourceEdge(graph, anchorId, edgeType, kind, name, evidenceFile, li
11729
12383
  source: anchorId,
11730
12384
  target: node.id,
11731
12385
  type: edgeType,
11732
- provenance: import_types42.Provenance.EXTRACTED,
11733
- confidence: (0, import_types42.confidenceForExtracted)("structural"),
12386
+ provenance: import_types43.Provenance.EXTRACTED,
12387
+ confidence: (0, import_types43.confidenceForExtracted)("structural"),
11734
12388
  evidence: { file: evidenceFile, ...line !== void 0 ? { line } : {} }
11735
12389
  };
11736
12390
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -11748,11 +12402,11 @@ async function addCloudflareWorkers(graph, services, scanPath) {
11748
12402
  try {
11749
12403
  read = await readWranglerConfig(service.dir);
11750
12404
  } catch (err) {
11751
- recordExtractionError("infra cloudflare", import_node_path52.default.relative(scanPath, service.dir), err);
12405
+ recordExtractionError("infra cloudflare", import_node_path53.default.relative(scanPath, service.dir), err);
11752
12406
  continue;
11753
12407
  }
11754
12408
  if (!read || !read.config.name) continue;
11755
- const evidenceFile = toPosix(import_node_path52.default.relative(scanPath, import_node_path52.default.join(service.dir, read.relFile)));
12409
+ const evidenceFile = toPosix(import_node_path53.default.relative(scanPath, import_node_path53.default.join(service.dir, read.relFile)));
11756
12410
  discovered.push({ service, config: read.config, relFile: read.relFile, raw: read.raw, evidenceFile });
11757
12411
  }
11758
12412
  for (const worker of discovered) {
@@ -11764,7 +12418,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
11764
12418
  }
11765
12419
  let anchorId = service.node.id;
11766
12420
  if (config.main) {
11767
- const entryRelPath = toPosix(import_node_path52.default.normalize(config.main));
12421
+ const entryRelPath = toPosix(import_node_path53.default.normalize(config.main));
11768
12422
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
11769
12423
  graph,
11770
12424
  service.pkg.name,
@@ -11791,15 +12445,15 @@ async function addCloudflareWorkers(graph, services, scanPath) {
11791
12445
  nodesAdded++;
11792
12446
  }
11793
12447
  if (runtimeNode.id !== anchorId) {
11794
- const runsOnId = (0, import_types3.extractedEdgeId)(anchorId, runtimeNode.id, import_types42.EdgeType.RUNS_ON);
12448
+ const runsOnId = (0, import_types3.extractedEdgeId)(anchorId, runtimeNode.id, import_types43.EdgeType.RUNS_ON);
11795
12449
  if (!graph.hasEdge(runsOnId)) {
11796
12450
  const edge = {
11797
12451
  id: runsOnId,
11798
12452
  source: anchorId,
11799
12453
  target: runtimeNode.id,
11800
- type: import_types42.EdgeType.RUNS_ON,
11801
- provenance: import_types42.Provenance.EXTRACTED,
11802
- confidence: (0, import_types42.confidenceForExtracted)("structural"),
12454
+ type: import_types43.EdgeType.RUNS_ON,
12455
+ provenance: import_types43.Provenance.EXTRACTED,
12456
+ confidence: (0, import_types43.confidenceForExtracted)("structural"),
11803
12457
  evidence: {
11804
12458
  file: evidenceFile,
11805
12459
  ...config.compatibility_date ? { snippet: `compatibility_date = ${config.compatibility_date}`.slice(0, 120) } : {}
@@ -11813,7 +12467,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
11813
12467
  const result = addResourceEdge(
11814
12468
  graph,
11815
12469
  anchorId,
11816
- import_types42.EdgeType.CONNECTS_TO,
12470
+ import_types43.EdgeType.CONNECTS_TO,
11817
12471
  "cloudflare-route",
11818
12472
  route,
11819
12473
  evidenceFile,
@@ -11837,7 +12491,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
11837
12491
  const result = addResourceEdge(
11838
12492
  graph,
11839
12493
  anchorId,
11840
- import_types42.EdgeType.DEPENDS_ON,
12494
+ import_types43.EdgeType.DEPENDS_ON,
11841
12495
  group.kind,
11842
12496
  name,
11843
12497
  evidenceFile,
@@ -11851,7 +12505,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
11851
12505
  const result = addResourceEdge(
11852
12506
  graph,
11853
12507
  anchorId,
11854
- import_types42.EdgeType.DEPENDS_ON,
12508
+ import_types43.EdgeType.DEPENDS_ON,
11855
12509
  "cloudflare-cron",
11856
12510
  cron,
11857
12511
  evidenceFile,
@@ -11864,7 +12518,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
11864
12518
  const result = addResourceEdge(
11865
12519
  graph,
11866
12520
  anchorId,
11867
- import_types42.EdgeType.DEPENDS_ON,
12521
+ import_types43.EdgeType.DEPENDS_ON,
11868
12522
  "cloudflare-env-var",
11869
12523
  varName,
11870
12524
  evidenceFile,
@@ -11877,15 +12531,15 @@ async function addCloudflareWorkers(graph, services, scanPath) {
11877
12531
  if (!svc.service) continue;
11878
12532
  const target = workerIndex.get(svc.service);
11879
12533
  if (target && target.anchorId !== anchorId) {
11880
- const edgeId = (0, import_types3.extractedEdgeId)(anchorId, target.anchorId, import_types42.EdgeType.CALLS);
12534
+ const edgeId = (0, import_types3.extractedEdgeId)(anchorId, target.anchorId, import_types43.EdgeType.CALLS);
11881
12535
  if (!graph.hasEdge(edgeId)) {
11882
12536
  const edge = {
11883
12537
  id: edgeId,
11884
12538
  source: anchorId,
11885
12539
  target: target.anchorId,
11886
- type: import_types42.EdgeType.CALLS,
11887
- provenance: import_types42.Provenance.EXTRACTED,
11888
- confidence: (0, import_types42.confidenceForExtracted)("structural"),
12540
+ type: import_types43.EdgeType.CALLS,
12541
+ provenance: import_types43.Provenance.EXTRACTED,
12542
+ confidence: (0, import_types43.confidenceForExtracted)("structural"),
11889
12543
  evidence: { file: evidenceFile, line: lineContaining2(raw, svc.service) }
11890
12544
  };
11891
12545
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -11896,7 +12550,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
11896
12550
  const result = addResourceEdge(
11897
12551
  graph,
11898
12552
  anchorId,
11899
- import_types42.EdgeType.DEPENDS_ON,
12553
+ import_types43.EdgeType.DEPENDS_ON,
11900
12554
  "cloudflare-service-binding",
11901
12555
  svc.service,
11902
12556
  evidenceFile,
@@ -11912,12 +12566,12 @@ async function addCloudflareWorkers(graph, services, scanPath) {
11912
12566
  // src/extract/infra/vercel.ts
11913
12567
  init_cjs_shims();
11914
12568
  var import_node_fs22 = require("fs");
11915
- var import_node_path53 = __toESM(require("path"), 1);
11916
- var import_types43 = require("@neat.is/types");
12569
+ var import_node_path54 = __toESM(require("path"), 1);
12570
+ var import_types44 = require("@neat.is/types");
11917
12571
  var VERCEL_CONFIG_FILENAMES = ["vercel.json", "vercel.jsonc"];
11918
12572
  async function readVercelConfig(dir) {
11919
12573
  for (const filename of VERCEL_CONFIG_FILENAMES) {
11920
- const abs = import_node_path53.default.join(dir, filename);
12574
+ const abs = import_node_path54.default.join(dir, filename);
11921
12575
  if (!await exists(abs)) continue;
11922
12576
  const raw = await import_node_fs22.promises.readFile(abs, "utf8");
11923
12577
  const config = JSON.parse(maskCommentsInSource(raw));
@@ -11926,7 +12580,7 @@ async function readVercelConfig(dir) {
11926
12580
  return null;
11927
12581
  }
11928
12582
  async function readLinkedProjectName(dir) {
11929
- const abs = import_node_path53.default.join(dir, ".vercel", "project.json");
12583
+ const abs = import_node_path54.default.join(dir, ".vercel", "project.json");
11930
12584
  if (!await exists(abs)) return void 0;
11931
12585
  const parsed = JSON.parse(await import_node_fs22.promises.readFile(abs, "utf8"));
11932
12586
  return typeof parsed.projectName === "string" ? parsed.projectName : void 0;
@@ -11944,7 +12598,7 @@ async function addVercelServices(graph, services, scanPath) {
11944
12598
  read = await readVercelConfig(service.dir);
11945
12599
  projectName = await readLinkedProjectName(service.dir);
11946
12600
  } catch (err) {
11947
- recordExtractionError("infra vercel", import_node_path53.default.relative(scanPath, service.dir), err);
12601
+ recordExtractionError("infra vercel", import_node_path54.default.relative(scanPath, service.dir), err);
11948
12602
  continue;
11949
12603
  }
11950
12604
  if (!read && !projectName) continue;
@@ -11960,7 +12614,7 @@ async function addVercelServices(graph, services, scanPath) {
11960
12614
  const anchorId = service.node.id;
11961
12615
  if (!read) continue;
11962
12616
  const { config, relFile, raw } = read;
11963
- const evidenceFile = toPosix(import_node_path53.default.relative(scanPath, import_node_path53.default.join(service.dir, relFile)));
12617
+ const evidenceFile = toPosix(import_node_path54.default.relative(scanPath, import_node_path54.default.join(service.dir, relFile)));
11964
12618
  const add = (edgeType, kind, name) => {
11965
12619
  if (!name) return;
11966
12620
  const result = emitPlatformResourceEdge(
@@ -11976,12 +12630,12 @@ async function addVercelServices(graph, services, scanPath) {
11976
12630
  nodesAdded += result.nodesAdded;
11977
12631
  edgesAdded += result.edgesAdded;
11978
12632
  };
11979
- add(import_types43.EdgeType.RUNS_ON, "vercel", "vercel");
11980
- for (const cron of config.crons ?? []) add(import_types43.EdgeType.DEPENDS_ON, "vercel-cron", cron.path ?? cron.schedule);
11981
- for (const varName of Object.keys(config.env ?? {})) add(import_types43.EdgeType.DEPENDS_ON, "vercel-env-var", varName);
11982
- for (const varName of Object.keys(config.build?.env ?? {})) add(import_types43.EdgeType.DEPENDS_ON, "vercel-env-var", varName);
12633
+ add(import_types44.EdgeType.RUNS_ON, "vercel", "vercel");
12634
+ for (const cron of config.crons ?? []) add(import_types44.EdgeType.DEPENDS_ON, "vercel-cron", cron.path ?? cron.schedule);
12635
+ for (const varName of Object.keys(config.env ?? {})) add(import_types44.EdgeType.DEPENDS_ON, "vercel-env-var", varName);
12636
+ for (const varName of Object.keys(config.build?.env ?? {})) add(import_types44.EdgeType.DEPENDS_ON, "vercel-env-var", varName);
11983
12637
  for (const route of [...config.rewrites ?? [], ...config.redirects ?? [], ...config.routes ?? []]) {
11984
- add(import_types43.EdgeType.CONNECTS_TO, "vercel-route", routeSource(route));
12638
+ add(import_types44.EdgeType.CONNECTS_TO, "vercel-route", routeSource(route));
11985
12639
  }
11986
12640
  }
11987
12641
  return { nodesAdded, edgesAdded };
@@ -11990,13 +12644,13 @@ async function addVercelServices(graph, services, scanPath) {
11990
12644
  // src/extract/infra/railway.ts
11991
12645
  init_cjs_shims();
11992
12646
  var import_node_fs23 = require("fs");
11993
- var import_node_path54 = __toESM(require("path"), 1);
12647
+ var import_node_path55 = __toESM(require("path"), 1);
11994
12648
  var import_smol_toml3 = require("smol-toml");
11995
- var import_types44 = require("@neat.is/types");
12649
+ var import_types45 = require("@neat.is/types");
11996
12650
  var RAILWAY_FILENAMES = ["railway.toml", "railway.json", "railway.jsonc"];
11997
12651
  async function readRailwayConfig(dir) {
11998
12652
  for (const filename of RAILWAY_FILENAMES) {
11999
- const abs = import_node_path54.default.join(dir, filename);
12653
+ const abs = import_node_path55.default.join(dir, filename);
12000
12654
  if (!await exists(abs)) continue;
12001
12655
  const raw = await import_node_fs23.promises.readFile(abs, "utf8");
12002
12656
  const config = filename === "railway.toml" ? (0, import_smol_toml3.parse)(raw) : JSON.parse(maskCommentsInSource(raw));
@@ -12012,7 +12666,7 @@ async function addRailwayServices(graph, services, scanPath) {
12012
12666
  try {
12013
12667
  read = await readRailwayConfig(service.dir);
12014
12668
  } catch (err) {
12015
- recordExtractionError("infra railway", import_node_path54.default.relative(scanPath, service.dir), err);
12669
+ recordExtractionError("infra railway", import_node_path55.default.relative(scanPath, service.dir), err);
12016
12670
  continue;
12017
12671
  }
12018
12672
  if (!read) continue;
@@ -12022,7 +12676,7 @@ async function addRailwayServices(graph, services, scanPath) {
12022
12676
  }
12023
12677
  const anchorId = service.node.id;
12024
12678
  const { config, relFile, raw } = read;
12025
- const evidenceFile = toPosix(import_node_path54.default.relative(scanPath, import_node_path54.default.join(service.dir, relFile)));
12679
+ const evidenceFile = toPosix(import_node_path55.default.relative(scanPath, import_node_path55.default.join(service.dir, relFile)));
12026
12680
  const add = (edgeType, kind, name) => {
12027
12681
  if (!name) return;
12028
12682
  const result = emitPlatformResourceEdge(
@@ -12038,9 +12692,9 @@ async function addRailwayServices(graph, services, scanPath) {
12038
12692
  nodesAdded += result.nodesAdded;
12039
12693
  edgesAdded += result.edgesAdded;
12040
12694
  };
12041
- add(import_types44.EdgeType.RUNS_ON, "railway", "railway");
12042
- add(import_types44.EdgeType.CONNECTS_TO, "railway-route", config.deploy?.healthcheckPath);
12043
- add(import_types44.EdgeType.DEPENDS_ON, "railway-cron", config.deploy?.cronSchedule);
12695
+ add(import_types45.EdgeType.RUNS_ON, "railway", "railway");
12696
+ add(import_types45.EdgeType.CONNECTS_TO, "railway-route", config.deploy?.healthcheckPath);
12697
+ add(import_types45.EdgeType.DEPENDS_ON, "railway-cron", config.deploy?.cronSchedule);
12044
12698
  }
12045
12699
  return { nodesAdded, edgesAdded };
12046
12700
  }
@@ -12048,12 +12702,12 @@ async function addRailwayServices(graph, services, scanPath) {
12048
12702
  // src/extract/infra/supabase.ts
12049
12703
  init_cjs_shims();
12050
12704
  var import_node_fs24 = require("fs");
12051
- var import_node_path55 = __toESM(require("path"), 1);
12705
+ var import_node_path56 = __toESM(require("path"), 1);
12052
12706
  var import_smol_toml4 = require("smol-toml");
12053
- var import_types45 = require("@neat.is/types");
12707
+ var import_types46 = require("@neat.is/types");
12054
12708
  async function readSupabaseConfig(dir) {
12055
- const relFile = import_node_path55.default.join("supabase", "config.toml");
12056
- const abs = import_node_path55.default.join(dir, relFile);
12709
+ const relFile = import_node_path56.default.join("supabase", "config.toml");
12710
+ const abs = import_node_path56.default.join(dir, relFile);
12057
12711
  if (!await exists(abs)) return null;
12058
12712
  const raw = await import_node_fs24.promises.readFile(abs, "utf8");
12059
12713
  const config = (0, import_smol_toml4.parse)(raw);
@@ -12067,7 +12721,7 @@ async function addSupabaseProjects(graph, services, scanPath) {
12067
12721
  try {
12068
12722
  read = await readSupabaseConfig(service.dir);
12069
12723
  } catch (err) {
12070
- recordExtractionError("infra supabase", import_node_path55.default.relative(scanPath, service.dir), err);
12724
+ recordExtractionError("infra supabase", import_node_path56.default.relative(scanPath, service.dir), err);
12071
12725
  continue;
12072
12726
  }
12073
12727
  if (!read) continue;
@@ -12082,7 +12736,7 @@ async function addSupabaseProjects(graph, services, scanPath) {
12082
12736
  });
12083
12737
  }
12084
12738
  const anchorId = service.node.id;
12085
- const evidenceFile = toPosix(import_node_path55.default.relative(scanPath, import_node_path55.default.join(service.dir, relFile)));
12739
+ const evidenceFile = toPosix(import_node_path56.default.relative(scanPath, import_node_path56.default.join(service.dir, relFile)));
12086
12740
  const add = (edgeType, kind, name) => {
12087
12741
  if (!name) return;
12088
12742
  const result = emitPlatformResourceEdge(
@@ -12098,10 +12752,10 @@ async function addSupabaseProjects(graph, services, scanPath) {
12098
12752
  nodesAdded += result.nodesAdded;
12099
12753
  edgesAdded += result.edgesAdded;
12100
12754
  };
12101
- add(import_types45.EdgeType.RUNS_ON, "supabase", "supabase");
12102
- for (const fn of Object.keys(config.functions ?? {})) add(import_types45.EdgeType.DEPENDS_ON, "supabase-function", fn);
12103
- if (config.storage) add(import_types45.EdgeType.DEPENDS_ON, "supabase-storage", "storage");
12104
- if (config.auth) add(import_types45.EdgeType.DEPENDS_ON, "supabase-auth", "auth");
12755
+ add(import_types46.EdgeType.RUNS_ON, "supabase", "supabase");
12756
+ for (const fn of Object.keys(config.functions ?? {})) add(import_types46.EdgeType.DEPENDS_ON, "supabase-function", fn);
12757
+ if (config.storage) add(import_types46.EdgeType.DEPENDS_ON, "supabase-storage", "storage");
12758
+ if (config.auth) add(import_types46.EdgeType.DEPENDS_ON, "supabase-auth", "auth");
12105
12759
  }
12106
12760
  return { nodesAdded, edgesAdded };
12107
12761
  }
@@ -12124,14 +12778,14 @@ async function addInfra(graph, scanPath, services) {
12124
12778
 
12125
12779
  // src/extract/zod-shapes.ts
12126
12780
  init_cjs_shims();
12127
- var import_node_path56 = __toESM(require("path"), 1);
12128
- var import_tree_sitter15 = __toESM(require("tree-sitter"), 1);
12781
+ var import_node_path57 = __toESM(require("path"), 1);
12782
+ var import_tree_sitter16 = __toESM(require("tree-sitter"), 1);
12129
12783
  var import_tree_sitter_javascript8 = __toESM(require("tree-sitter-javascript"), 1);
12130
- var import_types46 = require("@neat.is/types");
12784
+ var import_types47 = require("@neat.is/types");
12131
12785
  var ZOD_IMPORT_RE = /\bzod\b/;
12132
12786
  var ZOD_OBJECTS = /* @__PURE__ */ new Set(["z", "zod"]);
12133
12787
  function parserForExt3(ext) {
12134
- const p = new import_tree_sitter15.default();
12788
+ const p = new import_tree_sitter16.default();
12135
12789
  p.setLanguage(GRAMMAR_BY_EXT[ext] ?? import_tree_sitter_javascript8.default);
12136
12790
  return p;
12137
12791
  }
@@ -12219,7 +12873,7 @@ function topLevelSchemas(root) {
12219
12873
  }
12220
12874
  function zodShapesFromFile(file, serviceDir) {
12221
12875
  if (!ZOD_IMPORT_RE.test(file.content)) return [];
12222
- const tree = parseSource3(parserForExt3(import_node_path56.default.extname(file.path)), file.content);
12876
+ const tree = parseSource3(parserForExt3(import_node_path57.default.extname(file.path)), file.content);
12223
12877
  const out = [];
12224
12878
  const seen = /* @__PURE__ */ new Set();
12225
12879
  for (const { name, call } of topLevelSchemas(tree.rootNode)) {
@@ -12233,11 +12887,11 @@ function zodShapesFromFile(file, serviceDir) {
12233
12887
  seen.add(name);
12234
12888
  const line = call.startPosition.row + 1;
12235
12889
  out.push({
12236
- infraId: (0, import_types46.infraId)("zod-schema", name),
12890
+ infraId: (0, import_types47.infraId)("zod-schema", name),
12237
12891
  name,
12238
12892
  fields,
12239
12893
  evidence: {
12240
- file: import_node_path56.default.relative(serviceDir, file.path),
12894
+ file: import_node_path57.default.relative(serviceDir, file.path),
12241
12895
  line,
12242
12896
  snippet: snippet(file.content, line)
12243
12897
  }
@@ -12268,7 +12922,7 @@ async function addZodShapes(graph, services) {
12268
12922
  if (!graph.hasNode(shape.infraId)) {
12269
12923
  const node = {
12270
12924
  id: shape.infraId,
12271
- type: import_types46.NodeType.InfraNode,
12925
+ type: import_types47.NodeType.InfraNode,
12272
12926
  name: shape.name,
12273
12927
  provider: "self",
12274
12928
  kind: "zod-schema"
@@ -12278,14 +12932,14 @@ async function addZodShapes(graph, services) {
12278
12932
  }
12279
12933
  if (shape.fields.length > 0) {
12280
12934
  const node = graph.getNodeAttributes(shape.infraId);
12281
- if (node.type === import_types46.NodeType.InfraNode) {
12935
+ if (node.type === import_types47.NodeType.InfraNode) {
12282
12936
  graph.replaceNodeAttributes(shape.infraId, {
12283
12937
  ...node,
12284
12938
  columns: foldColumns(
12285
12939
  node.columns,
12286
12940
  shape.fields,
12287
- import_types46.Provenance.EXTRACTED,
12288
- (0, import_types46.confidenceForExtracted)("structural")
12941
+ import_types47.Provenance.EXTRACTED,
12942
+ (0, import_types47.confidenceForExtracted)("structural")
12289
12943
  )
12290
12944
  });
12291
12945
  }
@@ -12299,15 +12953,15 @@ async function addZodShapes(graph, services) {
12299
12953
  );
12300
12954
  nodesAdded += n;
12301
12955
  edgesAdded += e;
12302
- const edgeId = (0, import_types46.extractedEdgeId)(fileNodeId, shape.infraId, import_types46.EdgeType.CONTAINS);
12956
+ const edgeId = (0, import_types47.extractedEdgeId)(fileNodeId, shape.infraId, import_types47.EdgeType.CONTAINS);
12303
12957
  if (!graph.hasEdge(edgeId)) {
12304
12958
  const edge = {
12305
12959
  id: edgeId,
12306
12960
  source: fileNodeId,
12307
12961
  target: shape.infraId,
12308
- type: import_types46.EdgeType.CONTAINS,
12309
- provenance: import_types46.Provenance.EXTRACTED,
12310
- confidence: (0, import_types46.confidenceForExtracted)("structural"),
12962
+ type: import_types47.EdgeType.CONTAINS,
12963
+ provenance: import_types47.Provenance.EXTRACTED,
12964
+ confidence: (0, import_types47.confidenceForExtracted)("structural"),
12311
12965
  evidence: shape.evidence
12312
12966
  };
12313
12967
  graph.addEdgeWithKey(edgeId, fileNodeId, shape.infraId, edge);
@@ -12321,7 +12975,7 @@ async function addZodShapes(graph, services) {
12321
12975
 
12322
12976
  // src/extract/firestore-rules.ts
12323
12977
  init_cjs_shims();
12324
- var import_types47 = require("@neat.is/types");
12978
+ var import_types48 = require("@neat.is/types");
12325
12979
  var FIRESTORE_COLLECTION_KIND = "firestore-collection";
12326
12980
  var WRITE_METHODS = /* @__PURE__ */ new Set(["write", "create", "update"]);
12327
12981
  function stripComments(src) {
@@ -12461,7 +13115,7 @@ async function addFirestoreRules(graph, services) {
12461
13115
  if (guards.size === 0) return { nodesAdded: 0, edgesAdded: 0 };
12462
13116
  graph.forEachNode((id, attrs) => {
12463
13117
  const node = attrs;
12464
- if (node.type !== import_types47.NodeType.InfraNode) return;
13118
+ if (node.type !== import_types48.NodeType.InfraNode) return;
12465
13119
  if (node.kind !== FIRESTORE_COLLECTION_KIND) return;
12466
13120
  const fields = guards.get(collectionKeyFromName(node.name));
12467
13121
  if (!fields || fields.size === 0) return;
@@ -12474,17 +13128,17 @@ async function addFirestoreRules(graph, services) {
12474
13128
  }
12475
13129
 
12476
13130
  // src/extract/index.ts
12477
- var import_node_path58 = __toESM(require("path"), 1);
13131
+ var import_node_path59 = __toESM(require("path"), 1);
12478
13132
 
12479
13133
  // src/extract/retire.ts
12480
13134
  init_cjs_shims();
12481
13135
  var import_node_fs25 = require("fs");
12482
- var import_node_path57 = __toESM(require("path"), 1);
12483
- var import_types48 = require("@neat.is/types");
13136
+ var import_node_path58 = __toESM(require("path"), 1);
13137
+ var import_types49 = require("@neat.is/types");
12484
13138
  function dropOrphanedFileNodes(graph) {
12485
13139
  const orphans = [];
12486
13140
  graph.forEachNode((id, attrs) => {
12487
- if (attrs.type !== import_types48.NodeType.FileNode) return;
13141
+ if (attrs.type !== import_types49.NodeType.FileNode) return;
12488
13142
  if (graph.inboundEdges(id).length === 0 && graph.outboundEdges(id).length === 0) {
12489
13143
  orphans.push(id);
12490
13144
  }
@@ -12497,14 +13151,14 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
12497
13151
  const bases = [scanPath, ...serviceDirs];
12498
13152
  graph.forEachEdge((id, attrs) => {
12499
13153
  const edge = attrs;
12500
- if (edge.provenance !== import_types48.Provenance.EXTRACTED) return;
13154
+ if (edge.provenance !== import_types49.Provenance.EXTRACTED) return;
12501
13155
  const evidenceFile = edge.evidence?.file;
12502
13156
  if (!evidenceFile) return;
12503
- if (import_node_path57.default.isAbsolute(evidenceFile)) {
13157
+ if (import_node_path58.default.isAbsolute(evidenceFile)) {
12504
13158
  if (!(0, import_node_fs25.existsSync)(evidenceFile)) toDrop.push(id);
12505
13159
  return;
12506
13160
  }
12507
- const found = bases.some((base) => (0, import_node_fs25.existsSync)(import_node_path57.default.join(base, evidenceFile)));
13161
+ const found = bases.some((base) => (0, import_node_fs25.existsSync)(import_node_path58.default.join(base, evidenceFile)));
12508
13162
  if (!found) toDrop.push(id);
12509
13163
  });
12510
13164
  for (const id of toDrop) graph.dropEdge(id);
@@ -12561,7 +13215,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
12561
13215
  }
12562
13216
  const droppedEntries = drainDroppedExtracted();
12563
13217
  if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
12564
- const rejectedPath = import_node_path58.default.join(import_node_path58.default.dirname(opts.errorsPath), "rejected.ndjson");
13218
+ const rejectedPath = import_node_path59.default.join(import_node_path59.default.dirname(opts.errorsPath), "rejected.ndjson");
12565
13219
  try {
12566
13220
  await writeRejectedExtracted(droppedEntries, rejectedPath);
12567
13221
  } catch (err) {
@@ -12596,8 +13250,8 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
12596
13250
  // src/persist.ts
12597
13251
  init_cjs_shims();
12598
13252
  var import_node_fs26 = require("fs");
12599
- var import_node_path59 = __toESM(require("path"), 1);
12600
- var import_types49 = require("@neat.is/types");
13253
+ var import_node_path60 = __toESM(require("path"), 1);
13254
+ var import_types50 = require("@neat.is/types");
12601
13255
  var SCHEMA_VERSION = 7;
12602
13256
  function migrateV1ToV2(payload) {
12603
13257
  const nodes = payload.graph.nodes;
@@ -12621,7 +13275,7 @@ function migrateV5ToV6(payload) {
12621
13275
  if (Array.isArray(nodes)) {
12622
13276
  for (const node of nodes) {
12623
13277
  const attrs = node.attributes;
12624
- if (!attrs || attrs.type !== import_types49.NodeType.InfraNode) continue;
13278
+ if (!attrs || attrs.type !== import_types50.NodeType.InfraNode) continue;
12625
13279
  if (attrs.kind !== "sql-table" && attrs.kind !== "supabase-table") continue;
12626
13280
  if (!Array.isArray(attrs.columns)) attrs.columns = [];
12627
13281
  }
@@ -12637,12 +13291,12 @@ function migrateV2ToV3(payload) {
12637
13291
  for (const edge of edges) {
12638
13292
  const attrs = edge.attributes;
12639
13293
  if (!attrs || attrs.provenance !== "FRONTIER") continue;
12640
- attrs.provenance = import_types49.Provenance.OBSERVED;
13294
+ attrs.provenance = import_types50.Provenance.OBSERVED;
12641
13295
  const type = typeof attrs.type === "string" ? attrs.type : void 0;
12642
13296
  const source = typeof attrs.source === "string" ? attrs.source : void 0;
12643
13297
  const target = typeof attrs.target === "string" ? attrs.target : void 0;
12644
13298
  if (type && source && target) {
12645
- const newId = (0, import_types49.observedEdgeId)(source, target, type);
13299
+ const newId = (0, import_types50.observedEdgeId)(source, target, type);
12646
13300
  attrs.id = newId;
12647
13301
  if (edge.key) edge.key = newId;
12648
13302
  }
@@ -12651,7 +13305,7 @@ function migrateV2ToV3(payload) {
12651
13305
  return { ...payload, schemaVersion: 3 };
12652
13306
  }
12653
13307
  async function ensureDir(filePath) {
12654
- await import_node_fs26.promises.mkdir(import_node_path59.default.dirname(filePath), { recursive: true });
13308
+ await import_node_fs26.promises.mkdir(import_node_path60.default.dirname(filePath), { recursive: true });
12655
13309
  }
12656
13310
  async function saveGraphToDisk(graph, outPath) {
12657
13311
  await ensureDir(outPath);
@@ -12741,23 +13395,23 @@ function startPersistLoop(graph, outPath, opts = {}) {
12741
13395
 
12742
13396
  // src/projects.ts
12743
13397
  init_cjs_shims();
12744
- var import_node_path60 = __toESM(require("path"), 1);
13398
+ var import_node_path61 = __toESM(require("path"), 1);
12745
13399
  function pathsForProject(project, baseDir) {
12746
13400
  if (project === DEFAULT_PROJECT) {
12747
13401
  return {
12748
- snapshotPath: import_node_path60.default.join(baseDir, "graph.json"),
12749
- errorsPath: import_node_path60.default.join(baseDir, "errors.ndjson"),
12750
- staleEventsPath: import_node_path60.default.join(baseDir, "stale-events.ndjson"),
12751
- embeddingsCachePath: import_node_path60.default.join(baseDir, "embeddings.json"),
12752
- policyViolationsPath: import_node_path60.default.join(baseDir, "policy-violations.ndjson")
13402
+ snapshotPath: import_node_path61.default.join(baseDir, "graph.json"),
13403
+ errorsPath: import_node_path61.default.join(baseDir, "errors.ndjson"),
13404
+ staleEventsPath: import_node_path61.default.join(baseDir, "stale-events.ndjson"),
13405
+ embeddingsCachePath: import_node_path61.default.join(baseDir, "embeddings.json"),
13406
+ policyViolationsPath: import_node_path61.default.join(baseDir, "policy-violations.ndjson")
12753
13407
  };
12754
13408
  }
12755
13409
  return {
12756
- snapshotPath: import_node_path60.default.join(baseDir, `${project}.json`),
12757
- errorsPath: import_node_path60.default.join(baseDir, `errors.${project}.ndjson`),
12758
- staleEventsPath: import_node_path60.default.join(baseDir, `stale-events.${project}.ndjson`),
12759
- embeddingsCachePath: import_node_path60.default.join(baseDir, `embeddings.${project}.json`),
12760
- policyViolationsPath: import_node_path60.default.join(baseDir, `policy-violations.${project}.ndjson`)
13410
+ snapshotPath: import_node_path61.default.join(baseDir, `${project}.json`),
13411
+ errorsPath: import_node_path61.default.join(baseDir, `errors.${project}.ndjson`),
13412
+ staleEventsPath: import_node_path61.default.join(baseDir, `stale-events.${project}.ndjson`),
13413
+ embeddingsCachePath: import_node_path61.default.join(baseDir, `embeddings.${project}.json`),
13414
+ policyViolationsPath: import_node_path61.default.join(baseDir, `policy-violations.${project}.ndjson`)
12761
13415
  };
12762
13416
  }
12763
13417
  var Projects = class {
@@ -12795,19 +13449,19 @@ var Projects = class {
12795
13449
  init_cjs_shims();
12796
13450
  var import_fastify2 = __toESM(require("fastify"), 1);
12797
13451
  var import_cors = __toESM(require("@fastify/cors"), 1);
12798
- var import_types79 = require("@neat.is/types");
13452
+ var import_types80 = require("@neat.is/types");
12799
13453
 
12800
13454
  // src/extend/index.ts
12801
13455
  init_cjs_shims();
12802
13456
  var import_node_fs28 = require("fs");
12803
- var import_node_path62 = __toESM(require("path"), 1);
13457
+ var import_node_path63 = __toESM(require("path"), 1);
12804
13458
  var import_node_os2 = __toESM(require("os"), 1);
12805
13459
  var import_instrumentation_registry = require("@neat.is/instrumentation-registry");
12806
13460
 
12807
13461
  // src/installers/package-manager.ts
12808
13462
  init_cjs_shims();
12809
13463
  var import_node_fs27 = require("fs");
12810
- var import_node_path61 = __toESM(require("path"), 1);
13464
+ var import_node_path62 = __toESM(require("path"), 1);
12811
13465
  var import_node_child_process = require("child_process");
12812
13466
  var LOCKFILE_PRIORITY = [
12813
13467
  { lockfile: "bun.lockb", pm: "bun", args: ["install", "--no-summary"] },
@@ -12829,22 +13483,22 @@ async function exists2(p) {
12829
13483
  }
12830
13484
  }
12831
13485
  async function detectPackageManager(serviceDir) {
12832
- let dir = import_node_path61.default.resolve(serviceDir);
13486
+ let dir = import_node_path62.default.resolve(serviceDir);
12833
13487
  const stops = /* @__PURE__ */ new Set();
12834
13488
  for (let i = 0; i < 64; i++) {
12835
13489
  if (stops.has(dir)) break;
12836
13490
  stops.add(dir);
12837
13491
  for (const candidate of LOCKFILE_PRIORITY) {
12838
- const lockPath = import_node_path61.default.join(dir, candidate.lockfile);
13492
+ const lockPath = import_node_path62.default.join(dir, candidate.lockfile);
12839
13493
  if (await exists2(lockPath)) {
12840
13494
  return { pm: candidate.pm, cwd: dir, args: [...candidate.args] };
12841
13495
  }
12842
13496
  }
12843
- const parent = import_node_path61.default.dirname(dir);
13497
+ const parent = import_node_path62.default.dirname(dir);
12844
13498
  if (parent === dir) break;
12845
13499
  dir = parent;
12846
13500
  }
12847
- return { pm: "npm", cwd: import_node_path61.default.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
13501
+ return { pm: "npm", cwd: import_node_path62.default.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
12848
13502
  }
12849
13503
  async function runPackageManagerInstall(cmd) {
12850
13504
  return new Promise((resolve) => {
@@ -12893,7 +13547,7 @@ async function fileExists2(p) {
12893
13547
  }
12894
13548
  }
12895
13549
  async function readPackageJson(scanPath) {
12896
- const pkgPath = import_node_path62.default.join(scanPath, "package.json");
13550
+ const pkgPath = import_node_path63.default.join(scanPath, "package.json");
12897
13551
  const raw = await import_node_fs28.promises.readFile(pkgPath, "utf8");
12898
13552
  return JSON.parse(raw);
12899
13553
  }
@@ -12907,27 +13561,27 @@ var HOOK_WALK_SKIP_DIRS = /* @__PURE__ */ new Set([
12907
13561
  ]);
12908
13562
  async function findHookFiles(scanPath) {
12909
13563
  const found = [];
12910
- const walk8 = async (dir) => {
13564
+ const walk9 = async (dir) => {
12911
13565
  const entries = await import_node_fs28.promises.readdir(dir, { withFileTypes: true }).catch(() => []);
12912
13566
  for (const entry2 of entries) {
12913
13567
  if (entry2.isDirectory()) {
12914
13568
  if (entry2.name.startsWith(".") || HOOK_WALK_SKIP_DIRS.has(entry2.name)) continue;
12915
- await walk8(import_node_path62.default.join(dir, entry2.name));
13569
+ await walk9(import_node_path63.default.join(dir, entry2.name));
12916
13570
  } else if (entry2.isFile()) {
12917
13571
  if ((entry2.name.startsWith("instrumentation") || entry2.name.startsWith("otel-init")) && /\.(ts|js|cjs|mjs)$/.test(entry2.name)) {
12918
- const rel = import_node_path62.default.relative(scanPath, import_node_path62.default.join(dir, entry2.name));
12919
- found.push(rel.split(import_node_path62.default.sep).join("/"));
13572
+ const rel = import_node_path63.default.relative(scanPath, import_node_path63.default.join(dir, entry2.name));
13573
+ found.push(rel.split(import_node_path63.default.sep).join("/"));
12920
13574
  }
12921
13575
  }
12922
13576
  }
12923
13577
  };
12924
- await walk8(scanPath);
13578
+ await walk9(scanPath);
12925
13579
  return found.sort();
12926
13580
  }
12927
13581
  async function pickPrimaryHookFile(scanPath, hookFiles, snippet2) {
12928
13582
  let fallback = null;
12929
13583
  for (const file of hookFiles) {
12930
- const content = await import_node_fs28.promises.readFile(import_node_path62.default.join(scanPath, file), "utf8");
13584
+ const content = await import_node_fs28.promises.readFile(import_node_path63.default.join(scanPath, file), "utf8");
12931
13585
  const patched = splicedContent(content, snippet2);
12932
13586
  if (patched !== null) return { file, content, patched };
12933
13587
  if (fallback === null) fallback = { file, content };
@@ -12935,11 +13589,11 @@ async function pickPrimaryHookFile(scanPath, hookFiles, snippet2) {
12935
13589
  return { file: fallback.file, content: fallback.content, patched: null };
12936
13590
  }
12937
13591
  function extendLogPath() {
12938
- return process.env.NEAT_EXTEND_LOG ?? import_node_path62.default.join(import_node_os2.default.homedir(), ".neat", "extend-log.ndjson");
13592
+ return process.env.NEAT_EXTEND_LOG ?? import_node_path63.default.join(import_node_os2.default.homedir(), ".neat", "extend-log.ndjson");
12939
13593
  }
12940
13594
  async function appendExtendLog(entry2) {
12941
13595
  const logPath = extendLogPath();
12942
- await import_node_fs28.promises.mkdir(import_node_path62.default.dirname(logPath), { recursive: true });
13596
+ await import_node_fs28.promises.mkdir(import_node_path63.default.dirname(logPath), { recursive: true });
12943
13597
  await import_node_fs28.promises.appendFile(logPath, JSON.stringify(entry2) + "\n", "utf8");
12944
13598
  }
12945
13599
  function splicedContent(fileContent, snippet2) {
@@ -12998,7 +13652,7 @@ function lookupInstrumentation(library, installedVersion) {
12998
13652
  }
12999
13653
  async function describeProjectInstrumentation(ctx) {
13000
13654
  const hookFiles = await findHookFiles(ctx.scanPath);
13001
- const envNeat = await fileExists2(import_node_path62.default.join(ctx.scanPath, ".env.neat"));
13655
+ const envNeat = await fileExists2(import_node_path63.default.join(ctx.scanPath, ".env.neat"));
13002
13656
  const registryInstrPackages = new Set(
13003
13657
  (0, import_instrumentation_registry.list)().map((e) => e.instrumentation_package).filter((p) => !!p)
13004
13658
  );
@@ -13020,7 +13674,7 @@ async function applyExtension(ctx, args, options) {
13020
13674
  );
13021
13675
  }
13022
13676
  for (const file of hookFiles) {
13023
- const content = await import_node_fs28.promises.readFile(import_node_path62.default.join(ctx.scanPath, file), "utf8");
13677
+ const content = await import_node_fs28.promises.readFile(import_node_path63.default.join(ctx.scanPath, file), "utf8");
13024
13678
  if (content.includes(args.registration_snippet)) {
13025
13679
  return { library: args.library, filesTouched: [], depsAdded: [], installOutput: "", alreadyApplied: true };
13026
13680
  }
@@ -13032,10 +13686,10 @@ async function applyExtension(ctx, args, options) {
13032
13686
  );
13033
13687
  }
13034
13688
  const primaryFile = primary.file;
13035
- const primaryPath = import_node_path62.default.join(ctx.scanPath, primaryFile);
13689
+ const primaryPath = import_node_path63.default.join(ctx.scanPath, primaryFile);
13036
13690
  const filesTouched = [];
13037
13691
  const depsAdded = [];
13038
- const pkgPath = import_node_path62.default.join(ctx.scanPath, "package.json");
13692
+ const pkgPath = import_node_path63.default.join(ctx.scanPath, "package.json");
13039
13693
  const pkg = await readPackageJson(ctx.scanPath);
13040
13694
  if (!(pkg.dependencies ?? {})[args.instrumentation_package]) {
13041
13695
  pkg.dependencies = { ...pkg.dependencies ?? {}, [args.instrumentation_package]: args.version };
@@ -13074,7 +13728,7 @@ async function dryRunExtension(ctx, args) {
13074
13728
  };
13075
13729
  }
13076
13730
  for (const file of hookFiles) {
13077
- const content = await import_node_fs28.promises.readFile(import_node_path62.default.join(ctx.scanPath, file), "utf8");
13731
+ const content = await import_node_fs28.promises.readFile(import_node_path63.default.join(ctx.scanPath, file), "utf8");
13078
13732
  if (content.includes(args.registration_snippet)) {
13079
13733
  return {
13080
13734
  library: args.library,
@@ -13115,7 +13769,7 @@ async function rollbackExtension(ctx, args) {
13115
13769
  if (!match) {
13116
13770
  return { undone: false, message: "no apply found for library" };
13117
13771
  }
13118
- const pkgPath = import_node_path62.default.join(ctx.scanPath, "package.json");
13772
+ const pkgPath = import_node_path63.default.join(ctx.scanPath, "package.json");
13119
13773
  if (await fileExists2(pkgPath)) {
13120
13774
  const pkg = await readPackageJson(ctx.scanPath);
13121
13775
  if (pkg.dependencies?.[match.instrumentation_package]) {
@@ -13126,7 +13780,7 @@ async function rollbackExtension(ctx, args) {
13126
13780
  }
13127
13781
  const hookFiles = await findHookFiles(ctx.scanPath);
13128
13782
  for (const file of hookFiles) {
13129
- const filePath = import_node_path62.default.join(ctx.scanPath, file);
13783
+ const filePath = import_node_path63.default.join(ctx.scanPath, file);
13130
13784
  const content = await import_node_fs28.promises.readFile(filePath, "utf8");
13131
13785
  if (content.includes(match.registration_snippet)) {
13132
13786
  const filtered = content.split("\n").filter((line) => !line.includes(match.registration_snippet)).join("\n");
@@ -13142,39 +13796,39 @@ async function rollbackExtension(ctx, args) {
13142
13796
 
13143
13797
  // src/divergences.ts
13144
13798
  init_cjs_shims();
13145
- var import_types50 = require("@neat.is/types");
13799
+ var import_types51 = require("@neat.is/types");
13146
13800
  function bucketKey(source, target, type) {
13147
13801
  return `${type}|${source}|${target}`;
13148
13802
  }
13149
13803
  function bucketSourceFor(graph, edge) {
13150
- if (edge.type !== import_types50.EdgeType.CONNECTS_TO) return edge.source;
13151
- const parsed = (0, import_types50.parseFileId)(edge.source);
13804
+ if (edge.type !== import_types51.EdgeType.CONNECTS_TO) return edge.source;
13805
+ const parsed = (0, import_types51.parseFileId)(edge.source);
13152
13806
  if (!parsed || !graph.hasNode(edge.target)) return edge.source;
13153
13807
  const target = graph.getNodeAttributes(edge.target);
13154
- if (target.type !== import_types50.NodeType.DatabaseNode) return edge.source;
13155
- return (0, import_types50.serviceId)(parsed.service);
13808
+ if (target.type !== import_types51.NodeType.DatabaseNode) return edge.source;
13809
+ return (0, import_types51.serviceId)(parsed.service);
13156
13810
  }
13157
13811
  function bucketEdges(graph) {
13158
13812
  const buckets2 = /* @__PURE__ */ new Map();
13159
13813
  graph.forEachEdge((id, attrs) => {
13160
13814
  const e = attrs;
13161
- const parsed = (0, import_types50.parseEdgeId)(id);
13815
+ const parsed = (0, import_types51.parseEdgeId)(id);
13162
13816
  const provenance = parsed?.provenance ?? e.provenance;
13163
13817
  const source = bucketSourceFor(graph, e);
13164
13818
  const key = bucketKey(source, e.target, e.type);
13165
13819
  const cur = buckets2.get(key) ?? { source, target: e.target, type: e.type };
13166
13820
  switch (provenance) {
13167
- case import_types50.Provenance.EXTRACTED:
13821
+ case import_types51.Provenance.EXTRACTED:
13168
13822
  cur.extracted = e;
13169
13823
  break;
13170
- case import_types50.Provenance.OBSERVED:
13824
+ case import_types51.Provenance.OBSERVED:
13171
13825
  cur.observed = e;
13172
13826
  break;
13173
- case import_types50.Provenance.INFERRED:
13827
+ case import_types51.Provenance.INFERRED:
13174
13828
  cur.inferred = e;
13175
13829
  break;
13176
13830
  default:
13177
- if (e.provenance === import_types50.Provenance.STALE) cur.stale = e;
13831
+ if (e.provenance === import_types51.Provenance.STALE) cur.stale = e;
13178
13832
  }
13179
13833
  buckets2.set(key, cur);
13180
13834
  });
@@ -13183,22 +13837,22 @@ function bucketEdges(graph) {
13183
13837
  function nodeIsFrontier(graph, nodeId) {
13184
13838
  if (!graph.hasNode(nodeId)) return false;
13185
13839
  const attrs = graph.getNodeAttributes(nodeId);
13186
- return attrs.type === import_types50.NodeType.FrontierNode;
13840
+ return attrs.type === import_types51.NodeType.FrontierNode;
13187
13841
  }
13188
13842
  function nodeIsWebsocketChannel(graph, nodeId) {
13189
13843
  if (!graph.hasNode(nodeId)) return false;
13190
13844
  const attrs = graph.getNodeAttributes(nodeId);
13191
- return attrs.type === import_types50.NodeType.WebSocketChannelNode;
13845
+ return attrs.type === import_types51.NodeType.WebSocketChannelNode;
13192
13846
  }
13193
13847
  function nodeIsServerAction(graph, nodeId) {
13194
13848
  if (!graph.hasNode(nodeId)) return false;
13195
13849
  const attrs = graph.getNodeAttributes(nodeId);
13196
- return attrs.type === import_types50.NodeType.ServerActionNode;
13850
+ return attrs.type === import_types51.NodeType.ServerActionNode;
13197
13851
  }
13198
13852
  function nodeIsSymbol(graph, nodeId) {
13199
13853
  if (!graph.hasNode(nodeId)) return false;
13200
13854
  const attrs = graph.getNodeAttributes(nodeId);
13201
- return attrs.type === import_types50.NodeType.SymbolNode;
13855
+ return attrs.type === import_types51.NodeType.SymbolNode;
13202
13856
  }
13203
13857
  function clampConfidence(n) {
13204
13858
  if (!Number.isFinite(n)) return 0;
@@ -13218,14 +13872,14 @@ function gradedConfidence(edge) {
13218
13872
  return clampConfidence(confidenceForEdge(edge));
13219
13873
  }
13220
13874
  var OBSERVABLE_EDGE_TYPES = /* @__PURE__ */ new Set([
13221
- import_types50.EdgeType.CALLS,
13222
- import_types50.EdgeType.CONNECTS_TO,
13223
- import_types50.EdgeType.PUBLISHES_TO,
13224
- import_types50.EdgeType.CONSUMES_FROM
13875
+ import_types51.EdgeType.CALLS,
13876
+ import_types51.EdgeType.CONNECTS_TO,
13877
+ import_types51.EdgeType.PUBLISHES_TO,
13878
+ import_types51.EdgeType.CONSUMES_FROM
13225
13879
  ]);
13226
13880
  function detectMissingDivergences(graph, bucket) {
13227
13881
  const out = [];
13228
- if (bucket.type === import_types50.EdgeType.CONTAINS) return out;
13882
+ if (bucket.type === import_types51.EdgeType.CONTAINS) return out;
13229
13883
  if (nodeIsSymbol(graph, bucket.source) || nodeIsSymbol(graph, bucket.target)) return out;
13230
13884
  if (bucket.extracted && !bucket.observed && OBSERVABLE_EDGE_TYPES.has(bucket.type)) {
13231
13885
  if (!nodeIsFrontier(graph, bucket.target) && !nodeIsServerAction(graph, bucket.target)) {
@@ -13267,7 +13921,7 @@ function declaredHostFor(svc) {
13267
13921
  function hasExtractedConfiguredBy(graph, svcId) {
13268
13922
  for (const edgeId of graph.outboundEdges(svcId)) {
13269
13923
  const e = graph.getEdgeAttributes(edgeId);
13270
- if (e.type === import_types50.EdgeType.CONFIGURED_BY && e.provenance === import_types50.Provenance.EXTRACTED) {
13924
+ if (e.type === import_types51.EdgeType.CONFIGURED_BY && e.provenance === import_types51.Provenance.EXTRACTED) {
13271
13925
  return true;
13272
13926
  }
13273
13927
  }
@@ -13280,10 +13934,10 @@ function detectHostMismatch(graph, svcId, svc) {
13280
13934
  const out = [];
13281
13935
  for (const edgeId of graph.outboundEdges(svcId)) {
13282
13936
  const edge = graph.getEdgeAttributes(edgeId);
13283
- if (edge.type !== import_types50.EdgeType.CONNECTS_TO) continue;
13284
- if (edge.provenance !== import_types50.Provenance.OBSERVED) continue;
13937
+ if (edge.type !== import_types51.EdgeType.CONNECTS_TO) continue;
13938
+ if (edge.provenance !== import_types51.Provenance.OBSERVED) continue;
13285
13939
  const target = graph.getNodeAttributes(edge.target);
13286
- if (target.type !== import_types50.NodeType.DatabaseNode) continue;
13940
+ if (target.type !== import_types51.NodeType.DatabaseNode) continue;
13287
13941
  const observedHost = target.host?.trim();
13288
13942
  if (!observedHost) continue;
13289
13943
  if (observedHost === declaredHost) continue;
@@ -13305,10 +13959,10 @@ function detectCompatDivergences(graph, svcId, svc) {
13305
13959
  const deps = svc.dependencies ?? {};
13306
13960
  for (const edgeId of graph.outboundEdges(svcId)) {
13307
13961
  const edge = graph.getEdgeAttributes(edgeId);
13308
- if (edge.type !== import_types50.EdgeType.CONNECTS_TO) continue;
13309
- if (edge.provenance !== import_types50.Provenance.OBSERVED) continue;
13962
+ if (edge.type !== import_types51.EdgeType.CONNECTS_TO) continue;
13963
+ if (edge.provenance !== import_types51.Provenance.OBSERVED) continue;
13310
13964
  const target = graph.getNodeAttributes(edge.target);
13311
- if (target.type !== import_types50.NodeType.DatabaseNode) continue;
13965
+ if (target.type !== import_types51.NodeType.DatabaseNode) continue;
13312
13966
  for (const pair of compatPairs()) {
13313
13967
  if (pair.engine !== target.engine) continue;
13314
13968
  const declared = deps[pair.driver];
@@ -13405,7 +14059,7 @@ function suppressHostMismatchHalves(all) {
13405
14059
  for (const d of all) {
13406
14060
  if (d.type !== "host-mismatch") continue;
13407
14061
  observedHalf.add(`${d.source}->${d.target}`);
13408
- declaredHalf.add((0, import_types50.databaseId)(d.extractedHost));
14062
+ declaredHalf.add((0, import_types51.databaseId)(d.extractedHost));
13409
14063
  }
13410
14064
  if (observedHalf.size === 0) return all;
13411
14065
  return all.filter((d) => {
@@ -13424,13 +14078,13 @@ function computeDivergences(graph, opts = {}) {
13424
14078
  }
13425
14079
  graph.forEachNode((nodeId, attrs) => {
13426
14080
  const n = attrs;
13427
- if (n.type === import_types50.NodeType.ServiceNode) {
14081
+ if (n.type === import_types51.NodeType.ServiceNode) {
13428
14082
  const svc = n;
13429
14083
  for (const d of detectHostMismatch(graph, nodeId, svc)) all.push(d);
13430
14084
  for (const d of detectCompatDivergences(graph, nodeId, svc)) all.push(d);
13431
14085
  return;
13432
14086
  }
13433
- if (n.type === import_types50.NodeType.InfraNode && n.kind === "sql-table") {
14087
+ if (n.type === import_types51.NodeType.InfraNode && n.kind === "sql-table") {
13434
14088
  for (const d of detectColumnDrift(n)) all.push(d);
13435
14089
  }
13436
14090
  });
@@ -13466,7 +14120,7 @@ function computeDivergences(graph, opts = {}) {
13466
14120
  const bc = "column" in b && b.column ? b.column : "";
13467
14121
  return ac.localeCompare(bc);
13468
14122
  });
13469
- return import_types50.DivergenceResultSchema.parse({
14123
+ return import_types51.DivergenceResultSchema.parse({
13470
14124
  divergences: filtered,
13471
14125
  totalAffected: filtered.length,
13472
14126
  computedAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -13599,26 +14253,26 @@ function canonicalJson(value) {
13599
14253
  init_cjs_shims();
13600
14254
  var import_node_fs30 = require("fs");
13601
14255
  var import_node_os3 = __toESM(require("os"), 1);
13602
- var import_node_path63 = __toESM(require("path"), 1);
13603
- var import_types51 = require("@neat.is/types");
14256
+ var import_node_path64 = __toESM(require("path"), 1);
14257
+ var import_types52 = require("@neat.is/types");
13604
14258
  var LOCK_TIMEOUT_MS = 5e3;
13605
14259
  var LOCK_RETRY_MS = 50;
13606
14260
  function neatHome() {
13607
14261
  const override = process.env.NEAT_HOME;
13608
- if (override && override.length > 0) return import_node_path63.default.resolve(override);
13609
- return import_node_path63.default.join(import_node_os3.default.homedir(), ".neat");
14262
+ if (override && override.length > 0) return import_node_path64.default.resolve(override);
14263
+ return import_node_path64.default.join(import_node_os3.default.homedir(), ".neat");
13610
14264
  }
13611
14265
  function registryPath() {
13612
- return import_node_path63.default.join(neatHome(), "projects.json");
14266
+ return import_node_path64.default.join(neatHome(), "projects.json");
13613
14267
  }
13614
14268
  function registryLockPath() {
13615
- return import_node_path63.default.join(neatHome(), "projects.json.lock");
14269
+ return import_node_path64.default.join(neatHome(), "projects.json.lock");
13616
14270
  }
13617
14271
  function daemonPidPath() {
13618
- return import_node_path63.default.join(neatHome(), "neatd.pid");
14272
+ return import_node_path64.default.join(neatHome(), "neatd.pid");
13619
14273
  }
13620
14274
  function daemonsDir() {
13621
- return import_node_path63.default.join(neatHome(), "daemons");
14275
+ return import_node_path64.default.join(neatHome(), "daemons");
13622
14276
  }
13623
14277
  function isFiniteInt(v) {
13624
14278
  return typeof v === "number" && Number.isFinite(v);
@@ -13659,7 +14313,7 @@ async function discoverDaemons(probe = defaultDiscoveryProbe) {
13659
14313
  const out = [];
13660
14314
  for (const name of names) {
13661
14315
  if (!name.endsWith(".json")) continue;
13662
- const file = import_node_path63.default.join(dir, name);
14316
+ const file = import_node_path64.default.join(dir, name);
13663
14317
  let raw;
13664
14318
  try {
13665
14319
  raw = await import_node_fs30.promises.readFile(file, "utf8");
@@ -13736,7 +14390,7 @@ function lockHolderMessage(holder, lockPath, timeoutMs) {
13736
14390
  }
13737
14391
  }
13738
14392
  async function writeAtomically(target, contents) {
13739
- await import_node_fs30.promises.mkdir(import_node_path63.default.dirname(target), { recursive: true });
14393
+ await import_node_fs30.promises.mkdir(import_node_path64.default.dirname(target), { recursive: true });
13740
14394
  const tmp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
13741
14395
  const fd = await import_node_fs30.promises.open(tmp, "w");
13742
14396
  try {
@@ -13749,7 +14403,7 @@ async function writeAtomically(target, contents) {
13749
14403
  }
13750
14404
  async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaultLockHolderProbe) {
13751
14405
  const deadline = Date.now() + timeoutMs;
13752
- await import_node_fs30.promises.mkdir(import_node_path63.default.dirname(lockPath), { recursive: true });
14406
+ await import_node_fs30.promises.mkdir(import_node_path64.default.dirname(lockPath), { recursive: true });
13753
14407
  let probedHolder = false;
13754
14408
  while (true) {
13755
14409
  try {
@@ -13802,10 +14456,10 @@ async function readRegistry() {
13802
14456
  throw err;
13803
14457
  }
13804
14458
  const parsed = JSON.parse(raw);
13805
- return import_types51.RegistryFileSchema.parse(parsed);
14459
+ return import_types52.RegistryFileSchema.parse(parsed);
13806
14460
  }
13807
14461
  async function writeRegistry(reg) {
13808
- const validated = import_types51.RegistryFileSchema.parse(reg);
14462
+ const validated = import_types52.RegistryFileSchema.parse(reg);
13809
14463
  await writeAtomically(registryPath(), JSON.stringify(validated, null, 2) + "\n");
13810
14464
  }
13811
14465
  async function getProject(name) {
@@ -13950,7 +14604,7 @@ init_auth();
13950
14604
  // src/connectors-config.ts
13951
14605
  init_cjs_shims();
13952
14606
  var import_node_os4 = __toESM(require("os"), 1);
13953
- var import_node_path64 = __toESM(require("path"), 1);
14607
+ var import_node_path65 = __toESM(require("path"), 1);
13954
14608
  var import_node_fs31 = require("fs");
13955
14609
  var CONNECTORS_CONFIG_VERSION = 1;
13956
14610
  var EnvRefUnsetError = class extends Error {
@@ -13965,11 +14619,11 @@ var EnvRefUnsetError = class extends Error {
13965
14619
  };
13966
14620
  function neatHome2() {
13967
14621
  const override = process.env.NEAT_HOME;
13968
- if (override && override.length > 0) return import_node_path64.default.resolve(override);
13969
- return import_node_path64.default.join(import_node_os4.default.homedir(), ".neat");
14622
+ if (override && override.length > 0) return import_node_path65.default.resolve(override);
14623
+ return import_node_path65.default.join(import_node_os4.default.homedir(), ".neat");
13970
14624
  }
13971
14625
  function connectorsConfigPath(home = neatHome2()) {
13972
- return import_node_path64.default.join(home, "connectors.json");
14626
+ return import_node_path65.default.join(home, "connectors.json");
13973
14627
  }
13974
14628
  var MODE_MASK_LOOSER_THAN_0600 = 63;
13975
14629
  async function warnIfModeLooserThan0600(file) {
@@ -14156,15 +14810,15 @@ function getConnectorStatus(id, now = Date.now(), thresholdMs = CONNECTOR_STALE_
14156
14810
 
14157
14811
  // src/connectors/index.ts
14158
14812
  init_cjs_shims();
14159
- var import_types52 = require("@neat.is/types");
14813
+ var import_types53 = require("@neat.is/types");
14160
14814
  var NO_ENV = "unknown";
14161
14815
  function staticCallSiteFor(graph, serviceName, targetNodeId) {
14162
14816
  if (!graph.hasNode(targetNodeId)) return void 0;
14163
14817
  const sites = [];
14164
14818
  for (const edgeId of graph.inboundEdges(targetNodeId)) {
14165
14819
  const edge = graph.getEdgeAttributes(edgeId);
14166
- if (edge.provenance !== import_types52.Provenance.EXTRACTED) continue;
14167
- const parsed = (0, import_types52.parseFileId)(edge.source);
14820
+ if (edge.provenance !== import_types53.Provenance.EXTRACTED) continue;
14821
+ const parsed = (0, import_types53.parseFileId)(edge.source);
14168
14822
  if (!parsed || parsed.service !== serviceName || !edge.evidence) continue;
14169
14823
  const site = { relPath: edge.evidence.file };
14170
14824
  if (edge.evidence.line !== void 0) site.line = edge.evidence.line;
@@ -14175,7 +14829,7 @@ function staticCallSiteFor(graph, serviceName, targetNodeId) {
14175
14829
  function routeCallSiteFor(graph, targetNodeId) {
14176
14830
  if (!graph.hasNode(targetNodeId)) return void 0;
14177
14831
  const attrs = graph.getNodeAttributes(targetNodeId);
14178
- if (attrs.type !== import_types52.NodeType.RouteNode || !attrs.path) return void 0;
14832
+ if (attrs.type !== import_types53.NodeType.RouteNode || !attrs.path) return void 0;
14179
14833
  const site = { relPath: attrs.path };
14180
14834
  if (attrs.line !== void 0) site.line = attrs.line;
14181
14835
  return site;
@@ -14656,10 +15310,10 @@ var SUPABASE_RPC_TARGET_KIND = "supabase-rpc";
14656
15310
  // src/connectors/supabase/map.ts
14657
15311
  var REST_RPC_PATH_RE = /^\/rest\/v1\/rpc\/([^/?]+)/;
14658
15312
  var REST_TABLE_PATH_RE = /^\/rest\/v1\/([^/?]+)/;
14659
- function targetFromRestPath(path69) {
14660
- const rpcMatch = REST_RPC_PATH_RE.exec(path69);
15313
+ function targetFromRestPath(path70) {
15314
+ const rpcMatch = REST_RPC_PATH_RE.exec(path70);
14661
15315
  if (rpcMatch) return { targetKind: SUPABASE_RPC_TARGET_KIND, name: rpcMatch[1] };
14662
- const tableMatch = REST_TABLE_PATH_RE.exec(path69);
15316
+ const tableMatch = REST_TABLE_PATH_RE.exec(path70);
14663
15317
  if (tableMatch) return { targetKind: SUPABASE_TABLE_TARGET_KIND, name: tableMatch[1] };
14664
15318
  return null;
14665
15319
  }
@@ -14770,23 +15424,23 @@ async function fetchPgStatStatements(connectionString, limit = DEFAULT_STATEMENT
14770
15424
 
14771
15425
  // src/connectors/supabase/resolve.ts
14772
15426
  init_cjs_shims();
14773
- var import_types54 = require("@neat.is/types");
15427
+ var import_types55 = require("@neat.is/types");
14774
15428
  function createSupabaseResolveTarget(graph, config) {
14775
15429
  return (signal, _ctx) => {
14776
15430
  if (signal.targetKind !== SUPABASE_TABLE_TARGET_KIND && signal.targetKind !== SUPABASE_RPC_TARGET_KIND) {
14777
15431
  return null;
14778
15432
  }
14779
- const subResourceId = (0, import_types54.infraId)(signal.targetKind, `${config.nodeRef}/${signal.targetName}`);
15433
+ const subResourceId = (0, import_types55.infraId)(signal.targetKind, `${config.nodeRef}/${signal.targetName}`);
14780
15434
  if (graph.hasNode(subResourceId)) {
14781
- return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType: import_types54.EdgeType.CALLS };
15435
+ return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType: import_types55.EdgeType.CALLS };
14782
15436
  }
14783
- const bareResourceId = (0, import_types54.infraId)(signal.targetKind, signal.targetName);
15437
+ const bareResourceId = (0, import_types55.infraId)(signal.targetKind, signal.targetName);
14784
15438
  if (graph.hasNode(bareResourceId)) {
14785
- return { targetNodeId: bareResourceId, serviceName: config.serviceName, edgeType: import_types54.EdgeType.CALLS };
15439
+ return { targetNodeId: bareResourceId, serviceName: config.serviceName, edgeType: import_types55.EdgeType.CALLS };
14786
15440
  }
14787
- const projectLevelId = (0, import_types54.infraId)("supabase", config.nodeRef);
15441
+ const projectLevelId = (0, import_types55.infraId)("supabase", config.nodeRef);
14788
15442
  if (graph.hasNode(projectLevelId)) {
14789
- return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType: import_types54.EdgeType.CALLS };
15443
+ return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType: import_types55.EdgeType.CALLS };
14790
15444
  }
14791
15445
  return null;
14792
15446
  };
@@ -14879,7 +15533,7 @@ function createSupabaseConnector(graph, config, deps = {}) {
14879
15533
 
14880
15534
  // src/connectors/railway/index.ts
14881
15535
  init_cjs_shims();
14882
- var import_types58 = require("@neat.is/types");
15536
+ var import_types59 = require("@neat.is/types");
14883
15537
 
14884
15538
  // src/connectors/railway/client.ts
14885
15539
  init_cjs_shims();
@@ -15030,7 +15684,7 @@ function buildRailwayRouteIndex(graph, serviceName) {
15030
15684
  const out = [];
15031
15685
  graph.forEachNode((_id, attrs) => {
15032
15686
  const node = attrs;
15033
- if (node.type !== import_types58.NodeType.RouteNode) return;
15687
+ if (node.type !== import_types59.NodeType.RouteNode) return;
15034
15688
  const route = attrs;
15035
15689
  if (route.service !== serviceName) return;
15036
15690
  out.push({
@@ -15134,12 +15788,12 @@ function createRailwayResolveTarget(config) {
15134
15788
  const serviceName = config.serviceNameById[config.serviceId];
15135
15789
  if (!serviceName) return null;
15136
15790
  if (signal.targetKind === ROUTE_TARGET_KIND) {
15137
- return { targetNodeId: signal.targetName, serviceName, edgeType: import_types58.EdgeType.CALLS };
15791
+ return { targetNodeId: signal.targetName, serviceName, edgeType: import_types59.EdgeType.CALLS };
15138
15792
  }
15139
15793
  if (signal.targetKind === PEER_SERVICE_TARGET_KIND) {
15140
15794
  const peerName = config.serviceNameById[signal.targetName];
15141
15795
  if (!peerName) return null;
15142
- return { targetNodeId: (0, import_types58.serviceId)(peerName), serviceName, edgeType: import_types58.EdgeType.CONNECTS_TO };
15796
+ return { targetNodeId: (0, import_types59.serviceId)(peerName), serviceName, edgeType: import_types59.EdgeType.CONNECTS_TO };
15143
15797
  }
15144
15798
  return null;
15145
15799
  };
@@ -15263,9 +15917,9 @@ function parseFirebaseTargetName(targetName) {
15263
15917
  const secondSep = rest.indexOf(FIELD_SEP);
15264
15918
  if (secondSep === -1) return null;
15265
15919
  const method = rest.slice(0, secondSep);
15266
- const path69 = rest.slice(secondSep + 1);
15267
- if (!resourceName || !method || !path69) return null;
15268
- return { resourceName, method, path: path69 };
15920
+ const path70 = rest.slice(secondSep + 1);
15921
+ if (!resourceName || !method || !path70) return null;
15922
+ return { resourceName, method, path: path70 };
15269
15923
  }
15270
15924
  function resourceNameFor(type, labels) {
15271
15925
  if (!labels) return null;
@@ -15303,14 +15957,14 @@ function mapLogEntryToSignal(entry2) {
15303
15957
  if (!req2) return null;
15304
15958
  if (typeof req2.requestMethod !== "string" || req2.requestMethod.length === 0) return null;
15305
15959
  const method = req2.requestMethod.toUpperCase();
15306
- const path69 = pathFromRequestUrl(req2.requestUrl);
15307
- if (path69 === null) return null;
15960
+ const path70 = pathFromRequestUrl(req2.requestUrl);
15961
+ if (path70 === null) return null;
15308
15962
  const timestamp = entry2.timestamp;
15309
15963
  if (typeof timestamp !== "string" || timestamp.length === 0) return null;
15310
15964
  const isError = typeof req2.status === "number" && req2.status >= ERROR_STATUS_THRESHOLD2;
15311
15965
  return {
15312
15966
  targetKind: resourceType,
15313
- targetName: packFirebaseTargetName({ resourceName, method, path: path69 }),
15967
+ targetName: packFirebaseTargetName({ resourceName, method, path: path70 }),
15314
15968
  callCount: 1,
15315
15969
  errorCount: isError ? 1 : 0,
15316
15970
  lastObservedIso: timestamp
@@ -15327,7 +15981,7 @@ function mapLogEntriesToSignals(entries) {
15327
15981
 
15328
15982
  // src/connectors/firebase/resolve.ts
15329
15983
  init_cjs_shims();
15330
- var import_types59 = require("@neat.is/types");
15984
+ var import_types60 = require("@neat.is/types");
15331
15985
  function neatServiceNameFor(resourceType, resourceName, serviceMap) {
15332
15986
  switch (resourceType) {
15333
15987
  case "cloud_function":
@@ -15342,7 +15996,7 @@ function routeEntriesFor(graph, serviceName) {
15342
15996
  const entries = [];
15343
15997
  graph.forEachNode((_id, attrs) => {
15344
15998
  const node = attrs;
15345
- if (node.type !== import_types59.NodeType.RouteNode) return;
15999
+ if (node.type !== import_types60.NodeType.RouteNode) return;
15346
16000
  const route = attrs;
15347
16001
  if (route.service !== serviceName) return;
15348
16002
  entries.push({
@@ -15374,7 +16028,7 @@ function createFirebaseResolveTarget(graph, serviceMap) {
15374
16028
  return {
15375
16029
  targetNodeId: match.routeNodeId,
15376
16030
  serviceName,
15377
- edgeType: import_types59.EdgeType.CALLS
16031
+ edgeType: import_types60.EdgeType.CALLS
15378
16032
  };
15379
16033
  };
15380
16034
  }
@@ -15401,7 +16055,7 @@ init_cjs_shims();
15401
16055
 
15402
16056
  // src/connectors/cloudflare/connector.ts
15403
16057
  init_cjs_shims();
15404
- var import_types61 = require("@neat.is/types");
16058
+ var import_types62 = require("@neat.is/types");
15405
16059
 
15406
16060
  // src/connectors/cloudflare/client.ts
15407
16061
  init_cjs_shims();
@@ -15517,7 +16171,7 @@ function mapEventToSignal(event) {
15517
16171
  if (Number.isNaN(observedAt.getTime())) return null;
15518
16172
  const statusCode = metadata?.statusCode;
15519
16173
  const isError = typeof statusCode === "number" && statusCode >= ERROR_STATUS_THRESHOLD3;
15520
- const path69 = metadata?.trigger ? parsePathFromTrigger(metadata.trigger) : void 0;
16174
+ const path70 = metadata?.trigger ? parsePathFromTrigger(metadata.trigger) : void 0;
15521
16175
  return {
15522
16176
  targetKind: CLOUDFLARE_TARGET_KIND,
15523
16177
  targetName: scriptName,
@@ -15525,7 +16179,7 @@ function mapEventToSignal(event) {
15525
16179
  errorCount: isError ? 1 : 0,
15526
16180
  lastObservedIso: observedAt.toISOString(),
15527
16181
  method,
15528
- ...path69 ? { path: path69 } : {},
16182
+ ...path70 ? { path: path70 } : {},
15529
16183
  ...typeof statusCode === "number" ? { statusCode } : {},
15530
16184
  ...typeof metadata?.duration === "number" ? { duration: metadata.duration } : {}
15531
16185
  };
@@ -15565,19 +16219,19 @@ function findTaggedWorkerFileNode(graph, workerName) {
15565
16219
  graph.forEachNode((id, attrs) => {
15566
16220
  if (found) return;
15567
16221
  const a = attrs;
15568
- if (a.type === import_types61.NodeType.FileNode && a.platform === "cloudflare" && a.platformName === workerName) {
16222
+ if (a.type === import_types62.NodeType.FileNode && a.platform === "cloudflare" && a.platformName === workerName) {
15569
16223
  found = id;
15570
16224
  }
15571
16225
  });
15572
16226
  return found;
15573
16227
  }
15574
- function findMatchingRouteNode(graph, serviceName, method, path69) {
15575
- const normalizedPath = normalizePathTemplate(path69);
16228
+ function findMatchingRouteNode(graph, serviceName, method, path70) {
16229
+ const normalizedPath = normalizePathTemplate(path70);
15576
16230
  let found = null;
15577
16231
  graph.forEachNode((id, attrs) => {
15578
16232
  if (found) return;
15579
16233
  const a = attrs;
15580
- if (a.type !== import_types61.NodeType.RouteNode || a.service !== serviceName) return;
16234
+ if (a.type !== import_types62.NodeType.RouteNode || a.service !== serviceName) return;
15581
16235
  if (!a.pathTemplate || normalizePathTemplate(a.pathTemplate) !== normalizedPath) return;
15582
16236
  const routeMethod = (a.method ?? "").toUpperCase();
15583
16237
  if (routeMethod !== "ALL" && routeMethod !== method) return;
@@ -15589,18 +16243,18 @@ function createCloudflareResolveTarget(config, graph) {
15589
16243
  return (signal) => {
15590
16244
  if (signal.targetKind !== CLOUDFLARE_TARGET_KIND) return null;
15591
16245
  const scriptName = signal.targetName;
15592
- const { method, path: path69 } = signal;
16246
+ const { method, path: path70 } = signal;
15593
16247
  const resolveRouteGrain = (serviceName, wholeFileId) => {
15594
- if (!method || !path69) return wholeFileId;
15595
- return findMatchingRouteNode(graph, serviceName, method, path69) ?? wholeFileId;
16248
+ if (!method || !path70) return wholeFileId;
16249
+ return findMatchingRouteNode(graph, serviceName, method, path70) ?? wholeFileId;
15596
16250
  };
15597
16251
  const mapping = config.workers?.[scriptName];
15598
16252
  if (mapping) {
15599
- const wholeFileId = (0, import_types61.fileId)(mapping.service, mapping.entryFile);
16253
+ const wholeFileId = (0, import_types62.fileId)(mapping.service, mapping.entryFile);
15600
16254
  return {
15601
16255
  targetNodeId: resolveRouteGrain(mapping.service, wholeFileId),
15602
16256
  serviceName: mapping.service,
15603
- edgeType: import_types61.EdgeType.CALLS
16257
+ edgeType: import_types62.EdgeType.CALLS
15604
16258
  };
15605
16259
  }
15606
16260
  const taggedFileId = findTaggedWorkerFileNode(graph, scriptName);
@@ -15609,13 +16263,13 @@ function createCloudflareResolveTarget(config, graph) {
15609
16263
  return {
15610
16264
  targetNodeId: resolveRouteGrain(fileNode.service, taggedFileId),
15611
16265
  serviceName: fileNode.service,
15612
- edgeType: import_types61.EdgeType.CALLS
16266
+ edgeType: import_types62.EdgeType.CALLS
15613
16267
  };
15614
16268
  }
15615
16269
  return {
15616
- targetNodeId: (0, import_types61.infraId)("cloudflare-worker", scriptName),
16270
+ targetNodeId: (0, import_types62.infraId)("cloudflare-worker", scriptName),
15617
16271
  serviceName: scriptName,
15618
- edgeType: import_types61.EdgeType.CALLS,
16272
+ edgeType: import_types62.EdgeType.CALLS,
15619
16273
  ensureInfraNode: { kind: "cloudflare-worker", name: scriptName, provider: "cloudflare" }
15620
16274
  };
15621
16275
  };
@@ -15811,14 +16465,14 @@ function diffNeonStatementsToSignals(rows, previous, observedAtIso) {
15811
16465
 
15812
16466
  // src/connectors/neon/resolve.ts
15813
16467
  init_cjs_shims();
15814
- var import_types65 = require("@neat.is/types");
16468
+ var import_types66 = require("@neat.is/types");
15815
16469
  function createNeonResolveTarget(config) {
15816
16470
  return (signal) => {
15817
16471
  if (signal.targetKind !== NEON_SQL_TABLE_TARGET_KIND || !signal.targetName) return null;
15818
16472
  return {
15819
- targetNodeId: (0, import_types65.infraId)("sql-table", signal.targetName),
16473
+ targetNodeId: (0, import_types66.infraId)("sql-table", signal.targetName),
15820
16474
  serviceName: config.serviceName,
15821
- edgeType: import_types65.EdgeType.CALLS,
16475
+ edgeType: import_types66.EdgeType.CALLS,
15822
16476
  ensureInfraNode: { kind: "sql-table", name: signal.targetName, provider: "neon" }
15823
16477
  };
15824
16478
  };
@@ -15944,9 +16598,9 @@ function parseCloudRunTargetName(targetName) {
15944
16598
  const secondSep = rest.indexOf(FIELD_SEP2);
15945
16599
  if (secondSep === -1) return null;
15946
16600
  const method = rest.slice(0, secondSep);
15947
- const path69 = rest.slice(secondSep + 1);
15948
- if (!serviceName || !method || !path69) return null;
15949
- return { serviceName, method, path: path69 };
16601
+ const path70 = rest.slice(secondSep + 1);
16602
+ if (!serviceName || !method || !path70) return null;
16603
+ return { serviceName, method, path: path70 };
15950
16604
  }
15951
16605
 
15952
16606
  // src/connectors/cloud-run/map.ts
@@ -15975,14 +16629,14 @@ function mapLogEntryToSignal2(entry2) {
15975
16629
  if (!req2) return null;
15976
16630
  if (typeof req2.requestMethod !== "string" || req2.requestMethod.length === 0) return null;
15977
16631
  const method = req2.requestMethod.toUpperCase();
15978
- const path69 = pathFromRequestUrl2(req2.requestUrl);
15979
- if (path69 === null) return null;
16632
+ const path70 = pathFromRequestUrl2(req2.requestUrl);
16633
+ if (path70 === null) return null;
15980
16634
  const timestamp = entry2.timestamp;
15981
16635
  if (typeof timestamp !== "string" || timestamp.length === 0) return null;
15982
16636
  const isError = typeof req2.status === "number" && req2.status >= ERROR_STATUS_THRESHOLD4;
15983
16637
  return {
15984
16638
  targetKind: CLOUD_RUN_TARGET_KIND,
15985
- targetName: packCloudRunTargetName({ serviceName, method, path: path69 }),
16639
+ targetName: packCloudRunTargetName({ serviceName, method, path: path70 }),
15986
16640
  callCount: 1,
15987
16641
  errorCount: isError ? 1 : 0,
15988
16642
  lastObservedIso: timestamp
@@ -15999,14 +16653,14 @@ function mapLogEntriesToSignals2(entries) {
15999
16653
 
16000
16654
  // src/connectors/cloud-run/resolve.ts
16001
16655
  init_cjs_shims();
16002
- var import_types69 = require("@neat.is/types");
16656
+ var import_types70 = require("@neat.is/types");
16003
16657
  var CLOUD_RUN_SERVICE_INFRA_KIND = "cloud-run-service";
16004
16658
  function findMatchingRouteNode2(graph, serviceName, method, normalizedPath) {
16005
16659
  let found = null;
16006
16660
  graph.forEachNode((_id, attrs) => {
16007
16661
  if (found) return;
16008
16662
  const node = attrs;
16009
- if (node.type !== import_types69.NodeType.RouteNode) return;
16663
+ if (node.type !== import_types70.NodeType.RouteNode) return;
16010
16664
  const route = attrs;
16011
16665
  if (route.service !== serviceName || !route.pathTemplate) return;
16012
16666
  if (normalizePathTemplate(route.pathTemplate) !== normalizedPath) return;
@@ -16021,23 +16675,23 @@ function createCloudRunResolveTarget(graph, config) {
16021
16675
  if (signal.targetKind !== CLOUD_RUN_TARGET_KIND) return null;
16022
16676
  const identity = parseCloudRunTargetName(signal.targetName);
16023
16677
  if (!identity) return null;
16024
- const { serviceName: gcpServiceName, method, path: path69 } = identity;
16678
+ const { serviceName: gcpServiceName, method, path: path70 } = identity;
16025
16679
  const mappedService = config.serviceMap?.[gcpServiceName];
16026
16680
  if (mappedService) {
16027
16681
  const routeNodeId = findMatchingRouteNode2(
16028
16682
  graph,
16029
16683
  mappedService,
16030
16684
  method,
16031
- normalizePathTemplate(path69)
16685
+ normalizePathTemplate(path70)
16032
16686
  );
16033
16687
  if (routeNodeId) {
16034
- return { targetNodeId: routeNodeId, serviceName: mappedService, edgeType: import_types69.EdgeType.CALLS };
16688
+ return { targetNodeId: routeNodeId, serviceName: mappedService, edgeType: import_types70.EdgeType.CALLS };
16035
16689
  }
16036
16690
  }
16037
16691
  return {
16038
- targetNodeId: (0, import_types69.infraId)(CLOUD_RUN_SERVICE_INFRA_KIND, gcpServiceName),
16692
+ targetNodeId: (0, import_types70.infraId)(CLOUD_RUN_SERVICE_INFRA_KIND, gcpServiceName),
16039
16693
  serviceName: mappedService ?? gcpServiceName,
16040
- edgeType: import_types69.EdgeType.CALLS,
16694
+ edgeType: import_types70.EdgeType.CALLS,
16041
16695
  ensureInfraNode: {
16042
16696
  kind: CLOUD_RUN_SERVICE_INFRA_KIND,
16043
16697
  name: gcpServiceName,
@@ -16078,7 +16732,7 @@ function createCloudRunConnector(graph, config = {}) {
16078
16732
 
16079
16733
  // src/connectors/render/index.ts
16080
16734
  init_cjs_shims();
16081
- var import_types72 = require("@neat.is/types");
16735
+ var import_types73 = require("@neat.is/types");
16082
16736
 
16083
16737
  // src/connectors/render/types.ts
16084
16738
  init_cjs_shims();
@@ -16156,7 +16810,7 @@ function buildRenderRouteIndex(graph, serviceName) {
16156
16810
  const out = [];
16157
16811
  graph.forEachNode((_id, attrs) => {
16158
16812
  const node = attrs;
16159
- if (node.type !== import_types72.NodeType.RouteNode) return;
16813
+ if (node.type !== import_types73.NodeType.RouteNode) return;
16160
16814
  const route = attrs;
16161
16815
  if (route.service !== serviceName) return;
16162
16816
  out.push({
@@ -16241,7 +16895,7 @@ function mapRenderRequestLogsToSignals(entries, routeIndex) {
16241
16895
  function createRenderResolveTarget(config) {
16242
16896
  return (signal) => {
16243
16897
  if (signal.targetKind === ROUTE_TARGET_KIND2) {
16244
- return { targetNodeId: signal.targetName, serviceName: config.serviceName, edgeType: import_types72.EdgeType.CALLS };
16898
+ return { targetNodeId: signal.targetName, serviceName: config.serviceName, edgeType: import_types73.EdgeType.CALLS };
16245
16899
  }
16246
16900
  return null;
16247
16901
  };
@@ -16379,21 +17033,21 @@ function mapInsightsToSignals(rows, observedAtIso) {
16379
17033
 
16380
17034
  // src/connectors/planetscale/resolve.ts
16381
17035
  init_cjs_shims();
16382
- var import_types76 = require("@neat.is/types");
17036
+ var import_types77 = require("@neat.is/types");
16383
17037
  var PLANETSCALE_DATABASE_KIND = "planetscale-database";
16384
17038
  function createPlanetscaleResolveTarget(graph, config) {
16385
17039
  const databaseName = `${config.organization}/${config.database}`;
16386
17040
  return (signal, _ctx) => {
16387
17041
  if (signal.targetKind !== PLANETSCALE_SQL_TABLE_TARGET_KIND || !signal.targetName) return null;
16388
- const tableId = (0, import_types76.infraId)("sql-table", signal.targetName);
17042
+ const tableId = (0, import_types77.infraId)("sql-table", signal.targetName);
16389
17043
  if (graph.hasNode(tableId)) {
16390
- return { targetNodeId: tableId, serviceName: config.serviceName, edgeType: import_types76.EdgeType.CALLS };
17044
+ return { targetNodeId: tableId, serviceName: config.serviceName, edgeType: import_types77.EdgeType.CALLS };
16391
17045
  }
16392
- const providerId = (0, import_types76.infraId)(PLANETSCALE_DATABASE_KIND, databaseName);
17046
+ const providerId = (0, import_types77.infraId)(PLANETSCALE_DATABASE_KIND, databaseName);
16393
17047
  return {
16394
17048
  targetNodeId: providerId,
16395
17049
  serviceName: config.serviceName,
16396
- edgeType: import_types76.EdgeType.CALLS,
17050
+ edgeType: import_types77.EdgeType.CALLS,
16397
17051
  ensureInfraNode: { kind: PLANETSCALE_DATABASE_KIND, name: databaseName, provider: "planetscale" }
16398
17052
  };
16399
17053
  };
@@ -17063,11 +17717,11 @@ function registerRoutes(scope, ctx) {
17063
17717
  const candidates = req2.query.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
17064
17718
  const parsed = [];
17065
17719
  for (const c of candidates) {
17066
- const r = import_types79.DivergenceTypeSchema.safeParse(c);
17720
+ const r = import_types80.DivergenceTypeSchema.safeParse(c);
17067
17721
  if (!r.success) {
17068
17722
  return reply.code(400).send({
17069
17723
  error: `unknown divergence type "${c}"`,
17070
- allowed: import_types79.DivergenceTypeSchema.options
17724
+ allowed: import_types80.DivergenceTypeSchema.options
17071
17725
  });
17072
17726
  }
17073
17727
  parsed.push(r.data);
@@ -17376,7 +18030,7 @@ function registerRoutes(scope, ctx) {
17376
18030
  const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
17377
18031
  let violations = await log.readAll();
17378
18032
  if (req2.query.severity) {
17379
- const sev = import_types79.PolicySeveritySchema.safeParse(req2.query.severity);
18033
+ const sev = import_types80.PolicySeveritySchema.safeParse(req2.query.severity);
17380
18034
  if (!sev.success) {
17381
18035
  return reply.code(400).send({
17382
18036
  error: "invalid severity",
@@ -17415,7 +18069,7 @@ function registerRoutes(scope, ctx) {
17415
18069
  scope.post("/policies/check", async (req2, reply) => {
17416
18070
  const proj = resolveProject(registry, req2, reply, ctx.bootstrap, ctx.singleProject);
17417
18071
  if (!proj) return;
17418
- const parsed = import_types79.PoliciesCheckBodySchema.safeParse(req2.body ?? {});
18072
+ const parsed = import_types80.PoliciesCheckBodySchema.safeParse(req2.body ?? {});
17419
18073
  if (!parsed.success) {
17420
18074
  return reply.code(400).send({
17421
18075
  error: "invalid /policies/check body",
@@ -17737,7 +18391,7 @@ init_auth();
17737
18391
  // src/unrouted.ts
17738
18392
  init_cjs_shims();
17739
18393
  var import_node_fs32 = require("fs");
17740
- var import_node_path65 = __toESM(require("path"), 1);
18394
+ var import_node_path66 = __toESM(require("path"), 1);
17741
18395
  function buildUnroutedSpanRecord(serviceName, traceId, now = /* @__PURE__ */ new Date()) {
17742
18396
  return {
17743
18397
  timestamp: now.toISOString(),
@@ -17747,34 +18401,34 @@ function buildUnroutedSpanRecord(serviceName, traceId, now = /* @__PURE__ */ new
17747
18401
  };
17748
18402
  }
17749
18403
  async function appendUnroutedSpan(neatHome4, record) {
17750
- const target = import_node_path65.default.join(neatHome4, "errors.ndjson");
18404
+ const target = import_node_path66.default.join(neatHome4, "errors.ndjson");
17751
18405
  await import_node_fs32.promises.mkdir(neatHome4, { recursive: true });
17752
18406
  await import_node_fs32.promises.appendFile(target, JSON.stringify(record) + "\n", "utf8");
17753
18407
  }
17754
18408
  function unroutedErrorsPath(neatHome4) {
17755
- return import_node_path65.default.join(neatHome4, "errors.ndjson");
18409
+ return import_node_path66.default.join(neatHome4, "errors.ndjson");
17756
18410
  }
17757
18411
 
17758
18412
  // src/daemon.ts
17759
- var import_types80 = require("@neat.is/types");
18413
+ var import_types81 = require("@neat.is/types");
17760
18414
  function daemonJsonPath(scanPath) {
17761
- return import_node_path66.default.join(scanPath, "neat-out", "daemon.json");
18415
+ return import_node_path67.default.join(scanPath, "neat-out", "daemon.json");
17762
18416
  }
17763
18417
  function daemonsDiscoveryDir(home) {
17764
18418
  const base = home && home.length > 0 ? home : neatHomeFromEnv();
17765
- return import_node_path66.default.join(base, "daemons");
18419
+ return import_node_path67.default.join(base, "daemons");
17766
18420
  }
17767
18421
  function daemonDiscoveryPath(project, home) {
17768
- return import_node_path66.default.join(daemonsDiscoveryDir(home), `${sanitizeDiscoveryName(project)}.json`);
18422
+ return import_node_path67.default.join(daemonsDiscoveryDir(home), `${sanitizeDiscoveryName(project)}.json`);
17769
18423
  }
17770
18424
  function sanitizeDiscoveryName(project) {
17771
18425
  return project.replace(/[^A-Za-z0-9._-]/g, "_");
17772
18426
  }
17773
18427
  function neatHomeFromEnv() {
17774
18428
  const env = process.env.NEAT_HOME;
17775
- if (env && env.length > 0) return import_node_path66.default.resolve(env);
18429
+ if (env && env.length > 0) return import_node_path67.default.resolve(env);
17776
18430
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
17777
- return import_node_path66.default.join(home, ".neat");
18431
+ return import_node_path67.default.join(home, ".neat");
17778
18432
  }
17779
18433
  function resolveNeatVersion() {
17780
18434
  if (process.env.NEAT_LOCAL_VERSION && process.env.NEAT_LOCAL_VERSION.length > 0) {
@@ -17843,11 +18497,11 @@ function teardownSlot(slot) {
17843
18497
  }
17844
18498
  }
17845
18499
  function neatHomeFor(opts) {
17846
- if (opts.neatHome && opts.neatHome.length > 0) return import_node_path66.default.resolve(opts.neatHome);
18500
+ if (opts.neatHome && opts.neatHome.length > 0) return import_node_path67.default.resolve(opts.neatHome);
17847
18501
  const env = process.env.NEAT_HOME;
17848
- if (env && env.length > 0) return import_node_path66.default.resolve(env);
18502
+ if (env && env.length > 0) return import_node_path67.default.resolve(env);
17849
18503
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
17850
- return import_node_path66.default.join(home, ".neat");
18504
+ return import_node_path67.default.join(home, ".neat");
17851
18505
  }
17852
18506
  function routeSpanToProject(serviceName, projects) {
17853
18507
  if (!serviceName) return DEFAULT_PROJECT;
@@ -17895,11 +18549,11 @@ function spanBelongsToSingleProject(graph, project, serviceName) {
17895
18549
  if (!serviceName) return true;
17896
18550
  if (serviceNameMatchesProject(serviceName, project)) return true;
17897
18551
  return graph.someNode(
17898
- (_id, attrs) => attrs.type === import_types80.NodeType.ServiceNode && attrs.name === serviceName
18552
+ (_id, attrs) => attrs.type === import_types81.NodeType.ServiceNode && attrs.name === serviceName
17899
18553
  );
17900
18554
  }
17901
18555
  async function bootstrapProject(entry2, connectors = [], neatHome4) {
17902
- const paths = pathsForProject(entry2.name, import_node_path66.default.join(entry2.path, "neat-out"));
18556
+ const paths = pathsForProject(entry2.name, import_node_path67.default.join(entry2.path, "neat-out"));
17903
18557
  try {
17904
18558
  const stat = await import_node_fs33.promises.stat(entry2.path);
17905
18559
  if (!stat.isDirectory()) {
@@ -18015,7 +18669,7 @@ async function startDaemon(opts = {}) {
18015
18669
  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;
18016
18670
  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;
18017
18671
  const singleProject = projectArg;
18018
- const singleProjectPath = singleProject && projectPathArg ? import_node_path66.default.resolve(projectPathArg) : null;
18672
+ const singleProjectPath = singleProject && projectPathArg ? import_node_path67.default.resolve(projectPathArg) : null;
18019
18673
  if (singleProject && !singleProjectPath) {
18020
18674
  throw new Error(
18021
18675
  `neatd: project "${singleProject}" given without a projectPath; pass NEAT_PROJECT_PATH alongside NEAT_PROJECT.`
@@ -18030,7 +18684,7 @@ async function startDaemon(opts = {}) {
18030
18684
  );
18031
18685
  }
18032
18686
  }
18033
- const pidPath = import_node_path66.default.join(home, "neatd.pid");
18687
+ const pidPath = import_node_path67.default.join(home, "neatd.pid");
18034
18688
  await writeAtomically(pidPath, `${process.pid}
18035
18689
  `);
18036
18690
  const slots = /* @__PURE__ */ new Map();
@@ -18442,8 +19096,8 @@ async function startDaemon(opts = {}) {
18442
19096
  let registryWatcher = null;
18443
19097
  let reloadTimer = null;
18444
19098
  if (!singleProject) try {
18445
- const regDir = import_node_path66.default.dirname(regPath);
18446
- const regBase = import_node_path66.default.basename(regPath);
19099
+ const regDir = import_node_path67.default.dirname(regPath);
19100
+ const regBase = import_node_path67.default.basename(regPath);
18447
19101
  registryWatcher = (0, import_node_fs33.watch)(regDir, (_eventType, filename) => {
18448
19102
  if (filename !== null && filename !== regBase) return;
18449
19103
  if (reloadTimer) clearTimeout(reloadTimer);
@@ -18518,7 +19172,7 @@ init_cjs_shims();
18518
19172
  var import_node_child_process2 = require("child_process");
18519
19173
  var import_node_fs34 = require("fs");
18520
19174
  var import_node_net = __toESM(require("net"), 1);
18521
- var import_node_path67 = __toESM(require("path"), 1);
19175
+ var import_node_path68 = __toESM(require("path"), 1);
18522
19176
  var DEFAULT_WEB_PORT = 6328;
18523
19177
  var DEFAULT_REST_PORT = 8080;
18524
19178
  function asValidPort(value) {
@@ -18527,11 +19181,11 @@ function asValidPort(value) {
18527
19181
  }
18528
19182
  function projectRoot() {
18529
19183
  const fromEnv = process.env.NEAT_SCAN_PATH;
18530
- return import_node_path67.default.resolve(fromEnv && fromEnv.length > 0 ? fromEnv : process.cwd());
19184
+ return import_node_path68.default.resolve(fromEnv && fromEnv.length > 0 ? fromEnv : process.cwd());
18531
19185
  }
18532
19186
  async function readDaemonPorts(root) {
18533
19187
  try {
18534
- const raw = await import_node_fs34.promises.readFile(import_node_path67.default.join(root, "neat-out", "daemon.json"), "utf8");
19188
+ const raw = await import_node_fs34.promises.readFile(import_node_path68.default.join(root, "neat-out", "daemon.json"), "utf8");
18535
19189
  const parsed = JSON.parse(raw);
18536
19190
  const ports = parsed?.ports ?? {};
18537
19191
  return { web: asValidPort(ports.web), rest: asValidPort(ports.rest) };
@@ -18576,10 +19230,10 @@ function resolveWebPackageDir() {
18576
19230
  eval("require")
18577
19231
  );
18578
19232
  const pkgJsonPath = req.resolve("@neat.is/web/package.json");
18579
- return import_node_path67.default.dirname(pkgJsonPath);
19233
+ return import_node_path68.default.dirname(pkgJsonPath);
18580
19234
  }
18581
19235
  function resolveStandaloneServerEntry(webDir) {
18582
- return import_node_path67.default.join(webDir, ".next/standalone/packages/web/server.js");
19236
+ return import_node_path68.default.join(webDir, ".next/standalone/packages/web/server.js");
18583
19237
  }
18584
19238
  async function pickInternalPort() {
18585
19239
  return new Promise((resolve, reject) => {
@@ -18632,7 +19286,7 @@ async function spawnWebUI(restPort, opts = {}) {
18632
19286
  NEAT_API_URL: apiUrl
18633
19287
  };
18634
19288
  child = (0, import_node_child_process2.spawn)(process.execPath, [serverEntry], {
18635
- cwd: import_node_path67.default.dirname(serverEntry),
19289
+ cwd: import_node_path68.default.dirname(serverEntry),
18636
19290
  env,
18637
19291
  stdio: ["ignore", "inherit", "inherit"],
18638
19292
  detached: false
@@ -18808,14 +19462,14 @@ function localVersion() {
18808
19462
  }
18809
19463
  function neatHome3() {
18810
19464
  if (process.env.NEAT_HOME && process.env.NEAT_HOME.length > 0) {
18811
- return import_node_path68.default.resolve(process.env.NEAT_HOME);
19465
+ return import_node_path69.default.resolve(process.env.NEAT_HOME);
18812
19466
  }
18813
19467
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
18814
- return import_node_path68.default.join(home, ".neat");
19468
+ return import_node_path69.default.join(home, ".neat");
18815
19469
  }
18816
19470
  async function readPid() {
18817
19471
  try {
18818
- const raw = await import_node_fs35.promises.readFile(import_node_path68.default.join(neatHome3(), "neatd.pid"), "utf8");
19472
+ const raw = await import_node_fs35.promises.readFile(import_node_path69.default.join(neatHome3(), "neatd.pid"), "utf8");
18819
19473
  const n = Number.parseInt(raw.trim(), 10);
18820
19474
  return Number.isFinite(n) ? n : null;
18821
19475
  } catch {